From 859ef6c2504bcb5abae3d42db6b88a9843ee8cfb Mon Sep 17 00:00:00 2001 From: Lorenzo Chesi Date: Mon, 2 Oct 2023 18:21:35 +0200 Subject: [PATCH 01/39] #121 --- Dockerfile | 2 +- src/components/backend/core/Core.py | 14 +++++++++++++- requirements.txt => src/requirements.txt | 0 3 files changed, 14 insertions(+), 2 deletions(-) rename requirements.txt => src/requirements.txt (100%) diff --git a/Dockerfile b/Dockerfile index b074c87..e314dc4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,7 @@ RUN pip3 install --no-cache-dir --upgrade pip RUN pip3 install config --upgrade --no-cache-dir -COPY requirements.txt /tmp/ +COPY src/requirements.txt /tmp/ RUN pip3 install --no-cache-dir -r /tmp/requirements.txt diff --git a/src/components/backend/core/Core.py b/src/components/backend/core/Core.py index 0796da5..cb285d1 100644 --- a/src/components/backend/core/Core.py +++ b/src/components/backend/core/Core.py @@ -39,6 +39,7 @@ def __init__(self, *, ### Setup Thread ### super().__init__(name=self.__class__.__name__, daemon=True) + self.semaphore = threading.Condition() self.version = ctx.VERSION ### Setup logger ### @@ -143,7 +144,7 @@ def run(self): wait = next_run - time.time() self.log.info(f"╰───────────────────────────────────「{time.strftime('%d %b %Y %H:%M:%S', time.localtime(next_run))}」───────────────────────────────────╯") self.log.info("") - if wait > 0: time.sleep(wait) + if wait > 0: self.semaphore.wait(timeout=wait) except Exception as e: # Errore interno non recuperabile self.log.critical("]─────────────────────────────────────────[CRITICAL]─────────────────────────────────────────[") @@ -176,6 +177,17 @@ def job(self): except (aw.DeprecatedLibrary, httpx.HTTPError) as e: self.log.error(cs.red(f"🅴🆁🆁🅾🆁: {e}")) + def wakeUp(self) -> bool: + """ + Fa partire immediatamente il processo di ricerca e download. + """ + try: + self.semaphore.notify() + except RuntimeError: + return False + else: + return True + # def join(self) -> None: # super().join() diff --git a/requirements.txt b/src/requirements.txt similarity index 100% rename from requirements.txt rename to src/requirements.txt From 13c3650465a8a6c0894b55f773321eded39406ff Mon Sep 17 00:00:00 2001 From: Lorenzo Chesi Date: Tue, 3 Oct 2023 18:41:56 +0200 Subject: [PATCH 02/39] Ricerca automatica --- .gitignore | 3 ++- src/components/backend/connection/ExternalDB.py | 15 ++++++++++++++- tests/dump/ExternalDB.json | 0 tests/test_components.py | 7 +++++++ 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 tests/dump/ExternalDB.json diff --git a/.gitignore b/.gitignore index 8042ba1..edeea00 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .vscode/ *.pyc -*.log \ No newline at end of file +*.log +node_modules \ No newline at end of file diff --git a/src/components/backend/connection/ExternalDB.py b/src/components/backend/connection/ExternalDB.py index 53e0de7..19b31eb 100644 --- a/src/components/backend/connection/ExternalDB.py +++ b/src/components/backend/connection/ExternalDB.py @@ -73,8 +73,21 @@ def find(self, title:str, season:int, tvdb_id:int) -> Optional[dict[str, str]]: # Se non ho trovato nulla ritorno None if len(res) == 0: return None + # Converto le stagioni in numeri + def conv2num(x): + if x["season"] == 'winter': + x["season"] = 0 + elif x["season"] == 'spring': + x["season"] = 1 + elif x["season"] == 'summer': + x["season"] = 2 + else: + x["season"] = 3 + return x + res = list(map(conv2num, res)) + # Riordino per data - res.sort(key=lambda x: (x["release"] is None, x["release"])) + res.sort(key=lambda x: (x["year"], x["season"]), reverse=True) # Controllo se esiste la stagione if len(res) < season: return None diff --git a/tests/dump/ExternalDB.json b/tests/dump/ExternalDB.json new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_components.py b/tests/test_components.py index 82f889d..b3b3d2a 100644 --- a/tests/test_components.py +++ b/tests/test_components.py @@ -1,6 +1,7 @@ import unittest from src.components.backend.core import Core +from src.components.backend.connection import ExternalDB from src.components.backend.core import Constant as ctx from src.components.backend.connection.Sonarr import Sonarr from src.components.frontend import Frontend @@ -69,6 +70,12 @@ def testFrontend(self): app = Frontend(self.core) app.run(debug=False, host='0.0.0.0', use_reloader=False) + def testExternalDB(self): + ex = ExternalDB() + ex.sync() + with open(DUMP_FOLDER.joinpath('ExternalDB.json'), 'w') as f: + json.dump(ex.find('Tokyo Revengers', 3, 393478), f) + if __name__ == '__main__': unittest.main(verbosity=2, buffer=True) \ No newline at end of file From e82c1d1aae352cf9647d12428493b31d13097a4d Mon Sep 17 00:00:00 2001 From: Lorenzo Chesi Date: Wed, 4 Oct 2023 09:43:04 +0200 Subject: [PATCH 03/39] fix --- src/components/backend/core/Core.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/backend/core/Core.py b/src/components/backend/core/Core.py index cb285d1..d24b88e 100644 --- a/src/components/backend/core/Core.py +++ b/src/components/backend/core/Core.py @@ -39,7 +39,7 @@ def __init__(self, *, ### Setup Thread ### super().__init__(name=self.__class__.__name__, daemon=True) - self.semaphore = threading.Condition() + self.semaphore = threading.Semaphore(0) self.version = ctx.VERSION ### Setup logger ### @@ -144,7 +144,7 @@ def run(self): wait = next_run - time.time() self.log.info(f"╰───────────────────────────────────「{time.strftime('%d %b %Y %H:%M:%S', time.localtime(next_run))}」───────────────────────────────────╯") self.log.info("") - if wait > 0: self.semaphore.wait(timeout=wait) + if wait > 0: self.semaphore.acquire(timeout=wait) except Exception as e: # Errore interno non recuperabile self.log.critical("]─────────────────────────────────────────[CRITICAL]─────────────────────────────────────────[") @@ -182,7 +182,7 @@ def wakeUp(self) -> bool: Fa partire immediatamente il processo di ricerca e download. """ try: - self.semaphore.notify() + self.semaphore.release() except RuntimeError: return False else: From eb57601ebddf85e7aa319ec3fa1043f069d36913 Mon Sep 17 00:00:00 2001 From: Lorenzo Chesi Date: Thu, 12 Oct 2023 09:12:53 +0200 Subject: [PATCH 04/39] frontend --- DEV.md | 13 + frontend/.eslintrc.cjs | 18 + frontend/.gitignore | 24 + frontend/README.md | 27 + frontend/index.html | 16 + frontend/log.html | 13 + frontend/package-lock.json | 3067 +++++++++++++++++ frontend/package.json | 32 + .../static => frontend/public}/favicon.ico | Bin frontend/settings.html | 13 + frontend/src/assets/react.svg | 1 + frontend/src/components/Container/index.scss | 11 + frontend/src/components/Container/index.tsx | 14 + frontend/src/components/Footer/index.scss | 9 + frontend/src/components/Footer/index.tsx | 13 + frontend/src/components/Header/index.scss | 50 + frontend/src/components/Header/index.tsx | 22 + frontend/src/components/Icon/index.scss | 1 + frontend/src/components/Icon/index.tsx | 9 + frontend/src/components/Navigator/index.scss | 84 + frontend/src/components/Navigator/index.tsx | 30 + frontend/src/components/Table/index.scss | 73 + frontend/src/components/Table/index.tsx | 120 + frontend/src/components/index.tsx | 6 + frontend/src/pages/home/App.tsx | 31 + frontend/src/pages/home/main.tsx | 10 + frontend/src/pages/log/App.tsx | 10 + frontend/src/pages/log/main.tsx | 12 + frontend/src/pages/settings/App.tsx | 17 + frontend/src/pages/settings/main.tsx | 12 + frontend/src/styles/_global.scss | 3 + frontend/src/styles/base.scss | 35 + frontend/src/utils/API.ts | 17 + frontend/src/vite-env.d.ts | 4 + frontend/tsconfig.json | 30 + frontend/vite.config.ts | 27 + .../{frontend => __frontend__OLD}/__init__.py | 0 .../{frontend => __frontend__OLD}/api.py | 0 .../{frontend => __frontend__OLD}/app.py | 0 .../static/css/animation.css | 0 .../static/css/card.css | 0 .../static/css/elems.css | 0 .../static/css/index.css | 0 .../static/css/input.css | 0 .../static/css/log.css | 0 .../static/css/nav.css | 0 .../static/css/settings.css | 0 .../static/css/table.css | 0 .../__frontend__OLD/static/favicon.ico | Bin 0 -> 2007 bytes .../static/images/card-background.png | Bin .../static/js/base/nav.js | 0 .../static/js/components/components.js | 0 .../static/js/index/elems.js | 0 .../static/js/index/table.js | 0 .../static/js/log/ansi.js | 0 .../static/js/log/info.js | 0 .../static/js/log/log.js | 0 .../static/js/settings/settings.js | 0 .../templates/base.html | 0 .../templates/index.html | 0 .../templates/log.html | 0 .../templates/settings.html | 0 src/components/__init__.py | 2 +- src/components/api/TableRoute.py | 32 + src/components/api/__init__.py | 39 + src/dev_api.py | 30 + src/main.py | 7 +- src/requirements.txt | 5 +- tests/database/table.json | 776 ++++- tests/test_components.py | 7 +- 70 files changed, 4761 insertions(+), 11 deletions(-) create mode 100644 DEV.md create mode 100644 frontend/.eslintrc.cjs create mode 100644 frontend/.gitignore create mode 100644 frontend/README.md create mode 100644 frontend/index.html create mode 100644 frontend/log.html create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json rename {src/components/frontend/static => frontend/public}/favicon.ico (100%) create mode 100644 frontend/settings.html create mode 100644 frontend/src/assets/react.svg create mode 100644 frontend/src/components/Container/index.scss create mode 100644 frontend/src/components/Container/index.tsx create mode 100644 frontend/src/components/Footer/index.scss create mode 100644 frontend/src/components/Footer/index.tsx create mode 100644 frontend/src/components/Header/index.scss create mode 100644 frontend/src/components/Header/index.tsx create mode 100644 frontend/src/components/Icon/index.scss create mode 100644 frontend/src/components/Icon/index.tsx create mode 100644 frontend/src/components/Navigator/index.scss create mode 100644 frontend/src/components/Navigator/index.tsx create mode 100644 frontend/src/components/Table/index.scss create mode 100644 frontend/src/components/Table/index.tsx create mode 100644 frontend/src/components/index.tsx create mode 100644 frontend/src/pages/home/App.tsx create mode 100644 frontend/src/pages/home/main.tsx create mode 100644 frontend/src/pages/log/App.tsx create mode 100644 frontend/src/pages/log/main.tsx create mode 100644 frontend/src/pages/settings/App.tsx create mode 100644 frontend/src/pages/settings/main.tsx create mode 100644 frontend/src/styles/_global.scss create mode 100644 frontend/src/styles/base.scss create mode 100644 frontend/src/utils/API.ts create mode 100644 frontend/src/vite-env.d.ts create mode 100644 frontend/tsconfig.json create mode 100644 frontend/vite.config.ts rename src/components/{frontend => __frontend__OLD}/__init__.py (100%) rename src/components/{frontend => __frontend__OLD}/api.py (100%) rename src/components/{frontend => __frontend__OLD}/app.py (100%) rename src/components/{frontend => __frontend__OLD}/static/css/animation.css (100%) rename src/components/{frontend => __frontend__OLD}/static/css/card.css (100%) rename src/components/{frontend => __frontend__OLD}/static/css/elems.css (100%) rename src/components/{frontend => __frontend__OLD}/static/css/index.css (100%) rename src/components/{frontend => __frontend__OLD}/static/css/input.css (100%) rename src/components/{frontend => __frontend__OLD}/static/css/log.css (100%) rename src/components/{frontend => __frontend__OLD}/static/css/nav.css (100%) rename src/components/{frontend => __frontend__OLD}/static/css/settings.css (100%) rename src/components/{frontend => __frontend__OLD}/static/css/table.css (100%) create mode 100644 src/components/__frontend__OLD/static/favicon.ico rename src/components/{frontend => __frontend__OLD}/static/images/card-background.png (100%) rename src/components/{frontend => __frontend__OLD}/static/js/base/nav.js (100%) rename src/components/{frontend => __frontend__OLD}/static/js/components/components.js (100%) rename src/components/{frontend => __frontend__OLD}/static/js/index/elems.js (100%) rename src/components/{frontend => __frontend__OLD}/static/js/index/table.js (100%) rename src/components/{frontend => __frontend__OLD}/static/js/log/ansi.js (100%) rename src/components/{frontend => __frontend__OLD}/static/js/log/info.js (100%) rename src/components/{frontend => __frontend__OLD}/static/js/log/log.js (100%) rename src/components/{frontend => __frontend__OLD}/static/js/settings/settings.js (100%) rename src/components/{frontend => __frontend__OLD}/templates/base.html (100%) rename src/components/{frontend => __frontend__OLD}/templates/index.html (100%) rename src/components/{frontend => __frontend__OLD}/templates/log.html (100%) rename src/components/{frontend => __frontend__OLD}/templates/settings.html (100%) create mode 100644 src/components/api/TableRoute.py create mode 100644 src/components/api/__init__.py create mode 100644 src/dev_api.py diff --git a/DEV.md b/DEV.md new file mode 100644 index 0000000..dc709e1 --- /dev/null +++ b/DEV.md @@ -0,0 +1,13 @@ +Breve documentazione per costruire l'interfaccia: + +1. Avviare il backend: +```sh +cd src +python3 ./dev_apipy +``` + +2. Avviare l'interfaccia: +```sh +cd frontend +npm run dev +``` \ No newline at end of file diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs new file mode 100644 index 0000000..d6c9537 --- /dev/null +++ b/frontend/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..1ebe379 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,27 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: ['./tsconfig.json', './tsconfig.node.json'], + tsconfigRootDir: __dirname, + }, +``` + +- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` +- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..0c33897 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,16 @@ + + + + + + + + + + Home + + +
+ + + diff --git a/frontend/log.html b/frontend/log.html new file mode 100644 index 0000000..81cec20 --- /dev/null +++ b/frontend/log.html @@ -0,0 +1,13 @@ + + + + + + + Log + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..bbc41e3 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,3067 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/node": "^20.8.0", + "@types/react": "^18.2.15", + "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "@vitejs/plugin-react": "^4.0.3", + "eslint": "^8.45.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.3", + "sass": "^1.68.0", + "typescript": "^5.0.2", + "vite": "^4.4.5" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.20.tgz", + "integrity": "sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.0.tgz", + "integrity": "sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helpers": "^7.23.0", + "@babel/parser": "^7.23.0", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.0", + "@babel/types": "^7.23.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", + "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", + "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.1.tgz", + "integrity": "sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.0", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.22.5.tgz", + "integrity": "sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.22.5.tgz", + "integrity": "sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.0.tgz", + "integrity": "sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", + "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.22.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", + "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", + "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", + "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz", + "integrity": "sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.5.tgz", + "integrity": "sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.2.tgz", + "integrity": "sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.2.tgz", + "integrity": "sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", + "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.8.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.0.tgz", + "integrity": "sha512-LzcWltT83s1bthcvjBmiBvGJiiUe84NWRHkw+ZV6Fr41z2FbIzvc815dk2nQ3RAKMuN2fkenM/z3Xv2QzEpYxQ==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.8", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.8.tgz", + "integrity": "sha512-kMpQpfZKSCBqltAJwskgePRaYRFukDkm1oItcAbC3gNELR20XIBcN9VRgg4+m8DKsTfkWeA4m4Imp4DDuWy7FQ==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.2.24", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.24.tgz", + "integrity": "sha512-Ee0Jt4sbJxMu1iDcetZEIKQr99J1Zfb6D4F3qfUWoR1JpInkY1Wdg4WwCyBjL257D0+jGqSl1twBjV8iCaC0Aw==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.8.tgz", + "integrity": "sha512-bAIvO5lN/U8sPGvs1Xm61rlRHHaq5rp5N3kp9C+NJ/Q41P8iqjkXSu0+/qu8POsjH9pNWb0OYabFez7taP7omw==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.4", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.4.tgz", + "integrity": "sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.3.tgz", + "integrity": "sha512-vntq452UHNltxsaaN+L9WyuMch8bMd9CqJ3zhzTPXXidwbf5mqqKCVXEuvRZUqLJSTLeWE65lQwyXsRGnXkCTA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.7.3", + "@typescript-eslint/type-utils": "6.7.3", + "@typescript-eslint/utils": "6.7.3", + "@typescript-eslint/visitor-keys": "6.7.3", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.3.tgz", + "integrity": "sha512-TlutE+iep2o7R8Lf+yoer3zU6/0EAUc8QIBB3GYBc1KGz4c4TRm83xwXUZVPlZ6YCLss4r77jbu6j3sendJoiQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.7.3", + "@typescript-eslint/types": "6.7.3", + "@typescript-eslint/typescript-estree": "6.7.3", + "@typescript-eslint/visitor-keys": "6.7.3", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.3.tgz", + "integrity": "sha512-wOlo0QnEou9cHO2TdkJmzF7DFGvAKEnB82PuPNHpT8ZKKaZu6Bm63ugOTn9fXNJtvuDPanBc78lGUGGytJoVzQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.7.3", + "@typescript-eslint/visitor-keys": "6.7.3" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.3.tgz", + "integrity": "sha512-Fc68K0aTDrKIBvLnKTZ5Pf3MXK495YErrbHb1R6aTpfK5OdSFj0rVN7ib6Tx6ePrZ2gsjLqr0s98NG7l96KSQw==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.7.3", + "@typescript-eslint/utils": "6.7.3", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.3.tgz", + "integrity": "sha512-4g+de6roB2NFcfkZb439tigpAMnvEIg3rIjWQ+EM7IBaYt/CdJt6em9BJ4h4UpdgaBWdmx2iWsafHTrqmgIPNw==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.3.tgz", + "integrity": "sha512-YLQ3tJoS4VxLFYHTw21oe1/vIZPRqAO91z6Uv0Ss2BKm/Ag7/RVQBcXTGcXhgJMdA4U+HrKuY5gWlJlvoaKZ5g==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.7.3", + "@typescript-eslint/visitor-keys": "6.7.3", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.3.tgz", + "integrity": "sha512-vzLkVder21GpWRrmSR9JxGZ5+ibIUSudXlW52qeKpzUEQhRSmyZiVDDj3crAth7+5tmN1ulvgKaCU2f/bPRCzg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.7.3", + "@typescript-eslint/types": "6.7.3", + "@typescript-eslint/typescript-estree": "6.7.3", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.3.tgz", + "integrity": "sha512-HEVXkU9IB+nk9o63CeICMHxFWbHWr3E1mpilIQBe9+7L/lH97rleFLVtYsfnWB+JVMaiFnEaxvknvmIzX+CqVg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.7.3", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.1.0.tgz", + "integrity": "sha512-rM0SqazU9iqPUraQ2JlIvReeaxOoRj6n+PzB1C0cBzIbd8qP336nC39/R9yPi3wVcah7E7j/kdU1uCUqMEU4OQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.22.20", + "@babel/plugin-transform-react-jsx-self": "^7.22.5", + "@babel/plugin-transform-react-jsx-source": "^7.22.5", + "@types/babel__core": "^7.20.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0" + } + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001542", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001542.tgz", + "integrity": "sha512-UrtAXVcj1mvPBFQ4sKd38daP8dEcXXr5sQe6QNNinaPd0iA/cxg9/l3VrSdL73jgw5sKyuQ6jNgiKO12W3SsVA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.537", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.537.tgz", + "integrity": "sha512-W1+g9qs9hviII0HAwOdehGYkr+zt7KKdmCcJcjH0mYg6oL8+ioT3Skjmt7BLoAQqXhjf40AXd+HlR4oAWMlXjA==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", + "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.50.0", + "@humanwhocodes/config-array": "^0.11.11", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.3.tgz", + "integrity": "sha512-Hh0wv8bUNY877+sI0BlCUlsS0TYYQqvzEwJsJJPM2WF4RnTStSnSR3zdJYa2nPOJgg3UghXi54lVyMSmpCalzA==", + "dev": true, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.22.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", + "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", + "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "dev": true, + "dependencies": { + "flatted": "^3.2.7", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", + "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", + "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-refresh": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", + "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sass": { + "version": "1.68.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.68.0.tgz", + "integrity": "sha512-Lmj9lM/fef0nQswm1J2HJcEsBUba4wgNx2fea6yJHODREoMFnwRpZydBnX/RjyXw2REIwdkbqE4hrTo4qfDBUA==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", + "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", + "dev": true, + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..d3649c3 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,32 @@ +{ + "name": "frontend", + "description": "Interfaccia per Sonarr-AnimeDownloader ", + "private": true, + "version": "0.0.0", + "homepage": ".", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/node": "^20.8.0", + "@types/react": "^18.2.15", + "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "@vitejs/plugin-react": "^4.0.3", + "eslint": "^8.45.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.3", + "sass": "^1.68.0", + "typescript": "^5.0.2", + "vite": "^4.4.5" + } +} diff --git a/src/components/frontend/static/favicon.ico b/frontend/public/favicon.ico similarity index 100% rename from src/components/frontend/static/favicon.ico rename to frontend/public/favicon.ico diff --git a/frontend/settings.html b/frontend/settings.html new file mode 100644 index 0000000..f29580c --- /dev/null +++ b/frontend/settings.html @@ -0,0 +1,13 @@ + + + + + + + Settings + + +
+ + + diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/Container/index.scss b/frontend/src/components/Container/index.scss new file mode 100644 index 0000000..c891353 --- /dev/null +++ b/frontend/src/components/Container/index.scss @@ -0,0 +1,11 @@ +main { + max-width: 1280px; + width: 60%; + margin: auto; + display: grid; + gap: 50px; + + @media screen and (max-width: 900px){ + width: 95%; + } +} \ No newline at end of file diff --git a/frontend/src/components/Container/index.tsx b/frontend/src/components/Container/index.tsx new file mode 100644 index 0000000..63e9fff --- /dev/null +++ b/frontend/src/components/Container/index.tsx @@ -0,0 +1,14 @@ +import { ReactNode } from 'react'; + +import "./index.scss" + +interface ContainerProps { + children: ReactNode +} +export function Container({children}: ContainerProps) { + return ( +
+ {children} +
+ ); +} \ No newline at end of file diff --git a/frontend/src/components/Footer/index.scss b/frontend/src/components/Footer/index.scss new file mode 100644 index 0000000..243b678 --- /dev/null +++ b/frontend/src/components/Footer/index.scss @@ -0,0 +1,9 @@ +@use "sass:color"; +@use '@/styles/global' as *; + +footer { + background-color: color.adjust($background-color, $whiteness: 3%); + margin-top: 5vh; + text-align: center; + padding: 20px 50px; +} \ No newline at end of file diff --git a/frontend/src/components/Footer/index.tsx b/frontend/src/components/Footer/index.tsx new file mode 100644 index 0000000..1e3ae94 --- /dev/null +++ b/frontend/src/components/Footer/index.tsx @@ -0,0 +1,13 @@ +import "./index.scss" + +interface FooterProps { + version: string +} + +export function Footer({version}:FooterProps) { + return ( + + ); +} \ No newline at end of file diff --git a/frontend/src/components/Header/index.scss b/frontend/src/components/Header/index.scss new file mode 100644 index 0000000..381a57e --- /dev/null +++ b/frontend/src/components/Header/index.scss @@ -0,0 +1,50 @@ +@use "sass:color"; +@use '@/styles/global' as *; + +header { + background-color: color.adjust($background-color, $whiteness: 8%); + padding: 15px 5vw; + margin-bottom: 5vh; + box-shadow: 0 1px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12), 0 3px 1px -2px rgba(0,0,0,.2); + + > h1 { + text-align: center; + margin: 10px; + } +} + +#donate{ + @keyframes HeartBeat { + 0% { + transform: scale(0.8); + } + 5% { + transform: scale(0.9); + } + 10% { + transform: scale(0.8); + } + 15% { + transform: scale(1); + } + 50% { + transform: scale(0.8); + } + 100% { + transform: scale(0.8); + } + } + + transition: background-color, color 0.1s; + animation: HeartBeat 1.5s infinite; + position: absolute; + right: 20px; + top: 20px; + text-shadow: 0 0 10px $base-color; + text-decoration: none; + + &:hover { + color: $base-color; + } + +} \ No newline at end of file diff --git a/frontend/src/components/Header/index.tsx b/frontend/src/components/Header/index.tsx new file mode 100644 index 0000000..cb76432 --- /dev/null +++ b/frontend/src/components/Header/index.tsx @@ -0,0 +1,22 @@ +import { ReactNode } from "react" +import { Icon } from ".." +import "./index.scss" + +interface HeaderProps { + title: string, + children?: ReactNode +} + +export function Header(props: HeaderProps) { + return ( +
+

{props.title}

+ + + + {props.children} +
+ ) +} \ No newline at end of file diff --git a/frontend/src/components/Icon/index.scss b/frontend/src/components/Icon/index.scss new file mode 100644 index 0000000..9355b22 --- /dev/null +++ b/frontend/src/components/Icon/index.scss @@ -0,0 +1 @@ +@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@24,400,1,0'); diff --git a/frontend/src/components/Icon/index.tsx b/frontend/src/components/Icon/index.tsx new file mode 100644 index 0000000..7e584b5 --- /dev/null +++ b/frontend/src/components/Icon/index.tsx @@ -0,0 +1,9 @@ +import "./index.scss" + +interface IconProps { + icon: string +} + +export function Icon({icon}: IconProps) { + return {icon}; +} \ No newline at end of file diff --git a/frontend/src/components/Navigator/index.scss b/frontend/src/components/Navigator/index.scss new file mode 100644 index 0000000..b661801 --- /dev/null +++ b/frontend/src/components/Navigator/index.scss @@ -0,0 +1,84 @@ +@use "sass:color"; +@use '@/styles/global' as *; + +nav { + height: 100%; + width: 0; + position: fixed; + z-index: 2; + top: 0; + left: 0; + background-color: color.adjust($background-color, $whiteness: 8%); + overflow-x: hidden; + padding-top: 60px; + transition: 0.5s; + box-shadow: 0 2px 2px 0 rgb(0 0 0 / 14%), 0 3px 1px -2px rgb(0 0 0 / 12%), 0 1px 5px 0 rgb(0 0 0 / 20%); + + > div:nth-child(1) { + position: fixed; + top: 0; + left: 0; + width: 0; + height: 100%; + background-color: rgba(0,0,0,0); + transition: background-color 0.5s; + } + + &.active { + width: 200px; + + > div:nth-child(1) { + width: 100%; + background-color: rgba(0,0,0,0.2); + } + } + + > div:nth-child(2) { + position: relative; + top: 0; + left: 0; + padding: 10px; + text-align: center; + + > a { + display: block; + color: rgba(255,255,255,0.5); + text-decoration: none; + width: max-content; + margin: 10px; + cursor: pointer; + padding: 5px; + border-bottom: 1.5px solid rgba(0,0,0,0.4); + + &:active, &:visited { + color: rgba(255,255,255,0.5); + } + + &:hover { + color: $base-color; + } + } + } + + + + button { + background-color: transparent; + border: 0; + margin: 10px; + color: rgba(255,255,255,0.60); + font-size: 1.5rem; + font-weight: 400; + padding: 0; + position: absolute; + top: 20px; + left: 20px; + + cursor: pointer; + + &:hover { + color: $base-color; + } + } + + +} \ No newline at end of file diff --git a/frontend/src/components/Navigator/index.tsx b/frontend/src/components/Navigator/index.tsx new file mode 100644 index 0000000..4d2123a --- /dev/null +++ b/frontend/src/components/Navigator/index.tsx @@ -0,0 +1,30 @@ +import { ReactElement, ReactNode, useState } from "react"; + + +import "./index.scss" +import { Icon } from ".."; + +interface NavigatorProps { + children?: ReactNode +} + +export function Navigator(props: NavigatorProps){ + + const [active, setActive] = useState(false); + + return ( + <> + + + + ); +} \ No newline at end of file diff --git a/frontend/src/components/Table/index.scss b/frontend/src/components/Table/index.scss new file mode 100644 index 0000000..2ca7ec2 --- /dev/null +++ b/frontend/src/components/Table/index.scss @@ -0,0 +1,73 @@ +@use "sass:color"; +@use '@/styles/global' as *; + +details{ + user-select: none; + background-color: color.adjust($background-color, $whiteness: 8%); + margin: 0 30px; + transition: margin 0.1s; +} + +details[open]{ + border-bottom: 1px solid $background-color; + margin: 30px 0; +} +summary{ + cursor: pointer; + color: $text-color; + font-size: 1rem; + font-weight: 400; + padding: 15px; + border-bottom: 1px solid $background-color; + transition: box-shadow 0.5s; +} +details[open] > summary{ + box-shadow: 0px 1px 2px -1px black; +} +summary::marker{content: none;} + +summary{ + color: $text-color; + transition: color 0.2s; +} +summary:hover{ + color: $base-color; +} +summary > span{ + // font-size: 1.5rem; + padding: 0 5px; + vertical-align: 5px; +} + +/* blocco di tutte le tabs */ +.tabs{ + display: flex; + justify-content: flex-start; + width: 100%; + margin: 0 auto; + border-bottom: 2px solid rgba(255,255,255,0.1); +} +.tab{ + color: $text-color; + font-size: 16px; + padding: 10px 50px; + cursor: pointer; + border-bottom: 2px solid rgba(255,255,255,0); + margin: 0 0 -2px 0; + transition-property: color, border; + transition-duration: 0.3s; +} +.tab.active{ + color: $base-color; + border-bottom: 2px solid $base-color; +} +.tab-content{ + display: none; +} +.tab-content.active{ + display: block; +} + +.tab-content > a{ + display: block; +} \ No newline at end of file diff --git a/frontend/src/components/Table/index.tsx b/frontend/src/components/Table/index.tsx new file mode 100644 index 0000000..7c0f983 --- /dev/null +++ b/frontend/src/components/Table/index.tsx @@ -0,0 +1,120 @@ +import { API } from "@/utils/API"; +import { useEffect, useState } from "react"; + +import "./index.scss" +import { Icon } from ".."; + +//https://github.com/MainKronos/Sonarr-AnimeDownloader/blob/1.8.0/documentation/other/react/table.js + +interface TableProps { + api: API +} +export function Table({api}:TableProps) { + + const [entries, setEntries] = useState([] as TableRowProps[]); + + useEffect(() => { + api.getTable() + .then(res => setEntries(res)) + }, [api]) + + return ( +
+ {entries.map(({title, seasons, absolute}, index) => ( + + ))} +
+ ); +} + +interface TableRowProps { + title: string, + seasons: { + [num:string]: string[] + }, + absolute: boolean +} +function TableRow({title, seasons, absolute}: TableRowProps){ + return ( +
+ + + + {title} + {absolute && ( absolute)} + + + +
+ ); +} + +interface TableRowBodyProps { + seasons: { + [num:string]: string[] + } +} +function TableRowBody({seasons}: TableRowBodyProps){ + + const [active_tab, setActive_tab] = useState(0); + + return ( +
+
+ {Object.keys(seasons).map((season, index) => + setActive_tab(index)} + /> + )} +
+
+ {Object.keys(seasons).map((season, index) => + + )} +
+
+ ); +} + +interface TabProps { + title: string, + active: boolean, + onClick: React.MouseEventHandler +} +function Tab({title, active, onClick}: TabProps) { + return ( + + {title.toUpperCase()} + + ); +} + +interface TabContentProps { + links: string[], + active: boolean +} +function TabContent({links, active}: TabContentProps){ + return ( +
+ {links.map(link => + + {link} + + )} +
+ ); +} \ No newline at end of file diff --git a/frontend/src/components/index.tsx b/frontend/src/components/index.tsx new file mode 100644 index 0000000..d1257b3 --- /dev/null +++ b/frontend/src/components/index.tsx @@ -0,0 +1,6 @@ +export {Header} from "./Header"; +export {Navigator} from "./Navigator" +export {Table} from './Table' +export {Footer} from './Footer' +export {Container} from './Container' +export {Icon} from './Icon' \ No newline at end of file diff --git a/frontend/src/pages/home/App.tsx b/frontend/src/pages/home/App.tsx new file mode 100644 index 0000000..3b1853b --- /dev/null +++ b/frontend/src/pages/home/App.tsx @@ -0,0 +1,31 @@ +import { useEffect, useState } from "react"; +import { Container, Footer, Header, Navigator, Table } from "@/components"; + +import '@/styles/base.scss' +import { API } from "@/utils/API"; + +export default function App() { + + const api = new API(new URL(BACKEND)); + const [version, setVersion] = useState(''); + + useEffect(() => { + api.getVersion() + .then(res => setVersion(res)); + }, [api]); + + + return (<> +
+ + Home + Settings + Log + +
+ + + +
+ ) +} \ No newline at end of file diff --git a/frontend/src/pages/home/main.tsx b/frontend/src/pages/home/main.tsx new file mode 100644 index 0000000..1c99ae8 --- /dev/null +++ b/frontend/src/pages/home/main.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); +root.render( + + + +); \ No newline at end of file diff --git a/frontend/src/pages/log/App.tsx b/frontend/src/pages/log/App.tsx new file mode 100644 index 0000000..d776b3c --- /dev/null +++ b/frontend/src/pages/log/App.tsx @@ -0,0 +1,10 @@ +import { Component } from "react"; +import { Header } from "@/components"; + +export default class App extends Component { + render() { + return ( +
+ ) + } +} \ No newline at end of file diff --git a/frontend/src/pages/log/main.tsx b/frontend/src/pages/log/main.tsx new file mode 100644 index 0000000..a9a7b44 --- /dev/null +++ b/frontend/src/pages/log/main.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +import '@/styles/base.scss' + +const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); +root.render( + + + +); \ No newline at end of file diff --git a/frontend/src/pages/settings/App.tsx b/frontend/src/pages/settings/App.tsx new file mode 100644 index 0000000..f34a70e --- /dev/null +++ b/frontend/src/pages/settings/App.tsx @@ -0,0 +1,17 @@ +import { Component } from "react"; +import { Header, Navigator } from "@/components"; + +export default class App extends Component { + render() { + return ( + <> +
+ + Home + Settings + Log + + + ) + } +} \ No newline at end of file diff --git a/frontend/src/pages/settings/main.tsx b/frontend/src/pages/settings/main.tsx new file mode 100644 index 0000000..a9a7b44 --- /dev/null +++ b/frontend/src/pages/settings/main.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +import '@/styles/base.scss' + +const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); +root.render( + + + +); \ No newline at end of file diff --git a/frontend/src/styles/_global.scss b/frontend/src/styles/_global.scss new file mode 100644 index 0000000..e897b75 --- /dev/null +++ b/frontend/src/styles/_global.scss @@ -0,0 +1,3 @@ +$base-color: #9FA8DA !default; +$background-color: #121212 !default; +$text-color: #bababa !default; \ No newline at end of file diff --git a/frontend/src/styles/base.scss b/frontend/src/styles/base.scss new file mode 100644 index 0000000..5304cdd --- /dev/null +++ b/frontend/src/styles/base.scss @@ -0,0 +1,35 @@ +@use "sass:color"; +@use 'global' as *; + +@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap'); + +html { + color-scheme: dark; +} + +body { + margin: 0; + font-family: 'Roboto', sans-serif; + background-color: $background-color; + color: $text-color; +} + +#root { + min-height: 100vh; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +a{ + color: rgba(255,255,255,0.5); + text-decoration: none; + width: fit-content; + margin: 10px; +} +a:active, a:visited{ + color: rgba(255,255,255,0.5); +} +a:hover{ + color: $base-color; +} \ No newline at end of file diff --git a/frontend/src/utils/API.ts b/frontend/src/utils/API.ts new file mode 100644 index 0000000..15688ef --- /dev/null +++ b/frontend/src/utils/API.ts @@ -0,0 +1,17 @@ +export class API { + backend: string; + constructor(backend:URL) { + this.backend = backend.origin + '/api'; + } + + async getVersion():Promise{ + const res = await fetch(this.backend + '/version'); + const data = await res.json() + return data.version; + } + + async getTable():Promise{ + const res = await fetch(this.backend + '/table'); + return await res.json() + } +} \ No newline at end of file diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..a2c9433 --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1,4 @@ +/// + +/** Url del backend */ +declare const BACKEND: string; \ No newline at end of file diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..5309a3c --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + + "composite": true, + "allowSyntheticDefaultImports": true, + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src", "vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..9f3d27e --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,27 @@ +import { defineConfig } from 'vite' +import { resolve } from 'path' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig(({mode}) => {return{ + plugins: [react()], + base: './', + build: { + outDir: 'build', + rollupOptions: { + input: { + index: resolve(__dirname, 'index.html'), + log: resolve(__dirname, 'log.html'), + settings: resolve(__dirname, 'settings.html'), + } + }, + }, + resolve: { + alias: { + '@': resolve(__dirname, 'src'), + } + }, + define: { + BACKEND: JSON.stringify(mode=='development' ? 'http://localhost:5000' : './') + } +}}) diff --git a/src/components/frontend/__init__.py b/src/components/__frontend__OLD/__init__.py similarity index 100% rename from src/components/frontend/__init__.py rename to src/components/__frontend__OLD/__init__.py diff --git a/src/components/frontend/api.py b/src/components/__frontend__OLD/api.py similarity index 100% rename from src/components/frontend/api.py rename to src/components/__frontend__OLD/api.py diff --git a/src/components/frontend/app.py b/src/components/__frontend__OLD/app.py similarity index 100% rename from src/components/frontend/app.py rename to src/components/__frontend__OLD/app.py diff --git a/src/components/frontend/static/css/animation.css b/src/components/__frontend__OLD/static/css/animation.css similarity index 100% rename from src/components/frontend/static/css/animation.css rename to src/components/__frontend__OLD/static/css/animation.css diff --git a/src/components/frontend/static/css/card.css b/src/components/__frontend__OLD/static/css/card.css similarity index 100% rename from src/components/frontend/static/css/card.css rename to src/components/__frontend__OLD/static/css/card.css diff --git a/src/components/frontend/static/css/elems.css b/src/components/__frontend__OLD/static/css/elems.css similarity index 100% rename from src/components/frontend/static/css/elems.css rename to src/components/__frontend__OLD/static/css/elems.css diff --git a/src/components/frontend/static/css/index.css b/src/components/__frontend__OLD/static/css/index.css similarity index 100% rename from src/components/frontend/static/css/index.css rename to src/components/__frontend__OLD/static/css/index.css diff --git a/src/components/frontend/static/css/input.css b/src/components/__frontend__OLD/static/css/input.css similarity index 100% rename from src/components/frontend/static/css/input.css rename to src/components/__frontend__OLD/static/css/input.css diff --git a/src/components/frontend/static/css/log.css b/src/components/__frontend__OLD/static/css/log.css similarity index 100% rename from src/components/frontend/static/css/log.css rename to src/components/__frontend__OLD/static/css/log.css diff --git a/src/components/frontend/static/css/nav.css b/src/components/__frontend__OLD/static/css/nav.css similarity index 100% rename from src/components/frontend/static/css/nav.css rename to src/components/__frontend__OLD/static/css/nav.css diff --git a/src/components/frontend/static/css/settings.css b/src/components/__frontend__OLD/static/css/settings.css similarity index 100% rename from src/components/frontend/static/css/settings.css rename to src/components/__frontend__OLD/static/css/settings.css diff --git a/src/components/frontend/static/css/table.css b/src/components/__frontend__OLD/static/css/table.css similarity index 100% rename from src/components/frontend/static/css/table.css rename to src/components/__frontend__OLD/static/css/table.css diff --git a/src/components/__frontend__OLD/static/favicon.ico b/src/components/__frontend__OLD/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..2f5d4afc30e65da994860aebfed3e0e1daa75487 GIT binary patch literal 2007 zcmbVNc~H_>0REw+S>cgdc{@yHW<^b2ix)PSiD_jamOXiXtvmD<#=I#t$ue)9j!7hgp9?Pf8Yn_sOJA*5_1OUoJ7lh-)_&H6tN7+YfM3LqwY?FuBt@UaP>isq|4zj=6e9p-nhlMk|xPHqP0ObjKQ zy$9KegJIO~`?x0fg_!};s9($XY!``g!gfBzw(NW~P9Kl;PRyb12Cb7{FAe|{fYksq zohr+4)AzHb`KA!acF{q!VmbYpj$Yn%`s}F(kTHSN#}CE$E-%yV$S|Hj&tNbG!qA%c zR5*saYw|9)=|eg+EjB4XWpFV&@9NyoogS#nD7Ustl^GZ;j2$vC8M?LMTe$;>NbKGk z-2AzrZzuj)Sn+e+zEU#9bpXEnxNCr41ecHA^wt(N=^z?Iz%6M>qRX%ED5uf0L9gz>a9Bk97hHsRXMwr9 z>7q6`(4I&v%i8#Ka^s9(%BZ&W&H@YT^4xUPiF~g`p~Dx2T8tq0jY^Wj^P)r~E{MDs zrY~x}b0XnUE9ljd9lyZi4L7&F{Vt2i)Fl~7r93r~l08AZ>!qth)gH`|!tdj~ERvE$ zUH#gT6}Sv+*w^h~9b`T&eV1GoDAk! zZCJ-^PDZ1)l^H(-!x0En->sq`<;n7g@9rHUb2un_2mXQOsQt3aa3F4}{t+Y7H6Z$I zmX^kYB>ZC zswcclE7TN~vQwUnbxbxfyQqxfIW<-9F98+%bs3e=7k(nF$-D8bx%$pIT`XB8M$IQv z^un)8kw`2uNzQP~;#8F2;>opby)7md2J7JYTCZz5cD=~IvC3Kqy{`L}ngMM$hQtP1 zGgHV>u2|dh#J-Ji?8gTp2(CRXkLJ9e5{49%`Fn{R5z(J6_+0V3f0VE^jlsTsAx3t( z{RSP3w%r`3N~4YyN^ESn>0~`lc8FAUVB#Jx<1Cg8TZ2)B%cdNyQaxm{h{(tq2IG^z zbYRqo;BP*?KtpR_e?ATtoP&|ha%+RlyA$F%&MT}?acAS|>5F&MVrtBVRWqd z;DoSbX2CI6WmV;?07%u8O4`mKd6Le2A~EId1`N2adE`=1;^ZDe6jkf^QRtH#_cnGj z()9Rk?|XfG^+K&;N*!xcTvE6#UbZ`@Hkv-0yp5tY#no?OcR9OqGpa&F%mB;mriq!( zFENTy2@P9S6BbqAh{k6B15CroL#Jk%tNv-=iP8ou42@YO(SZj{6QE{$y|^Rsf%9f7 zlIgf8jM28H&W@Aw2L~3xV+0blozrB62 t01iMXhezT)7q){}(FuQtZ@^Qk$Uk*y$6In_i2Q2?T%6nyr3Za}{|okxJk APIRouter: + route = APIRouter(prefix='/table', tags=['table']) + + @route.get("/") + def get_table() -> list[dict]: + """ + Restituisce la lista di elementi della tabella. + + ```json + [ + ... + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/bakuten.MfCW1" + ] + }, + "title": "Backflip!!" + }, + ... + ] + ``` + """ + return core.table.getData() + + + return route \ No newline at end of file diff --git a/src/components/api/__init__.py b/src/components/api/__init__.py new file mode 100644 index 0000000..29770d9 --- /dev/null +++ b/src/components/api/__init__.py @@ -0,0 +1,39 @@ +from ..backend import Core + +from typing import Union, TypedDict +from fastapi import FastAPI +from fastapi.routing import APIRouter +from fastapi.middleware.cors import CORSMiddleware + +from .TableRoute import TableRoute + +def API(core:Core) -> FastAPI: + api = APIRouter(prefix='/api') + + @api.get("/version") + def get_version() -> TypedDict('',{'version':str}): + """ + Restituisce il numero di versione. + + ```json + {'version': 'dev'} + ``` + """ + return {"version": core.version} + + api.include_router(TableRoute(core)) + + app = FastAPI( + title='Sonarr-AnimeDownloader', + summary='API', + version=core.version + ) + app.include_router(api) + app.add_middleware( + CORSMiddleware, + allow_origins=['*'], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) + return app \ No newline at end of file diff --git a/src/dev_api.py b/src/dev_api.py new file mode 100644 index 0000000..1153b3f --- /dev/null +++ b/src/dev_api.py @@ -0,0 +1,30 @@ +import os +import pathlib +import sys + +from components.backend.core.Core import Core, ctx +from components.api import API + +import pathlib +import sys, json +import uvicorn + +ctx.DOWNLOAD_FOLDER = pathlib.Path('./tests/downloads').absolute() +ctx.DATABASE_FOLDER = pathlib.Path("./tests/database").absolute() +ctx.SCRIPT_FOLDER = pathlib.Path("./tests/script").absolute() +ctx.SONARR_URL = "http://netvault:8989/" +ctx.API_KEY = "f10994e9f3494368a31a3088aba6b9fc" +ctx.VERSION = "dev" + +def main(): + + # Carico il core + core = Core() + + # Cario la pagina web + app = API(core) + + return app + +if __name__ == "__main__": + uvicorn.run("dev_api:main", port=5000, log_level="info", reload=True, factory=True) \ No newline at end of file diff --git a/src/main.py b/src/main.py index fd009e6..1b6868a 100644 --- a/src/main.py +++ b/src/main.py @@ -1,13 +1,14 @@ #!/usr/bin/python3 -from components import Core, Frontend +from components import Core, API import threading +import uvicorn def main(): # Carico il core core = Core() # Cario la pagina web - app = Frontend(core) + app = API(core) # Avvio la pagina web threading.Thread(target=server, args=[app], daemon=True).start() @@ -19,7 +20,7 @@ def main(): core.join() def server(app): - app.run(debug=False, host='0.0.0.0', use_reloader=False) + uvicorn.run(app, port=5000, host='0.0.0.0', log_level='critical') if __name__ == '__main__': main() \ No newline at end of file diff --git a/src/requirements.txt b/src/requirements.txt index 87338f9..c8877c6 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1,5 +1,4 @@ animeworld>=1.6.0 -flask -flask_socketio -werkzeug==2.2.3 +fastapi==0.103.2 +uvicorn[standard] httpx \ No newline at end of file diff --git a/tests/database/table.json b/tests/database/table.json index 8cb1952..8f8ca31 100644 --- a/tests/database/table.json +++ b/tests/database/table.json @@ -1 +1,775 @@ -[{"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/86.kdabE", "https://www.animeworld.tv/play/86-eighty-six-2.cWHjp"]}, "title": "86: Eighty Six"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/kakkou-no-iinazuke.Q6ykQ"]}, "title": "A Couple of Cuckoos"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/aharen-san-wa-hakarenai.SehbY"]}, "title": "Aharen-san wa Hakarenai"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/akebi-chan-no-sailor-fuku.6t2t7"]}, "title": "Akebi's Sailor Uniform"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/arifureta-shokugyou-de-sekai-saikyou-2.ihBQk"]}, "title": "Arifureta: From Commonplace to World's Strongest"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/ascendance-of-a-bookworm.paCPb", "https://www.animeworld.tv/play/ascendance-of-a-bookworm-2.Q0Rrm", "https://www.animeworld.tv/play/ascendance-of-a-bookworm-3.l5fru"]}, "title": "Ascendance of a Bookworm"}, {"absolute": false, "seasons": {"4": ["https://www.animeworld.tv/play/lattacco-dei-giganti-4.OH0AI", "https://www.animeworld.tv/play/lattacco-dei-giganti-4-parte-2.IsVqa"]}, "title": "Attack on Titan"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/bakuten.MfCW1"]}, "title": "Backflip!!"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/shin-no-nakama-ja-nai-to-yuusha-no-party-wo-oidasareta-node-henkyou-de-slow-life-suru-koto-ni-shima.JpINB"]}, "title": "Banished from the Hero's Party, I Decided to Live a Quiet Life in the Countryside"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/deatte-5-byou-de-battle.njUd3"]}, "title": "Battle Game in 5 Seconds"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/beastars-2.Jdlq8"]}, "title": "Beastars"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/blue-period.S2fQi"]}, "title": "Blue Period"}, {"title": "Bocchi the Rock!", "seasons": {"1": ["https://www.animeworld.tv/play/bocchi-the-rock.LRnn4"]}, "absolute": false}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/jaku-chara-tomozaki-kun.RDPHq"]}, "title": "Bottom-Tier Character Tomozaki"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/180-byou-de-kimi-no-mimi-wo-shiawase-ni-dekiru-ka.N9OZX"]}, "title": "Can I Make Your Ears Happy in 180 Seconds?"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/cells-at-work-2.pE6uS"]}, "title": "Cells at Work!"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/cells-at-work-code-black.w4Eor"]}, "title": "Cells at Work! Code Black"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/sentouin-hakenshimasu.6whgN"]}, "title": "Combatants Will Be Dispatched!"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/demon-slayer-kimetsu-no-yaiba-2.YIoa-"], "3": ["https://www.animeworld.tv/play/demon-slayer-kimetsu-no-yaiba-entertainment-district-arc.IFmFs"]}, "title": "Demon Slayer: Kimetsu no Yaiba"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/cheat-kusushi-no-slow-life-isekai-ni-tsukurou-drugstore.sfvL-"]}, "title": "Drug Store in Another World: The Slow Life of a Cheat Pharmacist"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/edens-zero.A3qCl"]}, "title": "EDENS ZERO"}, {"absolute": false, "seasons": {"3": ["https://www.animeworld.tv/play/fruits-basket-the-final.frf-n"]}, "title": "Fruits Basket (2019)"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/kyuukyoku-shinka-shita-full-dive-rpg-ga-genjitsu-yori-mo-kusoge-dattara.-nptV"]}, "title": "Full Dive: This Ultimate Next-Gen Full Dive RPG Is Even Shittier Than Real Life!"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/gunslinger-girl.vIUhE"]}, "title": "Gunslinger Girl"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/haibane-renmei.4yJAg"]}, "title": "Haibane Renmei"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/higurashi-no-naku-koro-ni-2020.DoJuQ"], "2": ["https://www.animeworld.tv/play/higurashi-no-naku-koro-ni-sotsu.j7iq7"]}, "title": "Higurashi: When They Cry \u2013 GOU"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/horimiya.Mse3-"]}, "title": "Horimiya"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/genjitsu-shugi-yuusha-no-oukoku-saikenki.LpDFM", "https://www.animeworld.tv/play/genjitsu-shugi-yuusha-no-oukoku-saikenki-parte-2.5cDX2"]}, "title": "How a Realist Hero Rebuilt the Kingdom"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/yuusha-yamemasu.pUnEf"]}, "title": "I'm Quitting Heroing"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/tsuki-to-laika-to-nosferatu.oon50"]}, "title": "Irina: The Vampire Cosmonaut"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/kemono-jihen.xb1V1"]}, "title": "Kemono Jihen"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/komi-cant-communicate-2.HgYCo"]}, "title": "Komi Can't Communicate"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/fantasy-bishoujo-juniku-ojisan-to.ZhCYX"]}, "title": "Life With an Ordinary Guy Who Reincarnated Into a Total Fantasy Knockout"}, {"absolute": false, "seasons": {"3": ["https://www.animeworld.tv/play/log-horizon-3.GGizW"]}, "title": "Log Horizon"}, {"title": "Made in Abyss", "seasons": {"2": ["https://www.animeworld.tv/play/made-in-abyss-2.T7h4U"]}, "absolute": false}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/mieruko-chan.M7FrP"]}, "title": "Mieruko-chan"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/moriarty-the-patriot.m9UeA", "https://www.animeworld.tv/play/moriarty-the-patriot-2.la8cg"]}, "title": "Moriarty the Patriot"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/megami-ryou-no-ryoubo-kun.48hLw"]}, "title": "Mother of the Goddess' Dormitory"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/mushoku-tensei-isekai-ittara-honki-dasu.FwbNc", "https://www.animeworld.tv/play/mushoku-tensei-isekai-ittara-honki-dasu-2.UNNMY"]}, "title": "Mushoku Tensei: Jobless Reincarnation"}, {"title": "My Happy Marriage", "absolute": false, "seasons": {"1": ["https://www.animeworld.so/play/watashi-no-shiawase-na-kekkon.vZoGM"]}}, {"absolute": false, "seasons": {"5": ["https://www.animeworld.tv/play/boku-no-hero-academia-5.JosMp"]}, "title": "My Hero Academia"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/my-life-as-a-villainess-all-routes-lead-to-doom-2.8RcIg"]}, "title": "My Next Life as a Villainess: All Routes Lead to Doom!"}, {"absolute": true, "seasons": {"absolute": ["https://www.animeworld.tv/play/one-piece-subita.qzG-L"]}, "title": "One Piece"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/orient.myAXD"]}, "title": "Orient"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/osananajimi-ga-zettai-ni-makenai-love-comedy.J2uW-"]}, "title": "Osamake: Romcom Where the Childhood Friend Won't Lose"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/puraore-pride-of-orange.OnxR3"]}, "title": "PuraOre! ~Pride of Orange~"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/ousama-ranking.W-Hq7"]}, "title": "Ranking of Kings"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/re-main.99hiG"]}, "title": "Re-Main"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/kaifuku-jutsushi-no-yarinaoshi.ol0ZN"]}, "title": "Redo of Healer"}, {"title": "Reincarnated as a Sword", "seasons": {"1": ["https://www.animeworld.tv/play/tensei-shitara-ken-deshita.ABj8v"]}, "absolute": false}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/bokutachi-no-remake.lB6Bh"]}, "title": "Remake Our Life!"}, {"title": "Rent-a-Girlfriend", "seasons": {"2": ["https://www.animeworld.tv/play/rent-a-girlfriend-2.qRp7e"]}, "absolute": false}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/baraou-no-souretsu.LVB1N"]}, "title": "Requiem of the Rose King"}, {"title": "SPY x FAMILY", "seasons": {"1": ["https://www.animeworld.tv/play/spy-x-family.iyICA"]}, "absolute": false}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/sasaki-to-miyano.mij7d"]}, "title": "Sasaki and Miyano"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/kuzu-no-honkai.MONpm"]}, "title": "Scum's Wish"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/seirei-gensouki.NgIKG/"]}, "title": "Seirei Gensouki: Spirit Chronicles"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/shadows-house.rCg-x"]}, "title": "Shadows House"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/sonny-boy.7Utce"]}, "title": "Sonny Boy"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/summertime-render.GDU38"]}, "title": "Summer Time Rendering"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/tatoeba-last-dungeon-mae-no-mura-no-shounen-ga-joban-no-machi-de-kurasu-youna-monogatari.EqCF-"]}, "title": "Suppose a Kid From the Last Dungeon Boonies Moved to a Starter Town?"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/taishou-otome-otogibanashi.rMAeY"]}, "title": "Taisho Otome Fairy Tale"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/karakai-jouzu-no-takagi-san.sKOCK"], "2": ["https://www.animeworld.tv/play/karakai-jouzu-no-takagi-san-2.M3Myw"], "3": ["https://www.animeworld.tv/play/karakai-jouzu-no-takagi-san-3.0ETgJ"]}, "title": "Teasing Master Takagi-san"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/tensei-shitara-slime-datta-ken-2.jregU", "https://www.animeworld.tv/play/tensei-shitara-slime-datta-ken-2nd-season-part-2.0K6vE"]}, "title": "That Time I Got Reincarnated as a Slime"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/shiroi-suna-no-aquatope.wCt0J"]}, "title": "The Aquatope on White Sand"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/vanitas-no-carte.Z2zwI"]}, "title": "The Case Study of Vanitas"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/the-detective-is-already-dead.wWFQN"]}, "title": "The Detective Is Already Dead"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/the-duke-of-death-and-his-maid.YkcXB"]}, "title": "The Duke of Death and His Maid"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/meikyuu-black-company.JOXcZ"]}, "title": "The Dungeon of Black Company"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/tensai-ouji-no-akaji-kokka-saisei-jutsu.iwtzm"]}, "title": "The Genius Prince's Guide to Raising a Nation Out of Debt"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/the-heike-story.zQCZz"]}, "title": "The Heike Story"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/heion-sedai-no-idaten-tachi.1TB--"]}, "title": "The Idaten Deities Know Only Peace"}, {"title": "The Most Heretical Last Boss Queen: From Villainess to Savior", "absolute": false, "seasons": {"1": ["https://www.animeworld.so/play/the-most-heretical-last-boss-queen-from-villainess-to-savior.6KetrA"]}}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/the-promised-neverland-2.HPGKr"]}, "title": "The Promised Neverland"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/the-rising-of-the-shield-hero-2.lgwm2"]}, "title": "The Rising of the Shield Hero"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/the-saints-magic-power-is-omnipotent.iKqoM"]}, "title": "The Saint's Magic Power Is Omnipotent"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/the-worlds-finest-assassin-gets-reincarnated-in-another-world-as-an-aristocrat.WFG1J"]}, "title": "The World's Finest Assassin Gets Reincarnated in Another World as an Aristocrat"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/fumetsu-no-anata-e.tY9Ls"]}, "title": "To Your Eternity"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/tokyo-revengers.Zkczy"]}, "title": "Tokyo Revengers"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/tsuki-ga-michibiku-isekai-douchuu.qTZiB"]}, "title": "Tsukimichi -Moonlit Fantasy-"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/vivy-fluorite-eyes-song.3vh3i"]}, "title": "Vivy -Fluorite Eye's Song-"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/wonder-egg-priority.8k2by"]}, "title": "Wonder Egg Priority"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/world-trigger.L3ftn"], "2": ["https://www.animeworld.tv/play/world-trigger-2.VHfyT"], "3": ["https://www.animeworld.tv/play/world-trigger-3.1tSD0"]}, "title": "World Trigger"}] \ No newline at end of file +[ + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/86.kdabE", + "https://www.animeworld.tv/play/86-eighty-six-2.cWHjp" + ] + }, + "title": "86: Eighty Six" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/kakkou-no-iinazuke.Q6ykQ" + ] + }, + "title": "A Couple of Cuckoos" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/aharen-san-wa-hakarenai.SehbY" + ] + }, + "title": "Aharen-san wa Hakarenai" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/akebi-chan-no-sailor-fuku.6t2t7" + ] + }, + "title": "Akebi's Sailor Uniform" + }, + { + "absolute": false, + "seasons": { + "2": [ + "https://www.animeworld.tv/play/arifureta-shokugyou-de-sekai-saikyou-2.ihBQk" + ] + }, + "title": "Arifureta: From Commonplace to World's Strongest" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/ascendance-of-a-bookworm.paCPb", + "https://www.animeworld.tv/play/ascendance-of-a-bookworm-2.Q0Rrm", + "https://www.animeworld.tv/play/ascendance-of-a-bookworm-3.l5fru" + ] + }, + "title": "Ascendance of a Bookworm" + }, + { + "absolute": false, + "seasons": { + "4": [ + "https://www.animeworld.tv/play/lattacco-dei-giganti-4.OH0AI", + "https://www.animeworld.tv/play/lattacco-dei-giganti-4-parte-2.IsVqa" + ] + }, + "title": "Attack on Titan" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/bakuten.MfCW1" + ] + }, + "title": "Backflip!!" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/shin-no-nakama-ja-nai-to-yuusha-no-party-wo-oidasareta-node-henkyou-de-slow-life-suru-koto-ni-shima.JpINB" + ] + }, + "title": "Banished from the Hero's Party, I Decided to Live a Quiet Life in the Countryside" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/deatte-5-byou-de-battle.njUd3" + ] + }, + "title": "Battle Game in 5 Seconds" + }, + { + "absolute": false, + "seasons": { + "2": [ + "https://www.animeworld.tv/play/beastars-2.Jdlq8" + ] + }, + "title": "Beastars" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/blue-period.S2fQi" + ] + }, + "title": "Blue Period" + }, + { + "title": "Bocchi the Rock!", + "seasons": { + "1": [ + "https://www.animeworld.tv/play/bocchi-the-rock.LRnn4" + ] + }, + "absolute": false + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/jaku-chara-tomozaki-kun.RDPHq" + ] + }, + "title": "Bottom-Tier Character Tomozaki" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/180-byou-de-kimi-no-mimi-wo-shiawase-ni-dekiru-ka.N9OZX" + ] + }, + "title": "Can I Make Your Ears Happy in 180 Seconds?" + }, + { + "absolute": false, + "seasons": { + "2": [ + "https://www.animeworld.tv/play/cells-at-work-2.pE6uS" + ] + }, + "title": "Cells at Work!" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/cells-at-work-code-black.w4Eor" + ] + }, + "title": "Cells at Work! Code Black" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/sentouin-hakenshimasu.6whgN" + ] + }, + "title": "Combatants Will Be Dispatched!" + }, + { + "absolute": false, + "seasons": { + "2": [ + "https://www.animeworld.tv/play/demon-slayer-kimetsu-no-yaiba-2.YIoa-" + ], + "3": [ + "https://www.animeworld.tv/play/demon-slayer-kimetsu-no-yaiba-entertainment-district-arc.IFmFs" + ] + }, + "title": "Demon Slayer: Kimetsu no Yaiba" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/cheat-kusushi-no-slow-life-isekai-ni-tsukurou-drugstore.sfvL-" + ] + }, + "title": "Drug Store in Another World: The Slow Life of a Cheat Pharmacist" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/edens-zero.A3qCl" + ] + }, + "title": "EDENS ZERO" + }, + { + "absolute": false, + "seasons": { + "3": [ + "https://www.animeworld.tv/play/fruits-basket-the-final.frf-n" + ] + }, + "title": "Fruits Basket (2019)" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/kyuukyoku-shinka-shita-full-dive-rpg-ga-genjitsu-yori-mo-kusoge-dattara.-nptV" + ] + }, + "title": "Full Dive: This Ultimate Next-Gen Full Dive RPG Is Even Shittier Than Real Life!" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/gunslinger-girl.vIUhE" + ] + }, + "title": "Gunslinger Girl" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/haibane-renmei.4yJAg" + ] + }, + "title": "Haibane Renmei" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/higurashi-no-naku-koro-ni-2020.DoJuQ" + ], + "2": [ + "https://www.animeworld.tv/play/higurashi-no-naku-koro-ni-sotsu.j7iq7" + ] + }, + "title": "Higurashi: When They Cry \u2013 GOU" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/horimiya.Mse3-" + ] + }, + "title": "Horimiya" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/genjitsu-shugi-yuusha-no-oukoku-saikenki.LpDFM", + "https://www.animeworld.tv/play/genjitsu-shugi-yuusha-no-oukoku-saikenki-parte-2.5cDX2" + ] + }, + "title": "How a Realist Hero Rebuilt the Kingdom" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/yuusha-yamemasu.pUnEf" + ] + }, + "title": "I'm Quitting Heroing" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/tsuki-to-laika-to-nosferatu.oon50" + ] + }, + "title": "Irina: The Vampire Cosmonaut" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/kemono-jihen.xb1V1" + ] + }, + "title": "Kemono Jihen" + }, + { + "absolute": false, + "seasons": { + "2": [ + "https://www.animeworld.tv/play/komi-cant-communicate-2.HgYCo" + ] + }, + "title": "Komi Can't Communicate" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/fantasy-bishoujo-juniku-ojisan-to.ZhCYX" + ] + }, + "title": "Life With an Ordinary Guy Who Reincarnated Into a Total Fantasy Knockout" + }, + { + "absolute": false, + "seasons": { + "3": [ + "https://www.animeworld.tv/play/log-horizon-3.GGizW" + ] + }, + "title": "Log Horizon" + }, + { + "title": "Made in Abyss", + "seasons": { + "2": [ + "https://www.animeworld.tv/play/made-in-abyss-2.T7h4U" + ] + }, + "absolute": false + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/mieruko-chan.M7FrP" + ] + }, + "title": "Mieruko-chan" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/moriarty-the-patriot.m9UeA", + "https://www.animeworld.tv/play/moriarty-the-patriot-2.la8cg" + ] + }, + "title": "Moriarty the Patriot" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/megami-ryou-no-ryoubo-kun.48hLw" + ] + }, + "title": "Mother of the Goddess' Dormitory" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/mushoku-tensei-isekai-ittara-honki-dasu.FwbNc", + "https://www.animeworld.tv/play/mushoku-tensei-isekai-ittara-honki-dasu-2.UNNMY" + ] + }, + "title": "Mushoku Tensei: Jobless Reincarnation" + }, + { + "title": "My Happy Marriage", + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.so/play/watashi-no-shiawase-na-kekkon.vZoGM" + ] + } + }, + { + "absolute": false, + "seasons": { + "5": [ + "https://www.animeworld.tv/play/boku-no-hero-academia-5.JosMp" + ] + }, + "title": "My Hero Academia" + }, + { + "absolute": false, + "seasons": { + "2": [ + "https://www.animeworld.tv/play/my-life-as-a-villainess-all-routes-lead-to-doom-2.8RcIg" + ] + }, + "title": "My Next Life as a Villainess: All Routes Lead to Doom!" + }, + { + "absolute": true, + "seasons": { + "absolute": [ + "https://www.animeworld.tv/play/one-piece-subita.qzG-L" + ] + }, + "title": "One Piece" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/orient.myAXD" + ] + }, + "title": "Orient" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/osananajimi-ga-zettai-ni-makenai-love-comedy.J2uW-" + ] + }, + "title": "Osamake: Romcom Where the Childhood Friend Won't Lose" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/puraore-pride-of-orange.OnxR3" + ] + }, + "title": "PuraOre! ~Pride of Orange~" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/ousama-ranking.W-Hq7" + ] + }, + "title": "Ranking of Kings" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/re-main.99hiG" + ] + }, + "title": "Re-Main" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/kaifuku-jutsushi-no-yarinaoshi.ol0ZN" + ] + }, + "title": "Redo of Healer" + }, + { + "title": "Reincarnated as a Sword", + "seasons": { + "1": [ + "https://www.animeworld.tv/play/tensei-shitara-ken-deshita.ABj8v" + ] + }, + "absolute": false + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/bokutachi-no-remake.lB6Bh" + ] + }, + "title": "Remake Our Life!" + }, + { + "title": "Rent-a-Girlfriend", + "seasons": { + "2": [ + "https://www.animeworld.tv/play/rent-a-girlfriend-2.qRp7e" + ] + }, + "absolute": false + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/baraou-no-souretsu.LVB1N" + ] + }, + "title": "Requiem of the Rose King" + }, + { + "title": "SPY x FAMILY", + "seasons": { + "1": [ + "https://www.animeworld.tv/play/spy-x-family.iyICA" + ] + }, + "absolute": false + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/sasaki-to-miyano.mij7d" + ] + }, + "title": "Sasaki and Miyano" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/kuzu-no-honkai.MONpm" + ] + }, + "title": "Scum's Wish" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/seirei-gensouki.NgIKG/" + ] + }, + "title": "Seirei Gensouki: Spirit Chronicles" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/shadows-house.rCg-x" + ] + }, + "title": "Shadows House" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/sonny-boy.7Utce" + ] + }, + "title": "Sonny Boy" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/summertime-render.GDU38" + ] + }, + "title": "Summer Time Rendering" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/tatoeba-last-dungeon-mae-no-mura-no-shounen-ga-joban-no-machi-de-kurasu-youna-monogatari.EqCF-" + ] + }, + "title": "Suppose a Kid From the Last Dungeon Boonies Moved to a Starter Town?" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/taishou-otome-otogibanashi.rMAeY" + ] + }, + "title": "Taisho Otome Fairy Tale" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/karakai-jouzu-no-takagi-san.sKOCK" + ], + "2": [ + "https://www.animeworld.tv/play/karakai-jouzu-no-takagi-san-2.M3Myw" + ], + "3": [ + "https://www.animeworld.tv/play/karakai-jouzu-no-takagi-san-3.0ETgJ" + ] + }, + "title": "Teasing Master Takagi-san" + }, + { + "absolute": false, + "seasons": { + "2": [ + "https://www.animeworld.tv/play/tensei-shitara-slime-datta-ken-2.jregU", + "https://www.animeworld.tv/play/tensei-shitara-slime-datta-ken-2nd-season-part-2.0K6vE" + ] + }, + "title": "That Time I Got Reincarnated as a Slime" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/shiroi-suna-no-aquatope.wCt0J" + ] + }, + "title": "The Aquatope on White Sand" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/vanitas-no-carte.Z2zwI" + ] + }, + "title": "The Case Study of Vanitas" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/the-detective-is-already-dead.wWFQN" + ] + }, + "title": "The Detective Is Already Dead" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/the-duke-of-death-and-his-maid.YkcXB" + ] + }, + "title": "The Duke of Death and His Maid" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/meikyuu-black-company.JOXcZ" + ] + }, + "title": "The Dungeon of Black Company" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/tensai-ouji-no-akaji-kokka-saisei-jutsu.iwtzm" + ] + }, + "title": "The Genius Prince's Guide to Raising a Nation Out of Debt" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/the-heike-story.zQCZz" + ] + }, + "title": "The Heike Story" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/heion-sedai-no-idaten-tachi.1TB--" + ] + }, + "title": "The Idaten Deities Know Only Peace" + }, + { + "title": "The Most Heretical Last Boss Queen: From Villainess to Savior", + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.so/play/the-most-heretical-last-boss-queen-from-villainess-to-savior.6KetrA" + ] + } + }, + { + "absolute": false, + "seasons": { + "2": [ + "https://www.animeworld.tv/play/the-promised-neverland-2.HPGKr" + ] + }, + "title": "The Promised Neverland" + }, + { + "absolute": false, + "seasons": { + "2": [ + "https://www.animeworld.tv/play/the-rising-of-the-shield-hero-2.lgwm2" + ] + }, + "title": "The Rising of the Shield Hero" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/the-saints-magic-power-is-omnipotent.iKqoM" + ] + }, + "title": "The Saint's Magic Power Is Omnipotent" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/the-worlds-finest-assassin-gets-reincarnated-in-another-world-as-an-aristocrat.WFG1J" + ] + }, + "title": "The World's Finest Assassin Gets Reincarnated in Another World as an Aristocrat" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/fumetsu-no-anata-e.tY9Ls" + ] + }, + "title": "To Your Eternity" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/tokyo-revengers.Zkczy" + ] + }, + "title": "Tokyo Revengers" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/tsuki-ga-michibiku-isekai-douchuu.qTZiB" + ] + }, + "title": "Tsukimichi -Moonlit Fantasy-" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/vivy-fluorite-eyes-song.3vh3i" + ] + }, + "title": "Vivy -Fluorite Eye's Song-" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/wonder-egg-priority.8k2by" + ] + }, + "title": "Wonder Egg Priority" + }, + { + "absolute": false, + "seasons": { + "1": [ + "https://www.animeworld.tv/play/world-trigger.L3ftn" + ], + "2": [ + "https://www.animeworld.tv/play/world-trigger-2.VHfyT" + ], + "3": [ + "https://www.animeworld.tv/play/world-trigger-3.1tSD0" + ] + }, + "title": "World Trigger" + } +] \ No newline at end of file diff --git a/tests/test_components.py b/tests/test_components.py index b3b3d2a..1e18057 100644 --- a/tests/test_components.py +++ b/tests/test_components.py @@ -4,10 +4,11 @@ from src.components.backend.connection import ExternalDB from src.components.backend.core import Constant as ctx from src.components.backend.connection.Sonarr import Sonarr -from src.components.frontend import Frontend +from src.components.api import API import pathlib import sys, json +import uvicorn ctx.DOWNLOAD_FOLDER = pathlib.Path('./tests/downloads').absolute() ctx.DATABASE_FOLDER = pathlib.Path("./tests/database").absolute() @@ -67,8 +68,8 @@ def testCore(self): self.core.join() def testFrontend(self): - app = Frontend(self.core) - app.run(debug=False, host='0.0.0.0', use_reloader=False) + app = API(self.core) + uvicorn.run(app, port=5000, host='0.0.0.0') def testExternalDB(self): ex = ExternalDB() From 68af68048972f66e9a0c178431124fb07bfad47b Mon Sep 17 00:00:00 2001 From: Lorenzo Chesi Date: Thu, 12 Oct 2023 19:44:40 +0200 Subject: [PATCH 05/39] #122 --- src/components/backend/core/Processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/backend/core/Processor.py b/src/components/backend/core/Processor.py index 1a181fe..2e3477e 100644 --- a/src/components/backend/core/Processor.py +++ b/src/components/backend/core/Processor.py @@ -43,7 +43,7 @@ def getAllMissing(self) -> list: missing = [] - for page in range(1,50): + for page in range(1,100): res = self.sonarr.wantedMissing(page=page) res.raise_for_status() data = res.json() From 960e0bb0070585874ab5429ebb2f78e759881ae4 Mon Sep 17 00:00:00 2001 From: Lorenzo Chesi Date: Mon, 23 Oct 2023 09:47:24 +0200 Subject: [PATCH 06/39] dev --- .../__init__.py | 0 .../{__frontend__OLD => frontend_OLD}/api.py | 0 .../{__frontend__OLD => frontend_OLD}/app.py | 0 .../static/css/animation.css | 0 .../static/css/card.css | 0 .../static/css/elems.css | 0 .../static/css/index.css | 0 .../static/css/input.css | 0 .../static/css/log.css | 0 .../static/css/nav.css | 0 .../static/css/settings.css | 0 .../static/css/table.css | 0 .../static/favicon.ico | Bin .../static/images/card-background.png | Bin .../static/js/base/nav.js | 0 .../static/js/components/components.js | 0 .../static/js/index/elems.js | 0 .../static/js/index/table.js | 0 .../static/js/log/ansi.js | 0 .../static/js/log/info.js | 0 .../static/js/log/log.js | 0 .../static/js/settings/settings.js | 0 .../templates/base.html | 0 .../templates/index.html | 0 .../templates/log.html | 0 .../templates/settings.html | 0 src/main.py | 9 +- src/requirements.txt | 6 +- tests/database/table.json | 776 +----------------- tests/test_components.py | 12 + 30 files changed, 25 insertions(+), 778 deletions(-) rename src/components/{__frontend__OLD => frontend_OLD}/__init__.py (100%) rename src/components/{__frontend__OLD => frontend_OLD}/api.py (100%) rename src/components/{__frontend__OLD => frontend_OLD}/app.py (100%) rename src/components/{__frontend__OLD => frontend_OLD}/static/css/animation.css (100%) rename src/components/{__frontend__OLD => frontend_OLD}/static/css/card.css (100%) rename src/components/{__frontend__OLD => frontend_OLD}/static/css/elems.css (100%) rename src/components/{__frontend__OLD => frontend_OLD}/static/css/index.css (100%) rename src/components/{__frontend__OLD => frontend_OLD}/static/css/input.css (100%) rename src/components/{__frontend__OLD => frontend_OLD}/static/css/log.css (100%) rename src/components/{__frontend__OLD => frontend_OLD}/static/css/nav.css (100%) rename src/components/{__frontend__OLD => frontend_OLD}/static/css/settings.css (100%) rename src/components/{__frontend__OLD => frontend_OLD}/static/css/table.css (100%) rename src/components/{__frontend__OLD => frontend_OLD}/static/favicon.ico (100%) rename src/components/{__frontend__OLD => frontend_OLD}/static/images/card-background.png (100%) rename src/components/{__frontend__OLD => frontend_OLD}/static/js/base/nav.js (100%) rename src/components/{__frontend__OLD => frontend_OLD}/static/js/components/components.js (100%) rename src/components/{__frontend__OLD => frontend_OLD}/static/js/index/elems.js (100%) rename src/components/{__frontend__OLD => frontend_OLD}/static/js/index/table.js (100%) rename src/components/{__frontend__OLD => frontend_OLD}/static/js/log/ansi.js (100%) rename src/components/{__frontend__OLD => frontend_OLD}/static/js/log/info.js (100%) rename src/components/{__frontend__OLD => frontend_OLD}/static/js/log/log.js (100%) rename src/components/{__frontend__OLD => frontend_OLD}/static/js/settings/settings.js (100%) rename src/components/{__frontend__OLD => frontend_OLD}/templates/base.html (100%) rename src/components/{__frontend__OLD => frontend_OLD}/templates/index.html (100%) rename src/components/{__frontend__OLD => frontend_OLD}/templates/log.html (100%) rename src/components/{__frontend__OLD => frontend_OLD}/templates/settings.html (100%) diff --git a/src/components/__frontend__OLD/__init__.py b/src/components/frontend_OLD/__init__.py similarity index 100% rename from src/components/__frontend__OLD/__init__.py rename to src/components/frontend_OLD/__init__.py diff --git a/src/components/__frontend__OLD/api.py b/src/components/frontend_OLD/api.py similarity index 100% rename from src/components/__frontend__OLD/api.py rename to src/components/frontend_OLD/api.py diff --git a/src/components/__frontend__OLD/app.py b/src/components/frontend_OLD/app.py similarity index 100% rename from src/components/__frontend__OLD/app.py rename to src/components/frontend_OLD/app.py diff --git a/src/components/__frontend__OLD/static/css/animation.css b/src/components/frontend_OLD/static/css/animation.css similarity index 100% rename from src/components/__frontend__OLD/static/css/animation.css rename to src/components/frontend_OLD/static/css/animation.css diff --git a/src/components/__frontend__OLD/static/css/card.css b/src/components/frontend_OLD/static/css/card.css similarity index 100% rename from src/components/__frontend__OLD/static/css/card.css rename to src/components/frontend_OLD/static/css/card.css diff --git a/src/components/__frontend__OLD/static/css/elems.css b/src/components/frontend_OLD/static/css/elems.css similarity index 100% rename from src/components/__frontend__OLD/static/css/elems.css rename to src/components/frontend_OLD/static/css/elems.css diff --git a/src/components/__frontend__OLD/static/css/index.css b/src/components/frontend_OLD/static/css/index.css similarity index 100% rename from src/components/__frontend__OLD/static/css/index.css rename to src/components/frontend_OLD/static/css/index.css diff --git a/src/components/__frontend__OLD/static/css/input.css b/src/components/frontend_OLD/static/css/input.css similarity index 100% rename from src/components/__frontend__OLD/static/css/input.css rename to src/components/frontend_OLD/static/css/input.css diff --git a/src/components/__frontend__OLD/static/css/log.css b/src/components/frontend_OLD/static/css/log.css similarity index 100% rename from src/components/__frontend__OLD/static/css/log.css rename to src/components/frontend_OLD/static/css/log.css diff --git a/src/components/__frontend__OLD/static/css/nav.css b/src/components/frontend_OLD/static/css/nav.css similarity index 100% rename from src/components/__frontend__OLD/static/css/nav.css rename to src/components/frontend_OLD/static/css/nav.css diff --git a/src/components/__frontend__OLD/static/css/settings.css b/src/components/frontend_OLD/static/css/settings.css similarity index 100% rename from src/components/__frontend__OLD/static/css/settings.css rename to src/components/frontend_OLD/static/css/settings.css diff --git a/src/components/__frontend__OLD/static/css/table.css b/src/components/frontend_OLD/static/css/table.css similarity index 100% rename from src/components/__frontend__OLD/static/css/table.css rename to src/components/frontend_OLD/static/css/table.css diff --git a/src/components/__frontend__OLD/static/favicon.ico b/src/components/frontend_OLD/static/favicon.ico similarity index 100% rename from src/components/__frontend__OLD/static/favicon.ico rename to src/components/frontend_OLD/static/favicon.ico diff --git a/src/components/__frontend__OLD/static/images/card-background.png b/src/components/frontend_OLD/static/images/card-background.png similarity index 100% rename from src/components/__frontend__OLD/static/images/card-background.png rename to src/components/frontend_OLD/static/images/card-background.png diff --git a/src/components/__frontend__OLD/static/js/base/nav.js b/src/components/frontend_OLD/static/js/base/nav.js similarity index 100% rename from src/components/__frontend__OLD/static/js/base/nav.js rename to src/components/frontend_OLD/static/js/base/nav.js diff --git a/src/components/__frontend__OLD/static/js/components/components.js b/src/components/frontend_OLD/static/js/components/components.js similarity index 100% rename from src/components/__frontend__OLD/static/js/components/components.js rename to src/components/frontend_OLD/static/js/components/components.js diff --git a/src/components/__frontend__OLD/static/js/index/elems.js b/src/components/frontend_OLD/static/js/index/elems.js similarity index 100% rename from src/components/__frontend__OLD/static/js/index/elems.js rename to src/components/frontend_OLD/static/js/index/elems.js diff --git a/src/components/__frontend__OLD/static/js/index/table.js b/src/components/frontend_OLD/static/js/index/table.js similarity index 100% rename from src/components/__frontend__OLD/static/js/index/table.js rename to src/components/frontend_OLD/static/js/index/table.js diff --git a/src/components/__frontend__OLD/static/js/log/ansi.js b/src/components/frontend_OLD/static/js/log/ansi.js similarity index 100% rename from src/components/__frontend__OLD/static/js/log/ansi.js rename to src/components/frontend_OLD/static/js/log/ansi.js diff --git a/src/components/__frontend__OLD/static/js/log/info.js b/src/components/frontend_OLD/static/js/log/info.js similarity index 100% rename from src/components/__frontend__OLD/static/js/log/info.js rename to src/components/frontend_OLD/static/js/log/info.js diff --git a/src/components/__frontend__OLD/static/js/log/log.js b/src/components/frontend_OLD/static/js/log/log.js similarity index 100% rename from src/components/__frontend__OLD/static/js/log/log.js rename to src/components/frontend_OLD/static/js/log/log.js diff --git a/src/components/__frontend__OLD/static/js/settings/settings.js b/src/components/frontend_OLD/static/js/settings/settings.js similarity index 100% rename from src/components/__frontend__OLD/static/js/settings/settings.js rename to src/components/frontend_OLD/static/js/settings/settings.js diff --git a/src/components/__frontend__OLD/templates/base.html b/src/components/frontend_OLD/templates/base.html similarity index 100% rename from src/components/__frontend__OLD/templates/base.html rename to src/components/frontend_OLD/templates/base.html diff --git a/src/components/__frontend__OLD/templates/index.html b/src/components/frontend_OLD/templates/index.html similarity index 100% rename from src/components/__frontend__OLD/templates/index.html rename to src/components/frontend_OLD/templates/index.html diff --git a/src/components/__frontend__OLD/templates/log.html b/src/components/frontend_OLD/templates/log.html similarity index 100% rename from src/components/__frontend__OLD/templates/log.html rename to src/components/frontend_OLD/templates/log.html diff --git a/src/components/__frontend__OLD/templates/settings.html b/src/components/frontend_OLD/templates/settings.html similarity index 100% rename from src/components/__frontend__OLD/templates/settings.html rename to src/components/frontend_OLD/templates/settings.html diff --git a/src/main.py b/src/main.py index 1b6868a..a4f2c45 100644 --- a/src/main.py +++ b/src/main.py @@ -1,5 +1,8 @@ #!/usr/bin/python3 from components import Core, API + +from components.frontend_OLD import Frontend + import threading import uvicorn @@ -8,7 +11,8 @@ def main(): core = Core() # Cario la pagina web - app = API(core) + # app = API(core) + app = Frontend(core) # DEPRECATO (DA RIMUOVERE) # Avvio la pagina web threading.Thread(target=server, args=[app], daemon=True).start() @@ -20,7 +24,8 @@ def main(): core.join() def server(app): - uvicorn.run(app, port=5000, host='0.0.0.0', log_level='critical') + # uvicorn.run(app, port=5000, host='0.0.0.0', log_level='critical') + app.run(debug=False, host='0.0.0.0', use_reloader=False) # DEPRECATO (DA RIMUOVERE) if __name__ == '__main__': main() \ No newline at end of file diff --git a/src/requirements.txt b/src/requirements.txt index c8877c6..288c8d5 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1,4 +1,8 @@ animeworld>=1.6.0 fastapi==0.103.2 uvicorn[standard] -httpx \ No newline at end of file +httpx + +flask +flask_socketio +werkzeug==2.2.3 \ No newline at end of file diff --git a/tests/database/table.json b/tests/database/table.json index 8f8ca31..338e004 100644 --- a/tests/database/table.json +++ b/tests/database/table.json @@ -1,775 +1 @@ -[ - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/86.kdabE", - "https://www.animeworld.tv/play/86-eighty-six-2.cWHjp" - ] - }, - "title": "86: Eighty Six" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/kakkou-no-iinazuke.Q6ykQ" - ] - }, - "title": "A Couple of Cuckoos" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/aharen-san-wa-hakarenai.SehbY" - ] - }, - "title": "Aharen-san wa Hakarenai" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/akebi-chan-no-sailor-fuku.6t2t7" - ] - }, - "title": "Akebi's Sailor Uniform" - }, - { - "absolute": false, - "seasons": { - "2": [ - "https://www.animeworld.tv/play/arifureta-shokugyou-de-sekai-saikyou-2.ihBQk" - ] - }, - "title": "Arifureta: From Commonplace to World's Strongest" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/ascendance-of-a-bookworm.paCPb", - "https://www.animeworld.tv/play/ascendance-of-a-bookworm-2.Q0Rrm", - "https://www.animeworld.tv/play/ascendance-of-a-bookworm-3.l5fru" - ] - }, - "title": "Ascendance of a Bookworm" - }, - { - "absolute": false, - "seasons": { - "4": [ - "https://www.animeworld.tv/play/lattacco-dei-giganti-4.OH0AI", - "https://www.animeworld.tv/play/lattacco-dei-giganti-4-parte-2.IsVqa" - ] - }, - "title": "Attack on Titan" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/bakuten.MfCW1" - ] - }, - "title": "Backflip!!" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/shin-no-nakama-ja-nai-to-yuusha-no-party-wo-oidasareta-node-henkyou-de-slow-life-suru-koto-ni-shima.JpINB" - ] - }, - "title": "Banished from the Hero's Party, I Decided to Live a Quiet Life in the Countryside" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/deatte-5-byou-de-battle.njUd3" - ] - }, - "title": "Battle Game in 5 Seconds" - }, - { - "absolute": false, - "seasons": { - "2": [ - "https://www.animeworld.tv/play/beastars-2.Jdlq8" - ] - }, - "title": "Beastars" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/blue-period.S2fQi" - ] - }, - "title": "Blue Period" - }, - { - "title": "Bocchi the Rock!", - "seasons": { - "1": [ - "https://www.animeworld.tv/play/bocchi-the-rock.LRnn4" - ] - }, - "absolute": false - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/jaku-chara-tomozaki-kun.RDPHq" - ] - }, - "title": "Bottom-Tier Character Tomozaki" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/180-byou-de-kimi-no-mimi-wo-shiawase-ni-dekiru-ka.N9OZX" - ] - }, - "title": "Can I Make Your Ears Happy in 180 Seconds?" - }, - { - "absolute": false, - "seasons": { - "2": [ - "https://www.animeworld.tv/play/cells-at-work-2.pE6uS" - ] - }, - "title": "Cells at Work!" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/cells-at-work-code-black.w4Eor" - ] - }, - "title": "Cells at Work! Code Black" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/sentouin-hakenshimasu.6whgN" - ] - }, - "title": "Combatants Will Be Dispatched!" - }, - { - "absolute": false, - "seasons": { - "2": [ - "https://www.animeworld.tv/play/demon-slayer-kimetsu-no-yaiba-2.YIoa-" - ], - "3": [ - "https://www.animeworld.tv/play/demon-slayer-kimetsu-no-yaiba-entertainment-district-arc.IFmFs" - ] - }, - "title": "Demon Slayer: Kimetsu no Yaiba" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/cheat-kusushi-no-slow-life-isekai-ni-tsukurou-drugstore.sfvL-" - ] - }, - "title": "Drug Store in Another World: The Slow Life of a Cheat Pharmacist" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/edens-zero.A3qCl" - ] - }, - "title": "EDENS ZERO" - }, - { - "absolute": false, - "seasons": { - "3": [ - "https://www.animeworld.tv/play/fruits-basket-the-final.frf-n" - ] - }, - "title": "Fruits Basket (2019)" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/kyuukyoku-shinka-shita-full-dive-rpg-ga-genjitsu-yori-mo-kusoge-dattara.-nptV" - ] - }, - "title": "Full Dive: This Ultimate Next-Gen Full Dive RPG Is Even Shittier Than Real Life!" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/gunslinger-girl.vIUhE" - ] - }, - "title": "Gunslinger Girl" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/haibane-renmei.4yJAg" - ] - }, - "title": "Haibane Renmei" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/higurashi-no-naku-koro-ni-2020.DoJuQ" - ], - "2": [ - "https://www.animeworld.tv/play/higurashi-no-naku-koro-ni-sotsu.j7iq7" - ] - }, - "title": "Higurashi: When They Cry \u2013 GOU" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/horimiya.Mse3-" - ] - }, - "title": "Horimiya" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/genjitsu-shugi-yuusha-no-oukoku-saikenki.LpDFM", - "https://www.animeworld.tv/play/genjitsu-shugi-yuusha-no-oukoku-saikenki-parte-2.5cDX2" - ] - }, - "title": "How a Realist Hero Rebuilt the Kingdom" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/yuusha-yamemasu.pUnEf" - ] - }, - "title": "I'm Quitting Heroing" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/tsuki-to-laika-to-nosferatu.oon50" - ] - }, - "title": "Irina: The Vampire Cosmonaut" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/kemono-jihen.xb1V1" - ] - }, - "title": "Kemono Jihen" - }, - { - "absolute": false, - "seasons": { - "2": [ - "https://www.animeworld.tv/play/komi-cant-communicate-2.HgYCo" - ] - }, - "title": "Komi Can't Communicate" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/fantasy-bishoujo-juniku-ojisan-to.ZhCYX" - ] - }, - "title": "Life With an Ordinary Guy Who Reincarnated Into a Total Fantasy Knockout" - }, - { - "absolute": false, - "seasons": { - "3": [ - "https://www.animeworld.tv/play/log-horizon-3.GGizW" - ] - }, - "title": "Log Horizon" - }, - { - "title": "Made in Abyss", - "seasons": { - "2": [ - "https://www.animeworld.tv/play/made-in-abyss-2.T7h4U" - ] - }, - "absolute": false - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/mieruko-chan.M7FrP" - ] - }, - "title": "Mieruko-chan" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/moriarty-the-patriot.m9UeA", - "https://www.animeworld.tv/play/moriarty-the-patriot-2.la8cg" - ] - }, - "title": "Moriarty the Patriot" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/megami-ryou-no-ryoubo-kun.48hLw" - ] - }, - "title": "Mother of the Goddess' Dormitory" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/mushoku-tensei-isekai-ittara-honki-dasu.FwbNc", - "https://www.animeworld.tv/play/mushoku-tensei-isekai-ittara-honki-dasu-2.UNNMY" - ] - }, - "title": "Mushoku Tensei: Jobless Reincarnation" - }, - { - "title": "My Happy Marriage", - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.so/play/watashi-no-shiawase-na-kekkon.vZoGM" - ] - } - }, - { - "absolute": false, - "seasons": { - "5": [ - "https://www.animeworld.tv/play/boku-no-hero-academia-5.JosMp" - ] - }, - "title": "My Hero Academia" - }, - { - "absolute": false, - "seasons": { - "2": [ - "https://www.animeworld.tv/play/my-life-as-a-villainess-all-routes-lead-to-doom-2.8RcIg" - ] - }, - "title": "My Next Life as a Villainess: All Routes Lead to Doom!" - }, - { - "absolute": true, - "seasons": { - "absolute": [ - "https://www.animeworld.tv/play/one-piece-subita.qzG-L" - ] - }, - "title": "One Piece" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/orient.myAXD" - ] - }, - "title": "Orient" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/osananajimi-ga-zettai-ni-makenai-love-comedy.J2uW-" - ] - }, - "title": "Osamake: Romcom Where the Childhood Friend Won't Lose" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/puraore-pride-of-orange.OnxR3" - ] - }, - "title": "PuraOre! ~Pride of Orange~" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/ousama-ranking.W-Hq7" - ] - }, - "title": "Ranking of Kings" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/re-main.99hiG" - ] - }, - "title": "Re-Main" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/kaifuku-jutsushi-no-yarinaoshi.ol0ZN" - ] - }, - "title": "Redo of Healer" - }, - { - "title": "Reincarnated as a Sword", - "seasons": { - "1": [ - "https://www.animeworld.tv/play/tensei-shitara-ken-deshita.ABj8v" - ] - }, - "absolute": false - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/bokutachi-no-remake.lB6Bh" - ] - }, - "title": "Remake Our Life!" - }, - { - "title": "Rent-a-Girlfriend", - "seasons": { - "2": [ - "https://www.animeworld.tv/play/rent-a-girlfriend-2.qRp7e" - ] - }, - "absolute": false - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/baraou-no-souretsu.LVB1N" - ] - }, - "title": "Requiem of the Rose King" - }, - { - "title": "SPY x FAMILY", - "seasons": { - "1": [ - "https://www.animeworld.tv/play/spy-x-family.iyICA" - ] - }, - "absolute": false - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/sasaki-to-miyano.mij7d" - ] - }, - "title": "Sasaki and Miyano" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/kuzu-no-honkai.MONpm" - ] - }, - "title": "Scum's Wish" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/seirei-gensouki.NgIKG/" - ] - }, - "title": "Seirei Gensouki: Spirit Chronicles" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/shadows-house.rCg-x" - ] - }, - "title": "Shadows House" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/sonny-boy.7Utce" - ] - }, - "title": "Sonny Boy" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/summertime-render.GDU38" - ] - }, - "title": "Summer Time Rendering" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/tatoeba-last-dungeon-mae-no-mura-no-shounen-ga-joban-no-machi-de-kurasu-youna-monogatari.EqCF-" - ] - }, - "title": "Suppose a Kid From the Last Dungeon Boonies Moved to a Starter Town?" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/taishou-otome-otogibanashi.rMAeY" - ] - }, - "title": "Taisho Otome Fairy Tale" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/karakai-jouzu-no-takagi-san.sKOCK" - ], - "2": [ - "https://www.animeworld.tv/play/karakai-jouzu-no-takagi-san-2.M3Myw" - ], - "3": [ - "https://www.animeworld.tv/play/karakai-jouzu-no-takagi-san-3.0ETgJ" - ] - }, - "title": "Teasing Master Takagi-san" - }, - { - "absolute": false, - "seasons": { - "2": [ - "https://www.animeworld.tv/play/tensei-shitara-slime-datta-ken-2.jregU", - "https://www.animeworld.tv/play/tensei-shitara-slime-datta-ken-2nd-season-part-2.0K6vE" - ] - }, - "title": "That Time I Got Reincarnated as a Slime" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/shiroi-suna-no-aquatope.wCt0J" - ] - }, - "title": "The Aquatope on White Sand" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/vanitas-no-carte.Z2zwI" - ] - }, - "title": "The Case Study of Vanitas" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/the-detective-is-already-dead.wWFQN" - ] - }, - "title": "The Detective Is Already Dead" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/the-duke-of-death-and-his-maid.YkcXB" - ] - }, - "title": "The Duke of Death and His Maid" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/meikyuu-black-company.JOXcZ" - ] - }, - "title": "The Dungeon of Black Company" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/tensai-ouji-no-akaji-kokka-saisei-jutsu.iwtzm" - ] - }, - "title": "The Genius Prince's Guide to Raising a Nation Out of Debt" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/the-heike-story.zQCZz" - ] - }, - "title": "The Heike Story" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/heion-sedai-no-idaten-tachi.1TB--" - ] - }, - "title": "The Idaten Deities Know Only Peace" - }, - { - "title": "The Most Heretical Last Boss Queen: From Villainess to Savior", - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.so/play/the-most-heretical-last-boss-queen-from-villainess-to-savior.6KetrA" - ] - } - }, - { - "absolute": false, - "seasons": { - "2": [ - "https://www.animeworld.tv/play/the-promised-neverland-2.HPGKr" - ] - }, - "title": "The Promised Neverland" - }, - { - "absolute": false, - "seasons": { - "2": [ - "https://www.animeworld.tv/play/the-rising-of-the-shield-hero-2.lgwm2" - ] - }, - "title": "The Rising of the Shield Hero" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/the-saints-magic-power-is-omnipotent.iKqoM" - ] - }, - "title": "The Saint's Magic Power Is Omnipotent" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/the-worlds-finest-assassin-gets-reincarnated-in-another-world-as-an-aristocrat.WFG1J" - ] - }, - "title": "The World's Finest Assassin Gets Reincarnated in Another World as an Aristocrat" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/fumetsu-no-anata-e.tY9Ls" - ] - }, - "title": "To Your Eternity" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/tokyo-revengers.Zkczy" - ] - }, - "title": "Tokyo Revengers" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/tsuki-ga-michibiku-isekai-douchuu.qTZiB" - ] - }, - "title": "Tsukimichi -Moonlit Fantasy-" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/vivy-fluorite-eyes-song.3vh3i" - ] - }, - "title": "Vivy -Fluorite Eye's Song-" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/wonder-egg-priority.8k2by" - ] - }, - "title": "Wonder Egg Priority" - }, - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/world-trigger.L3ftn" - ], - "2": [ - "https://www.animeworld.tv/play/world-trigger-2.VHfyT" - ], - "3": [ - "https://www.animeworld.tv/play/world-trigger-3.1tSD0" - ] - }, - "title": "World Trigger" - } -] \ No newline at end of file +[{"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/86.kdabE", "https://www.animeworld.tv/play/86-eighty-six-2.cWHjp"]}, "title": "86: Eighty Six"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/kakkou-no-iinazuke.Q6ykQ"]}, "title": "A Couple of Cuckoos"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/aharen-san-wa-hakarenai.SehbY"]}, "title": "Aharen-san wa Hakarenai"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/akebi-chan-no-sailor-fuku.6t2t7"]}, "title": "Akebi's Sailor Uniform"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/arifureta-shokugyou-de-sekai-saikyou-2.ihBQk"]}, "title": "Arifureta: From Commonplace to World's Strongest"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/ascendance-of-a-bookworm.paCPb", "https://www.animeworld.tv/play/ascendance-of-a-bookworm-2.Q0Rrm", "https://www.animeworld.tv/play/ascendance-of-a-bookworm-3.l5fru"]}, "title": "Ascendance of a Bookworm"}, {"absolute": false, "seasons": {"4": ["https://www.animeworld.tv/play/lattacco-dei-giganti-4.OH0AI", "https://www.animeworld.tv/play/lattacco-dei-giganti-4-parte-2.IsVqa"]}, "title": "Attack on Titan"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/bakuten.MfCW1"]}, "title": "Backflip!!"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/shin-no-nakama-ja-nai-to-yuusha-no-party-wo-oidasareta-node-henkyou-de-slow-life-suru-koto-ni-shima.JpINB"]}, "title": "Banished from the Hero's Party, I Decided to Live a Quiet Life in the Countryside"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/deatte-5-byou-de-battle.njUd3"]}, "title": "Battle Game in 5 Seconds"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/beastars-2.Jdlq8"]}, "title": "Beastars"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/blue-period.S2fQi"]}, "title": "Blue Period"}, {"title": "Bocchi the Rock!", "seasons": {"1": ["https://www.animeworld.tv/play/bocchi-the-rock.LRnn4"]}, "absolute": false}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/jaku-chara-tomozaki-kun.RDPHq"]}, "title": "Bottom-Tier Character Tomozaki"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/180-byou-de-kimi-no-mimi-wo-shiawase-ni-dekiru-ka.N9OZX"]}, "title": "Can I Make Your Ears Happy in 180 Seconds?"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/cells-at-work-2.pE6uS"]}, "title": "Cells at Work!"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/cells-at-work-code-black.w4Eor"]}, "title": "Cells at Work! Code Black"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/sentouin-hakenshimasu.6whgN"]}, "title": "Combatants Will Be Dispatched!"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/demon-slayer-kimetsu-no-yaiba-2.YIoa-"], "3": ["https://www.animeworld.tv/play/demon-slayer-kimetsu-no-yaiba-entertainment-district-arc.IFmFs"]}, "title": "Demon Slayer: Kimetsu no Yaiba"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/cheat-kusushi-no-slow-life-isekai-ni-tsukurou-drugstore.sfvL-"]}, "title": "Drug Store in Another World: The Slow Life of a Cheat Pharmacist"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/edens-zero.A3qCl"]}, "title": "EDENS ZERO"}, {"absolute": false, "seasons": {"3": ["https://www.animeworld.tv/play/fruits-basket-the-final.frf-n"]}, "title": "Fruits Basket (2019)"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/kyuukyoku-shinka-shita-full-dive-rpg-ga-genjitsu-yori-mo-kusoge-dattara.-nptV"]}, "title": "Full Dive: This Ultimate Next-Gen Full Dive RPG Is Even Shittier Than Real Life!"}, {"title": "GOBLIN SLAYER", "absolute": false, "seasons": {"2": ["https://www.animeworld.so/play/goblin-slayer.cSbFz"]}}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/gunslinger-girl.vIUhE"]}, "title": "Gunslinger Girl"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/haibane-renmei.4yJAg"]}, "title": "Haibane Renmei"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/higurashi-no-naku-koro-ni-2020.DoJuQ"], "2": ["https://www.animeworld.tv/play/higurashi-no-naku-koro-ni-sotsu.j7iq7"]}, "title": "Higurashi: When They Cry \u2013 GOU"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/horimiya.Mse3-"]}, "title": "Horimiya"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/genjitsu-shugi-yuusha-no-oukoku-saikenki.LpDFM", "https://www.animeworld.tv/play/genjitsu-shugi-yuusha-no-oukoku-saikenki-parte-2.5cDX2"]}, "title": "How a Realist Hero Rebuilt the Kingdom"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/yuusha-yamemasu.pUnEf"]}, "title": "I'm Quitting Heroing"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/tsuki-to-laika-to-nosferatu.oon50"]}, "title": "Irina: The Vampire Cosmonaut"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/kemono-jihen.xb1V1"]}, "title": "Kemono Jihen"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/komi-cant-communicate-2.HgYCo"]}, "title": "Komi Can't Communicate"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/fantasy-bishoujo-juniku-ojisan-to.ZhCYX"]}, "title": "Life With an Ordinary Guy Who Reincarnated Into a Total Fantasy Knockout"}, {"absolute": false, "seasons": {"3": ["https://www.animeworld.tv/play/log-horizon-3.GGizW"]}, "title": "Log Horizon"}, {"title": "Made in Abyss", "seasons": {"2": ["https://www.animeworld.tv/play/made-in-abyss-2.T7h4U"]}, "absolute": false}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/mieruko-chan.M7FrP"]}, "title": "Mieruko-chan"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/moriarty-the-patriot.m9UeA", "https://www.animeworld.tv/play/moriarty-the-patriot-2.la8cg"]}, "title": "Moriarty the Patriot"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/megami-ryou-no-ryoubo-kun.48hLw"]}, "title": "Mother of the Goddess' Dormitory"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/mushoku-tensei-isekai-ittara-honki-dasu.FwbNc", "https://www.animeworld.tv/play/mushoku-tensei-isekai-ittara-honki-dasu-2.UNNMY"]}, "title": "Mushoku Tensei: Jobless Reincarnation"}, {"title": "My Happy Marriage", "absolute": false, "seasons": {"1": ["https://www.animeworld.so/play/watashi-no-shiawase-na-kekkon.vZoGM"]}}, {"absolute": false, "seasons": {"5": ["https://www.animeworld.tv/play/boku-no-hero-academia-5.JosMp"]}, "title": "My Hero Academia"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/my-life-as-a-villainess-all-routes-lead-to-doom-2.8RcIg"]}, "title": "My Next Life as a Villainess: All Routes Lead to Doom!"}, {"absolute": true, "seasons": {"absolute": ["https://www.animeworld.tv/play/one-piece-subita.qzG-L"]}, "title": "One Piece"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/orient.myAXD"]}, "title": "Orient"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/osananajimi-ga-zettai-ni-makenai-love-comedy.J2uW-"]}, "title": "Osamake: Romcom Where the Childhood Friend Won't Lose"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/puraore-pride-of-orange.OnxR3"]}, "title": "PuraOre! ~Pride of Orange~"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/ousama-ranking.W-Hq7"]}, "title": "Ranking of Kings"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/re-main.99hiG"]}, "title": "Re-Main"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/kaifuku-jutsushi-no-yarinaoshi.ol0ZN"]}, "title": "Redo of Healer"}, {"title": "Reincarnated as a Sword", "seasons": {"1": ["https://www.animeworld.tv/play/tensei-shitara-ken-deshita.ABj8v"]}, "absolute": false}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/bokutachi-no-remake.lB6Bh"]}, "title": "Remake Our Life!"}, {"title": "Rent-a-Girlfriend", "seasons": {"2": ["https://www.animeworld.tv/play/rent-a-girlfriend-2.qRp7e"]}, "absolute": false}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/baraou-no-souretsu.LVB1N"]}, "title": "Requiem of the Rose King"}, {"title": "SPY x FAMILY", "seasons": {"1": ["https://www.animeworld.tv/play/spy-x-family.iyICA"]}, "absolute": false}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/sasaki-to-miyano.mij7d"]}, "title": "Sasaki and Miyano"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/kuzu-no-honkai.MONpm"]}, "title": "Scum's Wish"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/seirei-gensouki.NgIKG/"]}, "title": "Seirei Gensouki: Spirit Chronicles"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/shadows-house.rCg-x"]}, "title": "Shadows House"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/sonny-boy.7Utce"]}, "title": "Sonny Boy"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/summertime-render.GDU38"]}, "title": "Summer Time Rendering"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/tatoeba-last-dungeon-mae-no-mura-no-shounen-ga-joban-no-machi-de-kurasu-youna-monogatari.EqCF-"]}, "title": "Suppose a Kid From the Last Dungeon Boonies Moved to a Starter Town?"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/taishou-otome-otogibanashi.rMAeY"]}, "title": "Taisho Otome Fairy Tale"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/karakai-jouzu-no-takagi-san.sKOCK"], "2": ["https://www.animeworld.tv/play/karakai-jouzu-no-takagi-san-2.M3Myw"], "3": ["https://www.animeworld.tv/play/karakai-jouzu-no-takagi-san-3.0ETgJ"]}, "title": "Teasing Master Takagi-san"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/tensei-shitara-slime-datta-ken-2.jregU", "https://www.animeworld.tv/play/tensei-shitara-slime-datta-ken-2nd-season-part-2.0K6vE"]}, "title": "That Time I Got Reincarnated as a Slime"}, {"title": "The Apothecary Diaries", "absolute": false, "seasons": {"1": ["https://www.animeworld.so/play/the-apothecary-diaries.3KzbH"]}}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/shiroi-suna-no-aquatope.wCt0J"]}, "title": "The Aquatope on White Sand"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/vanitas-no-carte.Z2zwI"]}, "title": "The Case Study of Vanitas"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/the-detective-is-already-dead.wWFQN"]}, "title": "The Detective Is Already Dead"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/the-duke-of-death-and-his-maid.YkcXB"]}, "title": "The Duke of Death and His Maid"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/meikyuu-black-company.JOXcZ"]}, "title": "The Dungeon of Black Company"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/tensai-ouji-no-akaji-kokka-saisei-jutsu.iwtzm"]}, "title": "The Genius Prince's Guide to Raising a Nation Out of Debt"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/the-heike-story.zQCZz"]}, "title": "The Heike Story"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/heion-sedai-no-idaten-tachi.1TB--"]}, "title": "The Idaten Deities Know Only Peace"}, {"title": "The Most Heretical Last Boss Queen: From Villainess to Savior", "absolute": false, "seasons": {"1": ["https://www.animeworld.so/play/the-most-heretical-last-boss-queen-from-villainess-to-savior.6KetrA"]}}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/the-promised-neverland-2.HPGKr"]}, "title": "The Promised Neverland"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/the-rising-of-the-shield-hero-2.lgwm2"]}, "title": "The Rising of the Shield Hero"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/the-saints-magic-power-is-omnipotent.iKqoM"]}, "title": "The Saint's Magic Power Is Omnipotent"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/the-worlds-finest-assassin-gets-reincarnated-in-another-world-as-an-aristocrat.WFG1J"]}, "title": "The World's Finest Assassin Gets Reincarnated in Another World as an Aristocrat"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/fumetsu-no-anata-e.tY9Ls"]}, "title": "To Your Eternity"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/tokyo-revengers.Zkczy"]}, "title": "Tokyo Revengers"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/tsuki-ga-michibiku-isekai-douchuu.qTZiB"]}, "title": "Tsukimichi -Moonlit Fantasy-"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/vivy-fluorite-eyes-song.3vh3i"]}, "title": "Vivy -Fluorite Eye's Song-"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/wonder-egg-priority.8k2by"]}, "title": "Wonder Egg Priority"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/world-trigger.L3ftn"], "2": ["https://www.animeworld.tv/play/world-trigger-2.VHfyT"], "3": ["https://www.animeworld.tv/play/world-trigger-3.1tSD0"]}, "title": "World Trigger"}] \ No newline at end of file diff --git a/tests/test_components.py b/tests/test_components.py index 1e18057..b4a731a 100644 --- a/tests/test_components.py +++ b/tests/test_components.py @@ -5,6 +5,7 @@ from src.components.backend.core import Constant as ctx from src.components.backend.connection.Sonarr import Sonarr from src.components.api import API +from src.components.frontend_OLD import Frontend import pathlib import sys, json @@ -24,6 +25,12 @@ class TestGeneral(unittest.TestCase): def setUpClass(cls): """Inizializza il nucleo.""" cls.core = Core() + + def testApp(self): + app = Frontend(self.core) + self.core.start() + uvicorn.run(app, port=5000, host='0.0.0.0', log_level='critical') + def testSonarr(self): with open(DUMP_FOLDER.joinpath('wanted_missing.json'), 'w') as f: @@ -71,6 +78,11 @@ def testFrontend(self): app = API(self.core) uvicorn.run(app, port=5000, host='0.0.0.0') + def testFrontend_OLD(self): + app = Frontend(self.core) + app.run(debug=False, host='0.0.0.0', use_reloader=False) + + def testExternalDB(self): ex = ExternalDB() ex.sync() From a42d0367f4a6137272ab70c8631f2a7b202523e8 Mon Sep 17 00:00:00 2001 From: Lorenzo Chesi Date: Mon, 23 Oct 2023 09:53:42 +0200 Subject: [PATCH 07/39] #122 --- src/components/backend/core/Processor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/backend/core/Processor.py b/src/components/backend/core/Processor.py index 2e3477e..ce31e88 100644 --- a/src/components/backend/core/Processor.py +++ b/src/components/backend/core/Processor.py @@ -4,6 +4,7 @@ from typing import Iterable, List from functools import reduce +from itertools import count class Processor: """Processa i dati che provengono da Sonarr""" @@ -43,7 +44,7 @@ def getAllMissing(self) -> list: missing = [] - for page in range(1,100): + for page in count(1): res = self.sonarr.wantedMissing(page=page) res.raise_for_status() data = res.json() From 716f2432bd874cf303d3d97fb6384eb04d874db9 Mon Sep 17 00:00:00 2001 From: MainKronos Date: Sun, 29 Oct 2023 15:22:05 +0100 Subject: [PATCH 08/39] Exception --- src/components/backend/core/Core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/backend/core/Core.py b/src/components/backend/core/Core.py index d24b88e..4697fb9 100644 --- a/src/components/backend/core/Core.py +++ b/src/components/backend/core/Core.py @@ -174,7 +174,7 @@ def job(self): self.log.info("") self.log.info("─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ") self.log.info("") - except (aw.DeprecatedLibrary, httpx.HTTPError) as e: + except aw.DeprecatedLibrary as e: self.log.error(cs.red(f"🅴🆁🆁🅾🆁: {e}")) def wakeUp(self) -> bool: From ff24548e63bb95e14e20a87322faf068a7f78c60 Mon Sep 17 00:00:00 2001 From: MainKronos Date: Fri, 3 Nov 2023 14:20:24 +0100 Subject: [PATCH 09/39] REST API --- DEV.md | 2 +- src/components/api/TableRoute.py | 32 ----- src/components/api/__init__.py | 51 ++++--- src/components/api/routes/Settings.py | 26 ++++ src/components/api/routes/Table.py | 151 ++++++++++++++++++++ src/components/api/routes/Tags.py | 27 ++++ src/components/backend/database/Settings.py | 7 +- src/components/backend/database/Table.py | 6 + src/dev_api.py | 2 +- src/main.py | 4 +- 10 files changed, 244 insertions(+), 64 deletions(-) delete mode 100644 src/components/api/TableRoute.py create mode 100644 src/components/api/routes/Settings.py create mode 100644 src/components/api/routes/Table.py create mode 100644 src/components/api/routes/Tags.py diff --git a/DEV.md b/DEV.md index dc709e1..cc74fc2 100644 --- a/DEV.md +++ b/DEV.md @@ -3,7 +3,7 @@ Breve documentazione per costruire l'interfaccia: 1. Avviare il backend: ```sh cd src -python3 ./dev_apipy +python3 ./dev_api.py ``` 2. Avviare l'interfaccia: diff --git a/src/components/api/TableRoute.py b/src/components/api/TableRoute.py deleted file mode 100644 index 2275102..0000000 --- a/src/components/api/TableRoute.py +++ /dev/null @@ -1,32 +0,0 @@ -from ..backend import Core - -from fastapi.routing import APIRouter - -def TableRoute(core:Core) -> APIRouter: - route = APIRouter(prefix='/table', tags=['table']) - - @route.get("/") - def get_table() -> list[dict]: - """ - Restituisce la lista di elementi della tabella. - - ```json - [ - ... - { - "absolute": false, - "seasons": { - "1": [ - "https://www.animeworld.tv/play/bakuten.MfCW1" - ] - }, - "title": "Backflip!!" - }, - ... - ] - ``` - """ - return core.table.getData() - - - return route \ No newline at end of file diff --git a/src/components/api/__init__.py b/src/components/api/__init__.py index 29770d9..0e93a6c 100644 --- a/src/components/api/__init__.py +++ b/src/components/api/__init__.py @@ -1,39 +1,38 @@ from ..backend import Core from typing import Union, TypedDict -from fastapi import FastAPI -from fastapi.routing import APIRouter -from fastapi.middleware.cors import CORSMiddleware +from apiflask import APIFlask, APIBlueprint -from .TableRoute import TableRoute +from .routes.Table import Table +from .routes.Settings import Settings +from .routes.Tags import Tags -def API(core:Core) -> FastAPI: - api = APIRouter(prefix='/api') +def API(core:Core) -> APIFlask: + app = APIFlask( + __name__, + title='Sonarr-AnimeDownloader', + version=core.version + ) + + app.config['AUTO_404_RESPONSE'] = False + app.config['AUTO_VALIDATION_ERROR_RESPONSE'] = False + + @app.after_request + def cors(res): + res.headers['Access-Control-Allow-Origin'] = '*' + return res + + api = APIBlueprint('api', __name__, url_prefix='/api', tag='General') @api.get("/version") - def get_version() -> TypedDict('',{'version':str}): + def get_version(): """ Restituisce il numero di versione. - - ```json - {'version': 'dev'} - ``` """ return {"version": core.version} - api.include_router(TableRoute(core)) - - app = FastAPI( - title='Sonarr-AnimeDownloader', - summary='API', - version=core.version - ) - app.include_router(api) - app.add_middleware( - CORSMiddleware, - allow_origins=['*'], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], - ) + api.register_blueprint(Table(core)) + api.register_blueprint(Settings(core)) + api.register_blueprint(Tags(core)) + app.register_blueprint(api) return app \ No newline at end of file diff --git a/src/components/api/routes/Settings.py b/src/components/api/routes/Settings.py new file mode 100644 index 0000000..84314d7 --- /dev/null +++ b/src/components/api/routes/Settings.py @@ -0,0 +1,26 @@ +from ...backend import Core + +from apiflask import APIBlueprint, abort, fields + +def Settings(core:Core) -> APIBlueprint: + + route = APIBlueprint('settings', __name__, url_prefix='/settings', tag='Settings') + + @route.get('/') + def get_settings(): + """Restituisce le impostazioni.""" + + return core.settings.getData() + + @route.patch('/') + @route.input({'value': fields.Raw()}) + def edit_settings(setting:str, json_data:dict): + + if setting not in core.settings: + abort(400, f"L'impostazione '{setting}' non esiste.") + + core.settings[setting] = json_data['value'] + + return {'message': f"Impostazione '{setting}' aggiornata."} + + return route diff --git a/src/components/api/routes/Table.py b/src/components/api/routes/Table.py new file mode 100644 index 0000000..6fd51a0 --- /dev/null +++ b/src/components/api/routes/Table.py @@ -0,0 +1,151 @@ +from ...backend import Core + +from apiflask import APIBlueprint, abort, fields + +def Table(core:Core) -> APIBlueprint: + route = APIBlueprint('table', __name__, url_prefix='/table', tag='Table') + + @route.get("/") + def get_table() -> list[dict]: + """Restituisce la lista di elementi della tabella.""" + return core.table.getData() + + @route.get('/') + def get_serie(title:str): + """Restituisce le informazioni di una serie.""" + if not title in core.table: + abort(400, f"La serie '{title}' non esiste.") + + return core.table[title] + + @route.get('/<title>/<season>') + def get_links(title:str, season:int): + """Restituisce tutti i link di una stagione.""" + + season = str(season) + serie = get_serie(title) + if not season in serie["seasons"]: + abort(400, f"La stagione '{season}' non esiste.") + + return serie["seasons"][season] + + @route.delete('/<title>') + def del_serie(title:str): + """Rimuove una serie.""" + if not title in core.table: + abort(400, f"La serie '{title}' non esiste.") + + core.table.removeSerie(title) + + return {"message": f"Serie '{title}' rimossa."} + + @route.delete('/<title>/<season>') + def del_season(title:str, season:int): + """Rimuove una stagione.""" + + serie = get_serie(title) + if not season in serie["seasons"]: + abort(400, f"La stagione '{season}' non esiste.") + + core.table.removeSeason(title, season) + + return {"message": f"Stagione '{season}' rimossa."} + + @route.delete('/<title>/<season>/<path:link>') + def del_link(title:str, season:int, link:str): + """Rimuove un link.""" + + links = get_links(title, season) + if not link in links: + abort(400, f"Il link '{link}' non esiste.") + + core.table.removeUrl(title, season, link) + + return {"message": f"Link '{link}' rimosso."} + + + @route.post('/') + @route.input({'title':fields.String(),'absolute': fields.Boolean()}) + def add_serie(json_data:dict): + """Aggiunge una nuova serie.""" + + title:str = json_data['title'] + absolute:bool = json_data['absolute'] + + if not core.table.appendSerie(title, absolute): + abort(409, f"La serie '{title}' è già presente.") + + return {"message": f"Serie '{title}' aggiunta."} + + @route.post('/<title>') + @route.input({'season':fields.Integer()}) + def add_season(title:str, json_data:dict): + """Aggiunge una nuova stagione.""" + + season:int = json_data['season'] + + if not core.table.appendSeason(title, season): + abort(409, f"Conflitto stagione '{season}'.") + + return {"message": f"Stagione '{season}' aggiunta."} + + @route.post('/<title>/<season>') + @route.input({'links':fields.List(fields.String())}) + def add_links(title:str, season:int, json_data:dict): + """Aggiunge dei links a una stagione.""" + + links:list[str] = json_data['links'] + + if not core.table.appendUrls(title, season, links): + abort(409, f"Conflitto links {links}.") + + return {"message": f"Links {links} aggiunti."} + + @route.patch('/<title>') + @route.input({'title':fields.String()}) + def edit_serie(title:str, json_data:dict): + """Rinomina il titolo di una serie.""" + + new_title:str = json_data['title'] + + if not title in core.table: + abort(400, f"La serie '{title}' non esiste.") + + if not core.table.renameSerie(title, new_title): + abort(409, f"Il titolo '{new_title}' esiste già.") + + return {"message": f"Serie '{new_title}' aggiornata."} + + @route.patch('/<title>/<season>') + @route.input({'season':fields.Integer()}) + def edit_season(title:str, season:int, json_data:dict): + """Rinomina una stagione.""" + + season = str(season) + new_season:str = str(json_data['season']) + serie = get_serie(title) + if not season in serie["seasons"]: + abort(400, f"La stagione '{season}' non esiste.") + + if not core.table.renameSeason(title, season, new_season): + abort(409, f"La stagione '{new_season}' esiste già.") + + return {"message": f"Stagione '{new_season}' aggiornata."} + + @route.patch('/<title>/<season>/<path:link>') + @route.input({'link':fields.String()}) + def edit_link(title:str, season:int, link:str, json_data:dict): + """Rinomina un link.""" + + new_link:str = json_data['link'] + links = get_links(title, season) + if not link in links: + abort(400, f"Il link '{link}' non esiste.") + + if not core.table.renameUrl(title, season, link, new_link): + abort(409, f"Il link '{new_link}' esiste già.") + + return {"message": f"Link '{new_link}' aggiornato."} + + + return route \ No newline at end of file diff --git a/src/components/api/routes/Tags.py b/src/components/api/routes/Tags.py new file mode 100644 index 0000000..54fd325 --- /dev/null +++ b/src/components/api/routes/Tags.py @@ -0,0 +1,27 @@ +from ...backend import Core + +from apiflask import APIBlueprint, abort, fields + +def Tags(core:Core) -> APIBlueprint: + + route = APIBlueprint('tags', __name__, url_prefix='/tags', tag='Tags') + + @route.get('/') + def get_tags(): + """Restituisce i tags.""" + + return core.tags.getData() + + @route.post('/') + @route.input({'name':fields.String(),'id': fields.Integer(), 'active': fields.Boolean()}) + def add_tag(json_data:dict): + """Aggiunge un tag.""" + + try: + core.tags.append(json_data['id'], json_data['name'], json_data['active']) + except ValueError as e: + abort(400, str(e)) + + return {'message': f"Tag '{json_data['name']}' aggiunto."} + + return route \ No newline at end of file diff --git a/src/components/backend/database/Settings.py b/src/components/backend/database/Settings.py index e7562a4..e889fa4 100644 --- a/src/components/backend/database/Settings.py +++ b/src/components/backend/database/Settings.py @@ -28,4 +28,9 @@ def __iter__(self): yield key def __len__(self) -> int: - return len(self._data) \ No newline at end of file + return len(self._data) + + def __contains__(self, key: str): + """Controlla se un impostazione esiste.""" + + return key in self._data \ No newline at end of file diff --git a/src/components/backend/database/Table.py b/src/components/backend/database/Table.py index dd96924..74e7ec5 100644 --- a/src/components/backend/database/Table.py +++ b/src/components/backend/database/Table.py @@ -153,6 +153,9 @@ def appendSeason(self, title:str, season:Union[str,int]) -> bool: serie = self[title] + # Per sicurezza + season = str(season) + # Se la serie è in formato assoluto e la stagione non si chiama "absolute" # Oppure la serie non è in formato assoluto e la stagione si chiama "absolute" if (serie["absolute"]) ^ (season == "absolute"): return False @@ -186,6 +189,9 @@ def appendUrls(self, title:str, season:Union[str,int], urls:list[str]) -> bool: serie = self[title] + # Per sicurezza + season = str(season) + # Controllo se c'è una stagione con quel numero if season not in serie["seasons"]: # Altrimenti la creo diff --git a/src/dev_api.py b/src/dev_api.py index 1153b3f..728cfd7 100644 --- a/src/dev_api.py +++ b/src/dev_api.py @@ -27,4 +27,4 @@ def main(): return app if __name__ == "__main__": - uvicorn.run("dev_api:main", port=5000, log_level="info", reload=True, factory=True) \ No newline at end of file + main().run(debug=True, host='0.0.0.0', use_reloader=True) \ No newline at end of file diff --git a/src/main.py b/src/main.py index a4f2c45..c390aeb 100644 --- a/src/main.py +++ b/src/main.py @@ -4,7 +4,6 @@ from components.frontend_OLD import Frontend import threading -import uvicorn def main(): # Carico il core @@ -24,8 +23,7 @@ def main(): core.join() def server(app): - # uvicorn.run(app, port=5000, host='0.0.0.0', log_level='critical') - app.run(debug=False, host='0.0.0.0', use_reloader=False) # DEPRECATO (DA RIMUOVERE) + app.run(debug=False, host='0.0.0.0', use_reloader=False) if __name__ == '__main__': main() \ No newline at end of file From df0716c61abd04dfbb29d39861d6ddeb40345915 Mon Sep 17 00:00:00 2001 From: MainKronos <lorenzo.chesi@live.it> Date: Fri, 3 Nov 2023 16:18:55 +0100 Subject: [PATCH 10/39] API REST --- src/components/api/routes/Settings.py | 1 + src/components/api/routes/Tags.py | 55 +++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/src/components/api/routes/Settings.py b/src/components/api/routes/Settings.py index 84314d7..95f60c3 100644 --- a/src/components/api/routes/Settings.py +++ b/src/components/api/routes/Settings.py @@ -15,6 +15,7 @@ def get_settings(): @route.patch('/<setting>') @route.input({'value': fields.Raw()}) def edit_settings(setting:str, json_data:dict): + """Modifica un impostazione.""" if setting not in core.settings: abort(400, f"L'impostazione '{setting}' non esiste.") diff --git a/src/components/api/routes/Tags.py b/src/components/api/routes/Tags.py index 54fd325..1f4c304 100644 --- a/src/components/api/routes/Tags.py +++ b/src/components/api/routes/Tags.py @@ -23,5 +23,60 @@ def add_tag(json_data:dict): abort(400, str(e)) return {'message': f"Tag '{json_data['name']}' aggiunto."} + + + @route.get('/<tag>') + def get_tag(tag:str|int): + """Restituisce le informazioni di un tag.""" + + try: tag = int(tag) + except ValueError: pass + + if tag not in core.tags: + abort(400, f"Il tag '{tag}' non esiste.") + + return core.tags[tag] + + @route.delete('/<tag>') + def del_tag(tag:str|int): + """Aggiunge un tag.""" + + try: tag = int(tag) + except ValueError: pass + + if tag not in core.tags: + abort(400, f"Il tag '{tag}' non esiste.") + + del core.tags[tag] + + return {'message': f"Tag '{tag}' eliminato."} + + @route.patch('/<tag>/enable') + def enable_tag(tag:str|int): + """Attiva un tag.""" + + try: tag = int(tag) + except ValueError: pass + + if tag not in core.tags: + abort(400, f"Il tag '{tag}' non esiste.") + + core.tags.enable(tag) + + return {'message': f"Tag '{tag}' attivato."} + + @route.patch('/<tag>/disable') + def disable_tag(tag:str|int): + """Disattiva un tag.""" + + try: tag = int(tag) + except ValueError: pass + + if tag not in core.tags: + abort(400, f"Il tag '{tag}' non esiste.") + + core.tags.disable(tag) + + return {'message': f"Tag '{tag}' disattivato."} return route \ No newline at end of file From 8b0841d317cb7bc3ab251368efb5204a88a1653c Mon Sep 17 00:00:00 2001 From: MainKronos <lorenzo.chesi@live.it> Date: Sun, 5 Nov 2023 12:18:00 +0100 Subject: [PATCH 11/39] style --- frontend/index.html | 2 +- frontend/package-lock.json | 1087 ++++++++++++++---- frontend/package.json | 5 + frontend/src/components/Container/index.scss | 11 - frontend/src/components/Container/index.tsx | 14 - frontend/src/components/Footer/index.scss | 9 - frontend/src/components/Footer/index.tsx | 18 +- frontend/src/components/Header/index.scss | 50 - frontend/src/components/Header/index.tsx | 28 +- frontend/src/components/Icon/index.scss | 1 - frontend/src/components/Icon/index.tsx | 9 - frontend/src/components/Navigator/index.scss | 84 -- frontend/src/components/Navigator/index.tsx | 30 - frontend/src/components/Table/index.scss | 73 -- frontend/src/components/Table/index.tsx | 120 -- frontend/src/components/index.tsx | 6 +- frontend/src/pages/home/App.tsx | 151 ++- frontend/src/styles/_global.scss | 3 - frontend/src/styles/base.scss | 35 - frontend/src/utils/API.ts | 8 +- frontend/src/utils/types.ts | 9 + src/components/api/__init__.py | 3 + 22 files changed, 1067 insertions(+), 689 deletions(-) delete mode 100644 frontend/src/components/Container/index.scss delete mode 100644 frontend/src/components/Container/index.tsx delete mode 100644 frontend/src/components/Footer/index.scss delete mode 100644 frontend/src/components/Header/index.scss delete mode 100644 frontend/src/components/Icon/index.scss delete mode 100644 frontend/src/components/Icon/index.tsx delete mode 100644 frontend/src/components/Navigator/index.scss delete mode 100644 frontend/src/components/Navigator/index.tsx delete mode 100644 frontend/src/components/Table/index.scss delete mode 100644 frontend/src/components/Table/index.tsx delete mode 100644 frontend/src/styles/_global.scss delete mode 100644 frontend/src/styles/base.scss create mode 100644 frontend/src/utils/types.ts diff --git a/frontend/index.html b/frontend/index.html index 0c33897..bdf370b 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,7 +2,7 @@ <html lang="it"> <head> <meta charset="UTF-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0"/> + <meta name="viewport" content="initial-scale=1, width=device-width" /> <meta charset="UTF-8"> <meta name="description" content="Home"> <meta name="author" content="MainKronos"> diff --git a/frontend/package-lock.json b/frontend/package-lock.json index bbc41e3..5676bfa 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,11 @@ "name": "frontend", "version": "0.0.0", "dependencies": { + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@fontsource/roboto": "^5.0.8", + "@mui/icons-material": "^5.14.16", + "@mui/material": "^5.14.16", "react": "^18.2.0", "react-dom": "^18.2.0" }, @@ -52,7 +57,6 @@ "version": "7.22.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", - "dev": true, "dependencies": { "@babel/highlight": "^7.22.13", "chalk": "^2.4.2" @@ -62,18 +66,18 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.20.tgz", - "integrity": "sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", + "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.0.tgz", - "integrity": "sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", + "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -81,10 +85,10 @@ "@babel/generator": "^7.23.0", "@babel/helper-compilation-targets": "^7.22.15", "@babel/helper-module-transforms": "^7.23.0", - "@babel/helpers": "^7.23.0", + "@babel/helpers": "^7.23.2", "@babel/parser": "^7.23.0", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.0", + "@babel/traverse": "^7.23.2", "@babel/types": "^7.23.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -100,6 +104,12 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -187,7 +197,6 @@ "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "dev": true, "dependencies": { "@babel/types": "^7.22.15" }, @@ -251,7 +260,6 @@ "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -260,7 +268,6 @@ "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -275,13 +282,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.1.tgz", - "integrity": "sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", + "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", "dev": true, "dependencies": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.0", + "@babel/traverse": "^7.23.2", "@babel/types": "^7.23.0" }, "engines": { @@ -292,7 +299,6 @@ "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", - "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", @@ -344,6 +350,17 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", @@ -359,9 +376,9 @@ } }, "node_modules/@babel/traverse": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.0.tgz", - "integrity": "sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dev": true, "dependencies": { "@babel/code-frame": "^7.22.13", @@ -383,7 +400,6 @@ "version": "7.23.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", - "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.20", @@ -393,6 +409,139 @@ "node": ">=6.9.0" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/serialize": "^1.1.2", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "dependencies": { + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/react": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz", + "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", + "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", + "dependencies": { + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + }, + "node_modules/@emotion/styled": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz", + "integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/is-prop-valid": "^1.2.1", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + }, "node_modules/@esbuild/android-arm": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", @@ -761,18 +910,18 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", - "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -793,9 +942,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.22.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", - "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -808,21 +957,60 @@ } }, "node_modules/@eslint/js": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", - "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", + "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", + "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==", + "dependencies": { + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", + "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", + "dependencies": { + "@floating-ui/core": "^1.4.2", + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz", + "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==", + "dependencies": { + "@floating-ui/dom": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", + "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" + }, + "node_modules/@fontsource/roboto": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.0.8.tgz", + "integrity": "sha512-XxPltXs5R31D6UZeLIV1td3wTXU3jzd3f2DLsXI8tytMGBkIsGcc9sIyiupRtA8y73HAhuSCeweOoBqf6DbWCA==" + }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" }, @@ -844,9 +1032,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "node_modules/@jridgewell/gen-mapping": { @@ -888,15 +1076,260 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mui/base": { + "version": "5.0.0-beta.22", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.22.tgz", + "integrity": "sha512-l4asGID5tmyerx9emJfXOKLyXzaBtdXNIFE3M+IrSZaFtGFvaQKHhc3+nxxSxPf1+G44psjczM0ekRQCdXx9HA==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@floating-ui/react-dom": "^2.0.2", + "@mui/types": "^7.2.8", + "@mui/utils": "^5.14.16", + "@popperjs/core": "^2.11.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "5.14.16", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.16.tgz", + "integrity": "sha512-97isBjzH2v1K7oB4UH2f4NOkBShOynY6dhnoR2XlUk/g6bb7ZBv2I3D1hvvqPtpEigKu93e7f/jAYr5d9LOc5w==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + } + }, + "node_modules/@mui/icons-material": { + "version": "5.14.16", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.14.16.tgz", + "integrity": "sha512-wmOgslMEGvbHZjFLru8uH5E+pif/ciXAvKNw16q6joK6EWVWU5rDYWFknDaZhCvz8ZE/K8ZnJQ+lMG6GgHzXbg==", + "dependencies": { + "@babel/runtime": "^7.23.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "5.14.16", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.16.tgz", + "integrity": "sha512-W4zZ4vnxgGk6/HqBwgsDHKU7x2l2NhX+r8gAwfg58Rhu3ikfY7NkIS6y8Gl3NkATc4GG1FNaGjjpQKfJx3U6Jw==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@mui/base": "5.0.0-beta.22", + "@mui/core-downloads-tracker": "^5.14.16", + "@mui/system": "^5.14.16", + "@mui/types": "^7.2.8", + "@mui/utils": "^5.14.16", + "@types/react-transition-group": "^4.4.8", + "clsx": "^2.0.0", + "csstype": "^3.1.2", + "prop-types": "^15.8.1", + "react-is": "^18.2.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "5.14.16", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.16.tgz", + "integrity": "sha512-FNlL0pTSEBh8nXsVWreCHDSHk+jG8cBx1sxRbT8JVtL+PYbYPi802zfV4B00Kkf0LNRVRvAVQwojMWSR/MYGng==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@mui/utils": "^5.14.16", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "5.14.16", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.16.tgz", + "integrity": "sha512-FfvYvTG/Zd+KXMMImbcMYEeQAbONGuX5Vx3gBmmtB6KyA7Mvm9Pma1ly3R0gc44yeoFd+2wBjn1feS8h42HW5w==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@emotion/cache": "^11.11.0", + "csstype": "^3.1.2", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "5.14.16", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.16.tgz", + "integrity": "sha512-uKnPfsDqDs8bbN54TviAuoGWOmFiQLwNZ3Wvj+OBkJCzwA6QnLb/sSeCB7Pk3ilH4h4jQ0BHtbR+Xpjy9wlOuA==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@mui/private-theming": "^5.14.16", + "@mui/styled-engine": "^5.14.16", + "@mui/types": "^7.2.8", + "@mui/utils": "^5.14.16", + "clsx": "^2.0.0", + "csstype": "^3.1.2", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.8", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.8.tgz", + "integrity": "sha512-9u0ji+xspl96WPqvrYJF/iO+1tQ1L5GTaDOeG3vCR893yy7VcWwRNiVMmPdPNpMDqx0WV1wtEW9OMwK9acWJzQ==", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "5.14.16", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.16.tgz", + "integrity": "sha512-3xV31GposHkwRbQzwJJuooWpK2ybWdEdeUPtRjv/6vjomyi97F3+68l+QVj9tPTvmfSbr2sx5c/NuvDulrdRmA==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@types/prop-types": "^15.7.9", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -932,10 +1365,19 @@ "node": ">= 8" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@types/babel__core": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz", - "integrity": "sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA==", + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.3.tgz", + "integrity": "sha512-54fjTSeSHwfan8AyHWrKbfBWiEUrNTZsUwPTDSNaaP1QDQIZbeNUg3a59E9D+375MzUw/x1vx2/0F5LBz+AeYA==", "dev": true, "dependencies": { "@babel/parser": "^7.20.7", @@ -946,18 +1388,18 @@ } }, "node_modules/@types/babel__generator": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.5.tgz", - "integrity": "sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w==", + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.6.tgz", + "integrity": "sha512-66BXMKb/sUWbMdBNdMvajU7i/44RkrA3z/Yt1c7R5xejt8qh84iU54yUWCtm0QwGJlDcf/gg4zd/x4mpLAlb/w==", "dev": true, "dependencies": { "@babel/types": "^7.0.0" } }, "node_modules/@types/babel__template": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.2.tgz", - "integrity": "sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ==", + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.3.tgz", + "integrity": "sha512-ciwyCLeuRfxboZ4isgdNZi/tkt06m8Tw6uGbBSBgWrnnZGNXiEyM27xc/PjXGQLqlZ6ylbgHMnm7ccF9tCkOeQ==", "dev": true, "dependencies": { "@babel/parser": "^7.1.0", @@ -965,37 +1407,43 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.2.tgz", - "integrity": "sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw==", + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.3.tgz", + "integrity": "sha512-Lsh766rGEFbaxMIDH7Qa+Yha8cMVI3qAK6CHt3OR0YfxOIn5Z54iHiyDRycHrBqeIiqGa20Kpsv1cavfBKkRSw==", "dev": true, "dependencies": { "@babel/types": "^7.20.7" } }, "node_modules/@types/json-schema": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", - "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", + "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", "dev": true }, "node_modules/@types/node": { - "version": "20.8.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.0.tgz", - "integrity": "sha512-LzcWltT83s1bthcvjBmiBvGJiiUe84NWRHkw+ZV6Fr41z2FbIzvc815dk2nQ3RAKMuN2fkenM/z3Xv2QzEpYxQ==", - "dev": true + "version": "20.8.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", + "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.1.tgz", + "integrity": "sha512-3YmXzzPAdOTVljVMkTMBdBEvlOLg2cDQaDhnnhT3nT9uDbnJzjWhKlzb+desT12Y7tGqaN6d+AbozcKzyL36Ng==" }, "node_modules/@types/prop-types": { - "version": "15.7.8", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.8.tgz", - "integrity": "sha512-kMpQpfZKSCBqltAJwskgePRaYRFukDkm1oItcAbC3gNELR20XIBcN9VRgg4+m8DKsTfkWeA4m4Imp4DDuWy7FQ==", - "dev": true + "version": "15.7.9", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.9.tgz", + "integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==" }, "node_modules/@types/react": { - "version": "18.2.24", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.24.tgz", - "integrity": "sha512-Ee0Jt4sbJxMu1iDcetZEIKQr99J1Zfb6D4F3qfUWoR1JpInkY1Wdg4WwCyBjL257D0+jGqSl1twBjV8iCaC0Aw==", - "dev": true, + "version": "18.2.34", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.34.tgz", + "integrity": "sha512-U6eW/alrRk37FU/MS2RYMjx0Va2JGIVXELTODaTIYgvWGCV4Y4TfTUzG8DdmpDNIT0Xpj/R7GfyHOJJrDttcvg==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -1003,37 +1451,44 @@ } }, "node_modules/@types/react-dom": { - "version": "18.2.8", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.8.tgz", - "integrity": "sha512-bAIvO5lN/U8sPGvs1Xm61rlRHHaq5rp5N3kp9C+NJ/Q41P8iqjkXSu0+/qu8POsjH9pNWb0OYabFez7taP7omw==", + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.14.tgz", + "integrity": "sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ==", "dev": true, "dependencies": { "@types/react": "*" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.8.tgz", + "integrity": "sha512-QmQ22q+Pb+HQSn04NL3HtrqHwYMf4h3QKArOy5F8U5nEVMaihBs3SR10WiOM1iwPz5jIo8x/u11al+iEGZZrvg==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/scheduler": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.4.tgz", - "integrity": "sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ==", - "dev": true + "version": "0.16.5", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.5.tgz", + "integrity": "sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==" }, "node_modules/@types/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.3.tgz", - "integrity": "sha512-vntq452UHNltxsaaN+L9WyuMch8bMd9CqJ3zhzTPXXidwbf5mqqKCVXEuvRZUqLJSTLeWE65lQwyXsRGnXkCTA==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.9.1.tgz", + "integrity": "sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.7.3", - "@typescript-eslint/type-utils": "6.7.3", - "@typescript-eslint/utils": "6.7.3", - "@typescript-eslint/visitor-keys": "6.7.3", + "@typescript-eslint/scope-manager": "6.9.1", + "@typescript-eslint/type-utils": "6.9.1", + "@typescript-eslint/utils": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -1059,15 +1514,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.3.tgz", - "integrity": "sha512-TlutE+iep2o7R8Lf+yoer3zU6/0EAUc8QIBB3GYBc1KGz4c4TRm83xwXUZVPlZ6YCLss4r77jbu6j3sendJoiQ==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.9.1.tgz", + "integrity": "sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.7.3", - "@typescript-eslint/types": "6.7.3", - "@typescript-eslint/typescript-estree": "6.7.3", - "@typescript-eslint/visitor-keys": "6.7.3", + "@typescript-eslint/scope-manager": "6.9.1", + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/typescript-estree": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1", "debug": "^4.3.4" }, "engines": { @@ -1087,13 +1542,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.3.tgz", - "integrity": "sha512-wOlo0QnEou9cHO2TdkJmzF7DFGvAKEnB82PuPNHpT8ZKKaZu6Bm63ugOTn9fXNJtvuDPanBc78lGUGGytJoVzQ==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.9.1.tgz", + "integrity": "sha512-38IxvKB6NAne3g/+MyXMs2Cda/Sz+CEpmm+KLGEM8hx/CvnSRuw51i8ukfwB/B/sESdeTGet1NH1Wj7I0YXswg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.3", - "@typescript-eslint/visitor-keys": "6.7.3" + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1104,13 +1559,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.3.tgz", - "integrity": "sha512-Fc68K0aTDrKIBvLnKTZ5Pf3MXK495YErrbHb1R6aTpfK5OdSFj0rVN7ib6Tx6ePrZ2gsjLqr0s98NG7l96KSQw==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.9.1.tgz", + "integrity": "sha512-eh2oHaUKCK58qIeYp19F5V5TbpM52680sB4zNSz29VBQPTWIlE/hCj5P5B1AChxECe/fmZlspAWFuRniep1Skg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.7.3", - "@typescript-eslint/utils": "6.7.3", + "@typescript-eslint/typescript-estree": "6.9.1", + "@typescript-eslint/utils": "6.9.1", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -1131,9 +1586,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.3.tgz", - "integrity": "sha512-4g+de6roB2NFcfkZb439tigpAMnvEIg3rIjWQ+EM7IBaYt/CdJt6em9BJ4h4UpdgaBWdmx2iWsafHTrqmgIPNw==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.9.1.tgz", + "integrity": "sha512-BUGslGOb14zUHOUmDB2FfT6SI1CcZEJYfF3qFwBeUrU6srJfzANonwRYHDpLBuzbq3HaoF2XL2hcr01c8f8OaQ==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1144,13 +1599,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.3.tgz", - "integrity": "sha512-YLQ3tJoS4VxLFYHTw21oe1/vIZPRqAO91z6Uv0Ss2BKm/Ag7/RVQBcXTGcXhgJMdA4U+HrKuY5gWlJlvoaKZ5g==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.1.tgz", + "integrity": "sha512-U+mUylTHfcqeO7mLWVQ5W/tMLXqVpRv61wm9ZtfE5egz7gtnmqVIw9ryh0mgIlkKk9rZLY3UHygsBSdB9/ftyw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.3", - "@typescript-eslint/visitor-keys": "6.7.3", + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1171,17 +1626,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.3.tgz", - "integrity": "sha512-vzLkVder21GpWRrmSR9JxGZ5+ibIUSudXlW52qeKpzUEQhRSmyZiVDDj3crAth7+5tmN1ulvgKaCU2f/bPRCzg==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.9.1.tgz", + "integrity": "sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.7.3", - "@typescript-eslint/types": "6.7.3", - "@typescript-eslint/typescript-estree": "6.7.3", + "@typescript-eslint/scope-manager": "6.9.1", + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/typescript-estree": "6.9.1", "semver": "^7.5.4" }, "engines": { @@ -1196,12 +1651,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.3.tgz", - "integrity": "sha512-HEVXkU9IB+nk9o63CeICMHxFWbHWr3E1mpilIQBe9+7L/lH97rleFLVtYsfnWB+JVMaiFnEaxvknvmIzX+CqVg==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.1.tgz", + "integrity": "sha512-MUaPUe/QRLEffARsmNfmpghuQkW436DvESW+h+M52w0coICHRfD6Np9/K6PdACwnrq1HmuLl+cSPZaJmeVPkSw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.3", + "@typescript-eslint/types": "6.9.1", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -1212,16 +1667,22 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/@vitejs/plugin-react": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.1.0.tgz", - "integrity": "sha512-rM0SqazU9iqPUraQ2JlIvReeaxOoRj6n+PzB1C0cBzIbd8qP336nC39/R9yPi3wVcah7E7j/kdU1uCUqMEU4OQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.1.1.tgz", + "integrity": "sha512-Jie2HERK+uh27e+ORXXwEP5h0Y2lS9T2PRGbfebiHGlwzDO0dEnd2aNtOR/qjBlPb1YgxwAONeblL1xqLikLag==", "dev": true, "dependencies": { - "@babel/core": "^7.22.20", + "@babel/core": "^7.23.2", "@babel/plugin-transform-react-jsx-self": "^7.22.5", "@babel/plugin-transform-react-jsx-source": "^7.22.5", - "@types/babel__core": "^7.20.2", + "@types/babel__core": "^7.20.3", "react-refresh": "^0.14.0" }, "engines": { @@ -1232,9 +1693,9 @@ } }, "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1281,7 +1742,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -1317,6 +1777,20 @@ "node": ">=8" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1390,15 +1864,14 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001542", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001542.tgz", - "integrity": "sha512-UrtAXVcj1mvPBFQ4sKd38daP8dEcXXr5sQe6QNNinaPd0iA/cxg9/l3VrSdL73jgw5sKyuQ6jNgiKO12W3SsVA==", + "version": "1.0.30001561", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001561.tgz", + "integrity": "sha512-NTt0DNoKe958Q0BE0j0c1V9jbUzhBxHIEJy7asmGrpE0yG63KTV7PLHPnK2E1O9RsQrQ081I3NLuXGS6zht3cw==", "dev": true, "funding": [ { @@ -1419,7 +1892,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -1429,6 +1901,14 @@ "node": ">=4" } }, + "node_modules/chalk/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -1468,11 +1948,18 @@ "node": ">= 6" } }, + "node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -1480,8 +1967,7 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "node_modules/concat-map": { "version": "0.0.1", @@ -1490,10 +1976,24 @@ "dev": true }, "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } }, "node_modules/cross-spawn": { "version": "7.0.3", @@ -1512,8 +2012,7 @@ "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", - "dev": true + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, "node_modules/debug": { "version": "4.3.4", @@ -1562,12 +2061,29 @@ "node": ">=6.0.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/electron-to-chromium": { - "version": "1.4.537", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.537.tgz", - "integrity": "sha512-W1+g9qs9hviII0HAwOdehGYkr+zt7KKdmCcJcjH0mYg6oL8+ioT3Skjmt7BLoAQqXhjf40AXd+HlR4oAWMlXjA==", + "version": "1.4.576", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.576.tgz", + "integrity": "sha512-yXsZyXJfAqzWk1WKryr0Wl0MN2D47xodPvEEwlVePBnhU5E7raevLQR+E6b9JAD3GfL/7MbAL9ZtWQQPcLx7wA==", "dev": true }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/esbuild": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", @@ -1615,27 +2131,30 @@ } }, "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "engines": { - "node": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", - "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", + "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.50.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -1690,9 +2209,9 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.3.tgz", - "integrity": "sha512-Hh0wv8bUNY877+sI0BlCUlsS0TYYQqvzEwJsJJPM2WF4RnTStSnSR3zdJYa2nPOJgg3UghXi54lVyMSmpCalzA==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.4.tgz", + "integrity": "sha512-eD83+65e8YPVg6603Om2iCIwcQJf/y7++MWm4tACtEswFLYMwxwVWAfwN+e19f5Ad/FOyyNg9Dfi5lXhH3Y3rA==", "dev": true, "peerDependencies": { "eslint": ">=7" @@ -1775,22 +2294,10 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint/node_modules/globals": { - "version": "13.22.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", - "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -1961,6 +2468,11 @@ "node": ">=8" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -1978,12 +2490,12 @@ } }, "node_modules/flat-cache": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", - "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", + "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", "dev": true, "dependencies": { - "flatted": "^3.2.7", + "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" }, @@ -2017,6 +2529,14 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2097,11 +2617,34 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, "engines": { "node": ">=4" } }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -2121,7 +2664,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -2158,6 +2700,11 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2170,6 +2717,17 @@ "node": ">=8" } }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2250,6 +2808,11 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2275,9 +2838,9 @@ } }, "node_modules/keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "dependencies": { "json-buffer": "3.0.1" @@ -2296,6 +2859,11 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2416,6 +2984,14 @@ "node": ">=0.10.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2476,7 +3052,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -2484,6 +3059,23 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2511,11 +3103,15 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, "engines": { "node": ">=8" } @@ -2575,10 +3171,25 @@ "node": ">= 0.8.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "engines": { "node": ">=6" @@ -2627,6 +3238,11 @@ "react": "^18.2.0" } }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -2636,6 +3252,21 @@ "node": ">=0.10.0" } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -2648,11 +3279,31 @@ "node": ">=8.10.0" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -2722,9 +3373,9 @@ } }, "node_modules/sass": { - "version": "1.68.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.68.0.tgz", - "integrity": "sha512-Lmj9lM/fef0nQswm1J2HJcEsBUba4wgNx2fea6yJHODREoMFnwRpZydBnX/RjyXw2REIwdkbqE4hrTo4qfDBUA==", + "version": "1.69.5", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz", + "integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -2809,6 +3460,14 @@ "node": ">=8" } }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", @@ -2842,11 +3501,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -2854,6 +3517,17 @@ "node": ">=4" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -2864,7 +3538,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, "engines": { "node": ">=4" } @@ -2930,6 +3603,12 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", @@ -2970,9 +3649,9 @@ } }, "node_modules/vite": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", - "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz", + "integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==", "dev": true, "dependencies": { "esbuild": "^0.18.10", @@ -3051,6 +3730,14 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index d3649c3..576265c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,11 @@ "preview": "vite preview" }, "dependencies": { + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@fontsource/roboto": "^5.0.8", + "@mui/icons-material": "^5.14.16", + "@mui/material": "^5.14.16", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/frontend/src/components/Container/index.scss b/frontend/src/components/Container/index.scss deleted file mode 100644 index c891353..0000000 --- a/frontend/src/components/Container/index.scss +++ /dev/null @@ -1,11 +0,0 @@ -main { - max-width: 1280px; - width: 60%; - margin: auto; - display: grid; - gap: 50px; - - @media screen and (max-width: 900px){ - width: 95%; - } -} \ No newline at end of file diff --git a/frontend/src/components/Container/index.tsx b/frontend/src/components/Container/index.tsx deleted file mode 100644 index 63e9fff..0000000 --- a/frontend/src/components/Container/index.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { ReactNode } from 'react'; - -import "./index.scss" - -interface ContainerProps { - children: ReactNode -} -export function Container({children}: ContainerProps) { - return ( - <main> - {children} - </main> - ); -} \ No newline at end of file diff --git a/frontend/src/components/Footer/index.scss b/frontend/src/components/Footer/index.scss deleted file mode 100644 index 243b678..0000000 --- a/frontend/src/components/Footer/index.scss +++ /dev/null @@ -1,9 +0,0 @@ -@use "sass:color"; -@use '@/styles/global' as *; - -footer { - background-color: color.adjust($background-color, $whiteness: 3%); - margin-top: 5vh; - text-align: center; - padding: 20px 50px; -} \ No newline at end of file diff --git a/frontend/src/components/Footer/index.tsx b/frontend/src/components/Footer/index.tsx index 1e3ae94..5c211db 100644 --- a/frontend/src/components/Footer/index.tsx +++ b/frontend/src/components/Footer/index.tsx @@ -1,13 +1,17 @@ -import "./index.scss" +import { ReactNode } from 'react'; +import Paper from '@mui/material/Paper'; +import Typography from '@mui/material/Typography'; interface FooterProps { - version: string + content:ReactNode } -export function Footer({version}:FooterProps) { +export function Footer({content}:FooterProps) { return ( - <footer> - <a href={`https://github.com/MainKronos/Sonarr-AnimeDownloader/releases/tag/${version}`} target="_blank">Ver. {version}</a> - </footer> + <Paper square elevation={1} sx={{ position: 'fixed', bottom: 0, left: 0, right: 0, padding: '16px' }}> + <Typography variant="body1" noWrap sx={{flex: 1, display: 'flex', justifyContent: 'center'}}> + {content} + </Typography> + </Paper> ); -} \ No newline at end of file +}; \ No newline at end of file diff --git a/frontend/src/components/Header/index.scss b/frontend/src/components/Header/index.scss deleted file mode 100644 index 381a57e..0000000 --- a/frontend/src/components/Header/index.scss +++ /dev/null @@ -1,50 +0,0 @@ -@use "sass:color"; -@use '@/styles/global' as *; - -header { - background-color: color.adjust($background-color, $whiteness: 8%); - padding: 15px 5vw; - margin-bottom: 5vh; - box-shadow: 0 1px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12), 0 3px 1px -2px rgba(0,0,0,.2); - - > h1 { - text-align: center; - margin: 10px; - } -} - -#donate{ - @keyframes HeartBeat { - 0% { - transform: scale(0.8); - } - 5% { - transform: scale(0.9); - } - 10% { - transform: scale(0.8); - } - 15% { - transform: scale(1); - } - 50% { - transform: scale(0.8); - } - 100% { - transform: scale(0.8); - } - } - - transition: background-color, color 0.1s; - animation: HeartBeat 1.5s infinite; - position: absolute; - right: 20px; - top: 20px; - text-shadow: 0 0 10px $base-color; - text-decoration: none; - - &:hover { - color: $base-color; - } - -} \ No newline at end of file diff --git a/frontend/src/components/Header/index.tsx b/frontend/src/components/Header/index.tsx index cb76432..66383d5 100644 --- a/frontend/src/components/Header/index.tsx +++ b/frontend/src/components/Header/index.tsx @@ -1,22 +1,24 @@ -import { ReactNode } from "react" -import { Icon } from ".." -import "./index.scss" +import { ReactNode } from 'react'; +import AppBar from '@mui/material/AppBar'; +import Toolbar from '@mui/material/Toolbar'; +import Typography from '@mui/material/Typography'; interface HeaderProps { title: string, children?: ReactNode } -export function Header(props: HeaderProps) { +export function Header({ title, children }: HeaderProps) { return ( - <header> - <h1>{props.title}</h1> + <AppBar position="fixed"> + <Toolbar sx={{ padding: "20px" }}> + {children} - <a id='donate' href="https://github.com/sponsors/MainKronos" target="_blank"> - <Icon icon="favorite"/> - </a> + <Typography variant="h4" textAlign='center' sx={{ flex: 1, fontWeight: 500 }} component="h1"> + {title} + </Typography> - {props.children} - </header> - ) -} \ No newline at end of file + </Toolbar> + </AppBar> + ); +}; \ No newline at end of file diff --git a/frontend/src/components/Icon/index.scss b/frontend/src/components/Icon/index.scss deleted file mode 100644 index 9355b22..0000000 --- a/frontend/src/components/Icon/index.scss +++ /dev/null @@ -1 +0,0 @@ -@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@24,400,1,0'); diff --git a/frontend/src/components/Icon/index.tsx b/frontend/src/components/Icon/index.tsx deleted file mode 100644 index 7e584b5..0000000 --- a/frontend/src/components/Icon/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import "./index.scss" - -interface IconProps { - icon: string -} - -export function Icon({icon}: IconProps) { - return <i className="material-symbols-rounded">{icon}</i>; -} \ No newline at end of file diff --git a/frontend/src/components/Navigator/index.scss b/frontend/src/components/Navigator/index.scss deleted file mode 100644 index b661801..0000000 --- a/frontend/src/components/Navigator/index.scss +++ /dev/null @@ -1,84 +0,0 @@ -@use "sass:color"; -@use '@/styles/global' as *; - -nav { - height: 100%; - width: 0; - position: fixed; - z-index: 2; - top: 0; - left: 0; - background-color: color.adjust($background-color, $whiteness: 8%); - overflow-x: hidden; - padding-top: 60px; - transition: 0.5s; - box-shadow: 0 2px 2px 0 rgb(0 0 0 / 14%), 0 3px 1px -2px rgb(0 0 0 / 12%), 0 1px 5px 0 rgb(0 0 0 / 20%); - - > div:nth-child(1) { - position: fixed; - top: 0; - left: 0; - width: 0; - height: 100%; - background-color: rgba(0,0,0,0); - transition: background-color 0.5s; - } - - &.active { - width: 200px; - - > div:nth-child(1) { - width: 100%; - background-color: rgba(0,0,0,0.2); - } - } - - > div:nth-child(2) { - position: relative; - top: 0; - left: 0; - padding: 10px; - text-align: center; - - > a { - display: block; - color: rgba(255,255,255,0.5); - text-decoration: none; - width: max-content; - margin: 10px; - cursor: pointer; - padding: 5px; - border-bottom: 1.5px solid rgba(0,0,0,0.4); - - &:active, &:visited { - color: rgba(255,255,255,0.5); - } - - &:hover { - color: $base-color; - } - } - } - - - + button { - background-color: transparent; - border: 0; - margin: 10px; - color: rgba(255,255,255,0.60); - font-size: 1.5rem; - font-weight: 400; - padding: 0; - position: absolute; - top: 20px; - left: 20px; - - cursor: pointer; - - &:hover { - color: $base-color; - } - } - - -} \ No newline at end of file diff --git a/frontend/src/components/Navigator/index.tsx b/frontend/src/components/Navigator/index.tsx deleted file mode 100644 index 4d2123a..0000000 --- a/frontend/src/components/Navigator/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { ReactElement, ReactNode, useState } from "react"; - - -import "./index.scss" -import { Icon } from ".."; - -interface NavigatorProps { - children?: ReactNode -} - -export function Navigator(props: NavigatorProps){ - - const [active, setActive] = useState(false); - - return ( - <> - <nav - className={active ? 'active':''} - > - <div onClick={() => setActive(!active)}></div> - <div> - {props.children} - </div> - </nav> - <button onClick={() => setActive(!active)}> - <Icon icon="menu"/> - </button> - </> - ); -} \ No newline at end of file diff --git a/frontend/src/components/Table/index.scss b/frontend/src/components/Table/index.scss deleted file mode 100644 index 2ca7ec2..0000000 --- a/frontend/src/components/Table/index.scss +++ /dev/null @@ -1,73 +0,0 @@ -@use "sass:color"; -@use '@/styles/global' as *; - -details{ - user-select: none; - background-color: color.adjust($background-color, $whiteness: 8%); - margin: 0 30px; - transition: margin 0.1s; -} - -details[open]{ - border-bottom: 1px solid $background-color; - margin: 30px 0; -} -summary{ - cursor: pointer; - color: $text-color; - font-size: 1rem; - font-weight: 400; - padding: 15px; - border-bottom: 1px solid $background-color; - transition: box-shadow 0.5s; -} -details[open] > summary{ - box-shadow: 0px 1px 2px -1px black; -} -summary::marker{content: none;} - -summary{ - color: $text-color; - transition: color 0.2s; -} -summary:hover{ - color: $base-color; -} -summary > span{ - // font-size: 1.5rem; - padding: 0 5px; - vertical-align: 5px; -} - -/* blocco di tutte le tabs */ -.tabs{ - display: flex; - justify-content: flex-start; - width: 100%; - margin: 0 auto; - border-bottom: 2px solid rgba(255,255,255,0.1); -} -.tab{ - color: $text-color; - font-size: 16px; - padding: 10px 50px; - cursor: pointer; - border-bottom: 2px solid rgba(255,255,255,0); - margin: 0 0 -2px 0; - transition-property: color, border; - transition-duration: 0.3s; -} -.tab.active{ - color: $base-color; - border-bottom: 2px solid $base-color; -} -.tab-content{ - display: none; -} -.tab-content.active{ - display: block; -} - -.tab-content > a{ - display: block; -} \ No newline at end of file diff --git a/frontend/src/components/Table/index.tsx b/frontend/src/components/Table/index.tsx deleted file mode 100644 index 7c0f983..0000000 --- a/frontend/src/components/Table/index.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { API } from "@/utils/API"; -import { useEffect, useState } from "react"; - -import "./index.scss" -import { Icon } from ".."; - -//https://github.com/MainKronos/Sonarr-AnimeDownloader/blob/1.8.0/documentation/other/react/table.js - -interface TableProps { - api: API -} -export function Table({api}:TableProps) { - - const [entries, setEntries] = useState([] as TableRowProps[]); - - useEffect(() => { - api.getTable() - .then(res => setEntries(res)) - }, [api]) - - return ( - <div> - {entries.map(({title, seasons, absolute}, index) => ( - <TableRow - title={title} - seasons={seasons} - absolute={absolute} - key={index} - /> - ))} - </div> - ); -} - -interface TableRowProps { - title: string, - seasons: { - [num:string]: string[] - }, - absolute: boolean -} -function TableRow({title, seasons, absolute}: TableRowProps){ - return ( - <details> - - <summary> - <Icon icon="movie"/> - <span>{title}</span> - {absolute && ( <span className="badge">absolute</span>)} - </summary> - - <TableRowBody - seasons={seasons} - /> - </details> - ); -} - -interface TableRowBodyProps { - seasons: { - [num:string]: string[] - } -} -function TableRowBody({seasons}: TableRowBodyProps){ - - const [active_tab, setActive_tab] = useState(0); - - return ( - <div className="content"> - <div className="tabs"> - {Object.keys(seasons).map((season, index) => - <Tab - title={season} - active={index==active_tab} - key={index} - onClick={()=>setActive_tab(index)} - /> - )} - </div> - <div className="tabs-content"> - {Object.keys(seasons).map((season, index) => - <TabContent - links={seasons[season]} - active={index==active_tab} - key={index} - /> - )} - </div> - </div> - ); -} - -interface TabProps { - title: string, - active: boolean, - onClick: React.MouseEventHandler<HTMLAnchorElement> -} -function Tab({title, active, onClick}: TabProps) { - return ( - <a className={active ? "tab active" : "tab"} onClick={onClick}> - {title.toUpperCase()} - </a> - ); -} - -interface TabContentProps { - links: string[], - active: boolean -} -function TabContent({links, active}: TabContentProps){ - return ( - <div className={active ? "tab-content active" : "tab-content"}> - {links.map(link => - <a href={link} target="_blank" key={link}> - {link} - </a> - )} - </div> - ); -} \ No newline at end of file diff --git a/frontend/src/components/index.tsx b/frontend/src/components/index.tsx index d1257b3..43ae63f 100644 --- a/frontend/src/components/index.tsx +++ b/frontend/src/components/index.tsx @@ -1,6 +1,2 @@ -export {Header} from "./Header"; -export {Navigator} from "./Navigator" -export {Table} from './Table' export {Footer} from './Footer' -export {Container} from './Container' -export {Icon} from './Icon' \ No newline at end of file +export {Header} from './Header' \ No newline at end of file diff --git a/frontend/src/pages/home/App.tsx b/frontend/src/pages/home/App.tsx index 3b1853b..378781b 100644 --- a/frontend/src/pages/home/App.tsx +++ b/frontend/src/pages/home/App.tsx @@ -1,31 +1,140 @@ -import { useEffect, useState } from "react"; -import { Container, Footer, Header, Navigator, Table } from "@/components"; +// import * from "react"; +import { useState, useEffect } from 'react'; -import '@/styles/base.scss' -import { API } from "@/utils/API"; +import { Header, Footer } from '@/components'; + +import '@fontsource/roboto/300.css'; +import '@fontsource/roboto/400.css'; +import '@fontsource/roboto/500.css'; +import '@fontsource/roboto/700.css'; + +import IconButton from '@mui/material/IconButton'; +import Drawer from '@mui/material/Drawer'; +import List from '@mui/material/List'; +import ListItemButton from '@mui/material/ListItemButton'; +import Container from '@mui/material/Container'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import Skeleton from '@mui/material/Skeleton'; +import Box from '@mui/material/Box'; +import Link from '@mui/material/Link'; +import Accordion from '@mui/material/Accordion'; +import AccordionSummary from '@mui/material/AccordionSummary'; +import AccordionDetails from '@mui/material/AccordionDetails'; +import Typography from '@mui/material/Typography'; +import Tabs from '@mui/material/Tabs'; +import Tab from '@mui/material/Tab'; +import Divider from '@mui/material/Divider'; + +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import { CssBaseline } from '@mui/material'; + +import MenuIcon from '@mui/icons-material/Menu'; +import SettingsIcon from '@mui/icons-material/Settings'; +import InboxIcon from '@mui/icons-material/Inbox'; +import ArticleIcon from '@mui/icons-material/Article'; + +import { API } from '@/utils/API'; + +import type {SerieTableEntry, SeasonsTableEntry} from '@/utils/types'; export default function App() { - const api = new API(new URL(BACKEND)); + const darkTheme = createTheme({ palette: { mode: 'dark' } }); + + const [drawerOpen, setDrawerOpen] = useState(false); const [version, setVersion] = useState(''); + const api = new API('http://127.0.0.1:5000'); + + useEffect(() => { + api.getVersion().then(res => setVersion(res)); + }, []); + + return ( + <ThemeProvider theme={darkTheme}> + <CssBaseline /> + + <Header title='Tabella Di Conversione'> + <IconButton + size="large" + sx={{ position: "absolute" }} + onClick={() => setDrawerOpen(true)} + > + <MenuIcon fontSize='medium' /> + </IconButton> + </Header> + + <Drawer anchor="left" open={drawerOpen} onClose={() => setDrawerOpen(false)}> + <Box sx={{ width: 250, paddingTop: 10 }}> + <List component="nav"> + <ListItemButton selected={true}> + <ListItemIcon> + <InboxIcon /> + </ListItemIcon> + <Link href="index.html" color="inherit" underline='none'>Index</Link> + </ListItemButton> + <ListItemButton> + <ListItemIcon> + <SettingsIcon /> + </ListItemIcon> + <Link href="settings.html" color="inherit" underline='none'>Settings</Link> + </ListItemButton> + <ListItemButton> + <ListItemIcon> + <ArticleIcon /> + </ListItemIcon> + <Link href="log.html" color="inherit" underline='none'>Logs</Link> + + </ListItemButton> + </List> + </Box> + </Drawer> + + <Container maxWidth="md" sx={{ py: 20 }}> + <SerieTable api={api} /> + </Container> + + <Footer content={version ? "Ver. " + version : <Skeleton width="80px" />} /> + + </ThemeProvider> + ); +} + +function SerieTable({ api }: { api: API }) { + + const [data, setData] = useState([] as SerieTableEntry[]); + useEffect(() => { - api.getVersion() - .then(res => setVersion(res)); - }, [api]); + api.getTable().then(res => setData(res)); + }, []) + + return (<> + {data.map((serie, index) => ( + <Accordion key={index} component="details"> + <AccordionSummary component="summary"> + <Typography>{serie.title}</Typography> + </AccordionSummary> + <Divider /> + <AccordionDetails> + <SeasonTabs api={api} season={serie.seasons} /> + </AccordionDetails> + </Accordion> + ))} + </>); +} + +function SeasonTabs({ api, season }: { api: API, season: SeasonsTableEntry }) { + const [tab, setTab] = useState(0); return (<> - <Header title="Tabella Di Conversione"> - <Navigator> - <a>Home</a> - <a href="settings.html">Settings</a> - <a href="log.html">Log</a> - </Navigator> - </Header> - <Container> - <Table api={api}/> - </Container> - <Footer version={version}/> - </>) -} \ No newline at end of file + <Tabs value={tab} onChange={(_, val) => setTab(val)} aria-label="basic tabs example"> + {Object.entries(season).map(([key, _]) => ( + <Tab label={key} key={key}/> + ))} + </Tabs> + {Object.values(season)[tab].map((val:string) => ( + <Link href="#" color="inherit" key={val}>{val}</Link> + ))} + </>); +} \ No newline at end of file diff --git a/frontend/src/styles/_global.scss b/frontend/src/styles/_global.scss deleted file mode 100644 index e897b75..0000000 --- a/frontend/src/styles/_global.scss +++ /dev/null @@ -1,3 +0,0 @@ -$base-color: #9FA8DA !default; -$background-color: #121212 !default; -$text-color: #bababa !default; \ No newline at end of file diff --git a/frontend/src/styles/base.scss b/frontend/src/styles/base.scss deleted file mode 100644 index 5304cdd..0000000 --- a/frontend/src/styles/base.scss +++ /dev/null @@ -1,35 +0,0 @@ -@use "sass:color"; -@use 'global' as *; - -@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap'); - -html { - color-scheme: dark; -} - -body { - margin: 0; - font-family: 'Roboto', sans-serif; - background-color: $background-color; - color: $text-color; -} - -#root { - min-height: 100vh; - display: flex; - flex-direction: column; - justify-content: space-between; -} - -a{ - color: rgba(255,255,255,0.5); - text-decoration: none; - width: fit-content; - margin: 10px; -} -a:active, a:visited{ - color: rgba(255,255,255,0.5); -} -a:hover{ - color: $base-color; -} \ No newline at end of file diff --git a/frontend/src/utils/API.ts b/frontend/src/utils/API.ts index 15688ef..de641e8 100644 --- a/frontend/src/utils/API.ts +++ b/frontend/src/utils/API.ts @@ -1,7 +1,9 @@ +import type {SerieTableEntry} from './types'; + export class API { backend: string; - constructor(backend:URL) { - this.backend = backend.origin + '/api'; + constructor(backend:string) { + this.backend = backend + '/api'; } async getVersion():Promise<string>{ @@ -10,7 +12,7 @@ export class API { return data.version; } - async getTable():Promise<any[]>{ + async getTable():Promise<SerieTableEntry[]>{ const res = await fetch(this.backend + '/table'); return await res.json() } diff --git a/frontend/src/utils/types.ts b/frontend/src/utils/types.ts new file mode 100644 index 0000000..d5d0cfe --- /dev/null +++ b/frontend/src/utils/types.ts @@ -0,0 +1,9 @@ +export interface SerieTableEntry { + title: string, + absolute: boolean, + seasons: SeasonsTableEntry +} + +export interface SeasonsTableEntry { + [season:string]: string[] +} \ No newline at end of file diff --git a/src/components/api/__init__.py b/src/components/api/__init__.py index 0e93a6c..013e21d 100644 --- a/src/components/api/__init__.py +++ b/src/components/api/__init__.py @@ -7,6 +7,8 @@ from .routes.Settings import Settings from .routes.Tags import Tags +import time + def API(core:Core) -> APIFlask: app = APIFlask( __name__, @@ -29,6 +31,7 @@ def get_version(): """ Restituisce il numero di versione. """ + # time.sleep(5) return {"version": core.version} api.register_blueprint(Table(core)) From 2f71adae5b80e36aa1d2a170c93d2cb108c1cfee Mon Sep 17 00:00:00 2001 From: MainKronos <lorenzo.chesi@live.it> Date: Fri, 10 Nov 2023 12:11:22 +0100 Subject: [PATCH 12/39] index frontend --- frontend/index.html | 2 +- frontend/src/components/Footer/index.tsx | 2 +- frontend/src/components/Navigator/index.tsx | 48 +++++++ frontend/src/components/index.tsx | 5 +- frontend/src/pages/home/App.tsx | 136 ++++++++++---------- frontend/src/utils/globals.ts | 35 +++++ 6 files changed, 159 insertions(+), 69 deletions(-) create mode 100644 frontend/src/components/Navigator/index.tsx create mode 100644 frontend/src/utils/globals.ts diff --git a/frontend/index.html b/frontend/index.html index bdf370b..a4256c1 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,5 +1,5 @@ <!doctype html> -<html lang="it"> +<html lang="it" style="color-scheme: dark;"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="initial-scale=1, width=device-width" /> diff --git a/frontend/src/components/Footer/index.tsx b/frontend/src/components/Footer/index.tsx index 5c211db..8e05ecb 100644 --- a/frontend/src/components/Footer/index.tsx +++ b/frontend/src/components/Footer/index.tsx @@ -8,7 +8,7 @@ interface FooterProps { export function Footer({content}:FooterProps) { return ( - <Paper square elevation={1} sx={{ position: 'fixed', bottom: 0, left: 0, right: 0, padding: '16px' }}> + <Paper square elevation={1} sx={{ position: 'fixed', bottom: 0, left: 0, right: 0, padding: '16px', borderTop: '1px solid', borderColor: 'divider' }}> <Typography variant="body1" noWrap sx={{flex: 1, display: 'flex', justifyContent: 'center'}}> {content} </Typography> diff --git a/frontend/src/components/Navigator/index.tsx b/frontend/src/components/Navigator/index.tsx new file mode 100644 index 0000000..34c9479 --- /dev/null +++ b/frontend/src/components/Navigator/index.tsx @@ -0,0 +1,48 @@ +import Drawer from '@mui/material/Drawer'; +import List from '@mui/material/List'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import Box from '@mui/material/Box'; +import Link from '@mui/material/Link'; + +import SettingsIcon from '@mui/icons-material/Settings'; +import InboxIcon from '@mui/icons-material/Inbox'; +import ArticleIcon from '@mui/icons-material/Article'; + + +interface NavigatorProps { + drawerState: boolean, + setDrawerState: (state:boolean) => void, +} + +export function Navigator({drawerState, setDrawerState}:NavigatorProps) { + + function closeDrawer() {setDrawerState(false)} + + return ( + <Drawer anchor="left" open={drawerState} onClose={(closeDrawer)}> + <Box sx={{ width: 250, paddingTop: 10 }}> + <List component="nav"> + <ListItemButton selected={true}> + <ListItemIcon> + <InboxIcon /> + </ListItemIcon> + <Link href="index.html" color="inherit" underline='none'>Index</Link> + </ListItemButton> + <ListItemButton> + <ListItemIcon> + <SettingsIcon /> + </ListItemIcon> + <Link href="settings.html" color="inherit" underline='none'>Settings</Link> + </ListItemButton> + <ListItemButton> + <ListItemIcon> + <ArticleIcon /> + </ListItemIcon> + <Link href="log.html" color="inherit" underline='none'>Logs</Link> + </ListItemButton> + </List> + </Box> + </Drawer> + ) +} \ No newline at end of file diff --git a/frontend/src/components/index.tsx b/frontend/src/components/index.tsx index 43ae63f..ff78911 100644 --- a/frontend/src/components/index.tsx +++ b/frontend/src/components/index.tsx @@ -1,2 +1,3 @@ -export {Footer} from './Footer' -export {Header} from './Header' \ No newline at end of file +export {Footer} from './Footer'; +export {Header} from './Header'; +export {Navigator} from './Navigator'; \ No newline at end of file diff --git a/frontend/src/pages/home/App.tsx b/frontend/src/pages/home/App.tsx index 378781b..49abfd6 100644 --- a/frontend/src/pages/home/App.tsx +++ b/frontend/src/pages/home/App.tsx @@ -1,7 +1,7 @@ // import * from "react"; import { useState, useEffect } from 'react'; -import { Header, Footer } from '@/components'; +import { Header, Footer, Navigator } from '@/components'; import '@fontsource/roboto/300.css'; import '@fontsource/roboto/400.css'; @@ -9,14 +9,13 @@ import '@fontsource/roboto/500.css'; import '@fontsource/roboto/700.css'; import IconButton from '@mui/material/IconButton'; -import Drawer from '@mui/material/Drawer'; -import List from '@mui/material/List'; -import ListItemButton from '@mui/material/ListItemButton'; + import Container from '@mui/material/Container'; -import ListItemIcon from '@mui/material/ListItemIcon'; import Skeleton from '@mui/material/Skeleton'; -import Box from '@mui/material/Box'; -import Link from '@mui/material/Link'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemText from '@mui/material/ListItemText'; import Accordion from '@mui/material/Accordion'; import AccordionSummary from '@mui/material/AccordionSummary'; import AccordionDetails from '@mui/material/AccordionDetails'; @@ -24,26 +23,32 @@ import Typography from '@mui/material/Typography'; import Tabs from '@mui/material/Tabs'; import Tab from '@mui/material/Tab'; import Divider from '@mui/material/Divider'; +import Paper from '@mui/material/Paper'; +import Button from '@mui/material/Button'; +import Chip from '@mui/material/Chip'; +import Stack from '@mui/material/Stack'; -import { ThemeProvider, createTheme } from '@mui/material/styles'; + +import { ThemeProvider } from '@mui/material/styles'; import { CssBaseline } from '@mui/material'; import MenuIcon from '@mui/icons-material/Menu'; -import SettingsIcon from '@mui/icons-material/Settings'; -import InboxIcon from '@mui/icons-material/Inbox'; -import ArticleIcon from '@mui/icons-material/Article'; +import MovieIcon from '@mui/icons-material/Movie'; +import AddIcon from '@mui/icons-material/Add'; +import DownloadIcon from '@mui/icons-material/Download'; +import UploadIcon from '@mui/icons-material/Upload'; import { API } from '@/utils/API'; - -import type {SerieTableEntry, SeasonsTableEntry} from '@/utils/types'; +import { theme } from '@/utils/globals' +import type { SerieTableEntry, SeasonsTableEntry } from '@/utils/types'; export default function App() { - const darkTheme = createTheme({ palette: { mode: 'dark' } }); - - const [drawerOpen, setDrawerOpen] = useState(false); + const [drawerState, setDrawerState] = useState(false); const [version, setVersion] = useState(''); + function openDrawer() { setDrawerState(true) } + const api = new API('http://127.0.0.1:5000'); useEffect(() => { @@ -51,48 +56,22 @@ export default function App() { }, []); return ( - <ThemeProvider theme={darkTheme}> + <ThemeProvider theme={theme}> <CssBaseline /> <Header title='Tabella Di Conversione'> <IconButton size="large" sx={{ position: "absolute" }} - onClick={() => setDrawerOpen(true)} + onClick={openDrawer} > <MenuIcon fontSize='medium' /> </IconButton> </Header> - <Drawer anchor="left" open={drawerOpen} onClose={() => setDrawerOpen(false)}> - <Box sx={{ width: 250, paddingTop: 10 }}> - <List component="nav"> - <ListItemButton selected={true}> - <ListItemIcon> - <InboxIcon /> - </ListItemIcon> - <Link href="index.html" color="inherit" underline='none'>Index</Link> - </ListItemButton> - <ListItemButton> - <ListItemIcon> - <SettingsIcon /> - </ListItemIcon> - <Link href="settings.html" color="inherit" underline='none'>Settings</Link> - </ListItemButton> - <ListItemButton> - <ListItemIcon> - <ArticleIcon /> - </ListItemIcon> - <Link href="log.html" color="inherit" underline='none'>Logs</Link> - - </ListItemButton> - </List> - </Box> - </Drawer> - - <Container maxWidth="md" sx={{ py: 20 }}> - <SerieTable api={api} /> - </Container> + <Navigator drawerState={drawerState} setDrawerState={setDrawerState} /> + + <SerieTable api={api} /> <Footer content={version ? "Ver. " + version : <Skeleton width="80px" />} /> @@ -108,19 +87,39 @@ function SerieTable({ api }: { api: API }) { api.getTable().then(res => setData(res)); }, []) - return (<> - {data.map((serie, index) => ( - <Accordion key={index} component="details"> - <AccordionSummary component="summary"> - <Typography>{serie.title}</Typography> - </AccordionSummary> - <Divider /> - <AccordionDetails> - <SeasonTabs api={api} season={serie.seasons} /> - </AccordionDetails> - </Accordion> - ))} - </>); + return ( + <Container maxWidth="md" sx={{ py: 20 }}> + <Paper> + <Button aria-label="add" size="large" variant="text" fullWidth={true}> + <AddIcon /> + </Button> + </Paper> + + {data.map((serie, index) => ( + <Accordion key={index}> + <AccordionSummary expandIcon={<MovieIcon />}> + <Typography>{serie.title}</Typography> {serie.absolute && <Chip label="absolute" color="primary" size="small"/>} + </AccordionSummary> + <Divider /> + <AccordionDetails> + <SeasonTabs api={api} season={serie.seasons} /> + </AccordionDetails> + </Accordion> + ))} + + <Paper> + <Stack direction="row" > + <Button aria-label="download" size="large" variant="text" fullWidth={true}> + <DownloadIcon /> + </Button> + <Button aria-label="upload" size="large" variant="text" fullWidth={true}> + <UploadIcon /> + </Button> + </Stack> + </Paper> + + </Container> + ); } @@ -128,13 +127,20 @@ function SeasonTabs({ api, season }: { api: API, season: SeasonsTableEntry }) { const [tab, setTab] = useState(0); return (<> - <Tabs value={tab} onChange={(_, val) => setTab(val)} aria-label="basic tabs example"> + <Tabs value={tab} onChange={(_, val) => val && setTab(val)} aria-label="basic tabs example"> {Object.entries(season).map(([key, _]) => ( - <Tab label={key} key={key}/> + <Tab label={key} key={key} /> ))} + <Tab icon={<AddIcon />} aria-label="add" value={null}/> </Tabs> - {Object.values(season)[tab].map((val:string) => ( - <Link href="#" color="inherit" key={val}>{val}</Link> - ))} + <List dense={true}> + {Object.values(season)[tab].map((val: string) => ( + <ListItem key={val}> + <ListItemButton component="a" href={val} target="_blank"> + <ListItemText primary={val} /> + </ListItemButton> + </ListItem> + ))} + </List> </>); } \ No newline at end of file diff --git a/frontend/src/utils/globals.ts b/frontend/src/utils/globals.ts new file mode 100644 index 0000000..62d95c3 --- /dev/null +++ b/frontend/src/utils/globals.ts @@ -0,0 +1,35 @@ +import { createTheme } from '@mui/material/styles'; + +export const theme = createTheme({ + palette: { + mode: 'dark', + primary: { + main: '#9FA8DA', + }, + secondary: { + main: '#9FA8DA', + }, + }, + components: { + MuiAccordionSummary: { + styleOverrides: { + root: { + flexDirection: 'row-reverse', + }, + content: { + justifyContent: 'space-between' + }, + expandIconWrapper: { + marginRight: "10px", + }, + } + }, + MuiTabs: { + styleOverrides: { + root: { + borderBottom: '1px solid rgba(255, 255, 255, 0.12)', + } + } + } + } +}); \ No newline at end of file From 39c7d257b9beccb01aaf7db8c875ea79273bcee3 Mon Sep 17 00:00:00 2001 From: MainKronos <lorenzo.chesi@live.it> Date: Wed, 22 Nov 2023 17:17:28 +0100 Subject: [PATCH 13/39] #131 --- frontend/src/utils/globals.ts | 4 ++++ src/components/backend/core/Downloader.py | 11 ++++++++--- src/requirements.txt | 1 + 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/frontend/src/utils/globals.ts b/frontend/src/utils/globals.ts index 62d95c3..a5f6c37 100644 --- a/frontend/src/utils/globals.ts +++ b/frontend/src/utils/globals.ts @@ -9,6 +9,9 @@ export const theme = createTheme({ secondary: { main: '#9FA8DA', }, + text: { + primary: 'rgba(255,255,255,0.6)', + }, }, components: { MuiAccordionSummary: { @@ -21,6 +24,7 @@ export const theme = createTheme({ }, expandIconWrapper: { marginRight: "10px", + color: 'rgba(255,255,255,0.6)' }, } }, diff --git a/src/components/backend/core/Downloader.py b/src/components/backend/core/Downloader.py index 0289fc9..d5eec7d 100644 --- a/src/components/backend/core/Downloader.py +++ b/src/components/backend/core/Downloader.py @@ -4,7 +4,7 @@ from ..utility import ColoredString as cs import httpx, re, pathlib, time -import shutil +import shutil, tenacity import animeworld as aw from copy import deepcopy from functools import reduce @@ -214,7 +214,8 @@ def __moveFile(self, src:pathlib.Path, dst:pathlib.Path) -> pathlib.Path: dst = dst.joinpath(src.name) return shutil.move(src,dst) - + + @tenacity.retry(reraise=True, stop=tenacity.stop_after_attempt(3), wait=tenacity.wait_fixed(2)) def __renameFile(self, episode_id:int, serie_id:int) -> None: """ Rinomina il file seguendo la formattazione definita su Sonarr. @@ -226,5 +227,9 @@ def __renameFile(self, episode_id:int, serie_id:int) -> None: res = self.sonarr.episode(episode_id) res.raise_for_status() - file_id = res.json()["episodeFile"]["id"] + res = res.json() + + if "episodeFile" not in res: raise Exception("Episodio Non trovato") + + file_id = res["episodeFile"]["id"] self.sonarr.commandRenameFiles(serie_id,[file_id]) diff --git a/src/requirements.txt b/src/requirements.txt index 288c8d5..e7c5ce1 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -2,6 +2,7 @@ animeworld>=1.6.0 fastapi==0.103.2 uvicorn[standard] httpx +tenacity flask flask_socketio From 0aba8c17869a482e01b7aa5f35d20041c9f1f98c Mon Sep 17 00:00:00 2001 From: MainKronos <lorenzo.chesi@live.it> Date: Wed, 22 Nov 2023 19:07:25 +0100 Subject: [PATCH 14/39] Correzione dipendenze --- src/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/requirements.txt b/src/requirements.txt index e7c5ce1..0bdc7a5 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -2,6 +2,7 @@ animeworld>=1.6.0 fastapi==0.103.2 uvicorn[standard] httpx +apiflask tenacity flask From 5c257b5c4b3eef036f1a12b85d54d04d178f5d4f Mon Sep 17 00:00:00 2001 From: MainKronos <lorenzo.chesi@live.it> Date: Thu, 23 Nov 2023 11:07:52 +0100 Subject: [PATCH 15/39] #128 --- src/components/backend/connection/Sonarr.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/backend/connection/Sonarr.py b/src/components/backend/connection/Sonarr.py index eb1ccf7..20c465a 100644 --- a/src/components/backend/connection/Sonarr.py +++ b/src/components/backend/connection/Sonarr.py @@ -15,7 +15,8 @@ def __init__(self, url:str, api_key:str) -> None: base_url=f"{url}/api/v3", headers={ 'X-Api-Key':api_key - } + }, + timeout=5 ) # Controlla che il sito sia raggiungibile e che la api_key sia valida From 58ff6fab18bfbbdbf59d87c1307ff0c6c47aa670 Mon Sep 17 00:00:00 2001 From: MainKronos <lorenzo.chesi@live.it> Date: Fri, 24 Nov 2023 13:52:45 +0100 Subject: [PATCH 16/39] #131 --- src/components/backend/core/Downloader.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/backend/core/Downloader.py b/src/components/backend/core/Downloader.py index d5eec7d..b67bfd7 100644 --- a/src/components/backend/core/Downloader.py +++ b/src/components/backend/core/Downloader.py @@ -202,10 +202,9 @@ def __moveFile(self, src:pathlib.Path, dst:pathlib.Path) -> pathlib.Path: raise FileNotFoundError(src) # Controllo se la cartella di destinazione non sia una cartella windows - if isinstance(dst, pathlib.WindowsPath): - tmp = dst.as_posix() - tmp = re.sub(r"\w:","",tmp) - dst = pathlib.Path(tmp) + if re.match(r"\w:", str(dst)): + dst = pathlib.PureWindowsPath(dst).as_posix() + dst = pathlib.PosixPath(re.sub(r"\w:","",dst)) if not dst.is_dir(): # Se la cartella non esiste viene creata From f564ec2ee9b60de4b14ac69d9380d49b2d655314 Mon Sep 17 00:00:00 2001 From: MainKronos <lorenzo.chesi@live.it> Date: Fri, 24 Nov 2023 14:18:44 +0100 Subject: [PATCH 17/39] #123 --- src/components/backend/connection/ExternalDB.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/backend/connection/ExternalDB.py b/src/components/backend/connection/ExternalDB.py index 19b31eb..66dc94a 100644 --- a/src/components/backend/connection/ExternalDB.py +++ b/src/components/backend/connection/ExternalDB.py @@ -68,13 +68,14 @@ def find(self, title:str, season:int, tvdb_id:int) -> Optional[dict[str, str]]: res = aw.find(title) # Filtro i risultati - res = list(filter(lambda x: x["malId"] in mal_ids, res)) + res = list(filter(lambda x: x["malId"] in mal_ids and x['language'] == 'jp', res)) # Se non ho trovato nulla ritorno None if len(res) == 0: return None # Converto le stagioni in numeri - def conv2num(x): + def convert(x): + x["year"] = int(x["year"]) if x["season"] == 'winter': x["season"] = 0 elif x["season"] == 'spring': @@ -84,10 +85,10 @@ def conv2num(x): else: x["season"] = 3 return x - res = list(map(conv2num, res)) + res = list(map(convert, res)) # Riordino per data - res.sort(key=lambda x: (x["year"], x["season"]), reverse=True) + res.sort(key=lambda x: (x["year"], x["season"])) # Controllo se esiste la stagione if len(res) < season: return None From aa75f8587827c783bd2ce28176f8421dc2c0fe21 Mon Sep 17 00:00:00 2001 From: MainKronos <lorenzo.chesi@live.it> Date: Thu, 7 Dec 2023 11:39:38 +0100 Subject: [PATCH 18/39] #128 --- src/components/backend/connection/Sonarr.py | 3 +-- src/components/backend/core/Processor.py | 28 +++++++++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/components/backend/connection/Sonarr.py b/src/components/backend/connection/Sonarr.py index 20c465a..14a7f40 100644 --- a/src/components/backend/connection/Sonarr.py +++ b/src/components/backend/connection/Sonarr.py @@ -45,8 +45,7 @@ def wantedMissing(self, n:int=20, page:int=1) -> httpx.Response: return self.client.get("/wanted/missing", params={ "includeSeries": True, "pageSize": n, - "page": page, - "sortKey": "airDateUtc" + "page": page }) def episode(self, epId:int) -> httpx.Response: diff --git a/src/components/backend/core/Processor.py b/src/components/backend/core/Processor.py index ce31e88..dd6ea50 100644 --- a/src/components/backend/core/Processor.py +++ b/src/components/backend/core/Processor.py @@ -30,7 +30,10 @@ def getData(self) -> list: self.external.sync() # Collego gli url per il download e rimuovo le stagioni che non fanno match - missing = list(filter(self.__bindUrl, missing)) + missing = filter(self.__bindUrl, missing) + + # Riodino le stagioni e gli episodi di ciascuna serie + missing = list(map(self.__sortSerie, missing)) return missing @@ -60,6 +63,27 @@ def getAllMissing(self) -> list: ### FUNCTIONS ### + def __sortSerie(self, elem:dict) -> dict: + """ + Riordina le stagione e gli episodi di una serie. + + Args: + elem: serie da ordinare. + + Returns: + La serie ordinata + """ + + # Ordino le stagioni + elem["seasons"].sort(key=lambda x: x["number"]) + + for season in elem["seasons"]: + # ordino gli episodi + season["episodes"].sort(key=lambda x: (x['seasonNumber'], x['episodeNumber'])) + + return elem + + def __filter(self, elem:dict) -> bool: """ Filtra le serie e stagioni non valide. @@ -137,7 +161,7 @@ def __reduce(self, base:list, elem:dict): season["urls"] = [] season["episodes"] = [] - # Aggiungom l'episodio + # Aggiungo l'episodio episode = self.__extractEpisode(elem) season["episodes"].append(episode) From 20765209c6daa4df9592a617e97e1d65c29c0cfe Mon Sep 17 00:00:00 2001 From: MainKronos <lorenzo.chesi@live.it> Date: Thu, 21 Dec 2023 20:00:58 +0100 Subject: [PATCH 19/39] up --- frontend/package-lock.json | 852 +++++++------------ frontend/package.json | 7 +- frontend/src/components/Container/index.tsx | 30 + frontend/src/components/Container/style.scss | 76 ++ frontend/src/components/Footer/index.tsx | 17 - frontend/src/components/Header/index.tsx | 24 - frontend/src/components/Navigator/index.tsx | 58 +- frontend/src/components/Navigator/style.scss | 74 ++ frontend/src/components/index.tsx | 7 +- frontend/src/pages/home/App.tsx | 143 +--- frontend/src/pages/log/App.tsx | 7 +- frontend/src/pages/settings/App.tsx | 8 +- frontend/src/styles/_index.scss | 73 ++ src/dev_api.py | 6 - 14 files changed, 606 insertions(+), 776 deletions(-) create mode 100644 frontend/src/components/Container/index.tsx create mode 100644 frontend/src/components/Container/style.scss delete mode 100644 frontend/src/components/Footer/index.tsx delete mode 100644 frontend/src/components/Header/index.tsx create mode 100644 frontend/src/components/Navigator/style.scss create mode 100644 frontend/src/styles/_index.scss diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5676bfa..0bfe91b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,12 +11,13 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@fontsource/roboto": "^5.0.8", - "@mui/icons-material": "^5.14.16", - "@mui/material": "^5.14.16", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { + "@fortawesome/fontawesome-svg-core": "^6.5.1", + "@fortawesome/free-solid-svg-icons": "^6.5.1", + "@fortawesome/react-fontawesome": "^0.2.0", "@types/node": "^20.8.0", "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", @@ -26,6 +27,8 @@ "eslint": "^8.45.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3", + "react-contexify": "^6.0.0", + "react-toastify": "^9.1.3", "sass": "^1.68.0", "typescript": "^5.0.2", "vite": "^4.4.5" @@ -54,11 +57,11 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dependencies": { - "@babel/highlight": "^7.22.13", + "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" }, "engines": { @@ -66,30 +69,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", - "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", - "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.6.tgz", + "integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helpers": "^7.23.2", - "@babel/parser": "^7.23.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.6", + "@babel/parser": "^7.23.6", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0", + "@babel/traverse": "^7.23.6", + "@babel/types": "^7.23.6", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -120,12 +123,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "dev": true, "dependencies": { - "@babel/types": "^7.23.0", + "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -135,14 +138,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -205,9 +208,9 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", - "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", @@ -257,9 +260,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", "engines": { "node": ">=6.9.0" } @@ -273,32 +276,32 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", - "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", - "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz", + "integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==", "dev": true, "dependencies": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0" + "@babel/traverse": "^7.23.6", + "@babel/types": "^7.23.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", @@ -309,9 +312,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -321,9 +324,9 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.22.5.tgz", - "integrity": "sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", + "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -336,9 +339,9 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.22.5.tgz", - "integrity": "sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", + "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -351,9 +354,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", - "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz", + "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -376,20 +379,20 @@ } }, "node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", + "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -397,11 +400,11 @@ } }, "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", + "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", "dependencies": { - "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, @@ -919,9 +922,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", - "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -942,9 +945,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -957,53 +960,68 @@ } }, "node_modules/@eslint/js": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", - "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@floating-ui/core": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", - "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==", + "node_modules/@fontsource/roboto": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.0.8.tgz", + "integrity": "sha512-XxPltXs5R31D6UZeLIV1td3wTXU3jzd3f2DLsXI8tytMGBkIsGcc9sIyiupRtA8y73HAhuSCeweOoBqf6DbWCA==" + }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz", + "integrity": "sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==", + "dev": true, + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.1.tgz", + "integrity": "sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==", + "dev": true, + "hasInstallScript": true, "dependencies": { - "@floating-ui/utils": "^0.1.3" + "@fortawesome/fontawesome-common-types": "6.5.1" + }, + "engines": { + "node": ">=6" } }, - "node_modules/@floating-ui/dom": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", - "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.1.tgz", + "integrity": "sha512-S1PPfU3mIJa59biTtXJz1oI0+KAXW6bkAb31XKhxdxtuXDiUIFsih4JR1v5BbxY7hVHsD1RKq+jRkVRaf773NQ==", + "dev": true, + "hasInstallScript": true, "dependencies": { - "@floating-ui/core": "^1.4.2", - "@floating-ui/utils": "^0.1.3" + "@fortawesome/fontawesome-common-types": "6.5.1" + }, + "engines": { + "node": ">=6" } }, - "node_modules/@floating-ui/react-dom": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz", - "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==", + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz", + "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==", + "dev": true, "dependencies": { - "@floating-ui/dom": "^1.5.1" + "prop-types": "^15.8.1" }, "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" } }, - "node_modules/@floating-ui/utils": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", - "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" - }, - "node_modules/@fontsource/roboto": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.0.8.tgz", - "integrity": "sha512-XxPltXs5R31D6UZeLIV1td3wTXU3jzd3f2DLsXI8tytMGBkIsGcc9sIyiupRtA8y73HAhuSCeweOoBqf6DbWCA==" - }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", @@ -1085,251 +1103,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@mui/base": { - "version": "5.0.0-beta.22", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.22.tgz", - "integrity": "sha512-l4asGID5tmyerx9emJfXOKLyXzaBtdXNIFE3M+IrSZaFtGFvaQKHhc3+nxxSxPf1+G44psjczM0ekRQCdXx9HA==", - "dependencies": { - "@babel/runtime": "^7.23.2", - "@floating-ui/react-dom": "^2.0.2", - "@mui/types": "^7.2.8", - "@mui/utils": "^5.14.16", - "@popperjs/core": "^2.11.8", - "clsx": "^2.0.0", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/core-downloads-tracker": { - "version": "5.14.16", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.16.tgz", - "integrity": "sha512-97isBjzH2v1K7oB4UH2f4NOkBShOynY6dhnoR2XlUk/g6bb7ZBv2I3D1hvvqPtpEigKu93e7f/jAYr5d9LOc5w==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - } - }, - "node_modules/@mui/icons-material": { - "version": "5.14.16", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.14.16.tgz", - "integrity": "sha512-wmOgslMEGvbHZjFLru8uH5E+pif/ciXAvKNw16q6joK6EWVWU5rDYWFknDaZhCvz8ZE/K8ZnJQ+lMG6GgHzXbg==", - "dependencies": { - "@babel/runtime": "^7.23.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, - "peerDependencies": { - "@mui/material": "^5.0.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/material": { - "version": "5.14.16", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.16.tgz", - "integrity": "sha512-W4zZ4vnxgGk6/HqBwgsDHKU7x2l2NhX+r8gAwfg58Rhu3ikfY7NkIS6y8Gl3NkATc4GG1FNaGjjpQKfJx3U6Jw==", - "dependencies": { - "@babel/runtime": "^7.23.2", - "@mui/base": "5.0.0-beta.22", - "@mui/core-downloads-tracker": "^5.14.16", - "@mui/system": "^5.14.16", - "@mui/types": "^7.2.8", - "@mui/utils": "^5.14.16", - "@types/react-transition-group": "^4.4.8", - "clsx": "^2.0.0", - "csstype": "^3.1.2", - "prop-types": "^15.8.1", - "react-is": "^18.2.0", - "react-transition-group": "^4.4.5" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/private-theming": { - "version": "5.14.16", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.16.tgz", - "integrity": "sha512-FNlL0pTSEBh8nXsVWreCHDSHk+jG8cBx1sxRbT8JVtL+PYbYPi802zfV4B00Kkf0LNRVRvAVQwojMWSR/MYGng==", - "dependencies": { - "@babel/runtime": "^7.23.2", - "@mui/utils": "^5.14.16", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/styled-engine": { - "version": "5.14.16", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.16.tgz", - "integrity": "sha512-FfvYvTG/Zd+KXMMImbcMYEeQAbONGuX5Vx3gBmmtB6KyA7Mvm9Pma1ly3R0gc44yeoFd+2wBjn1feS8h42HW5w==", - "dependencies": { - "@babel/runtime": "^7.23.2", - "@emotion/cache": "^11.11.0", - "csstype": "^3.1.2", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, - "peerDependencies": { - "@emotion/react": "^11.4.1", - "@emotion/styled": "^11.3.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - } - } - }, - "node_modules/@mui/system": { - "version": "5.14.16", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.16.tgz", - "integrity": "sha512-uKnPfsDqDs8bbN54TviAuoGWOmFiQLwNZ3Wvj+OBkJCzwA6QnLb/sSeCB7Pk3ilH4h4jQ0BHtbR+Xpjy9wlOuA==", - "dependencies": { - "@babel/runtime": "^7.23.2", - "@mui/private-theming": "^5.14.16", - "@mui/styled-engine": "^5.14.16", - "@mui/types": "^7.2.8", - "@mui/utils": "^5.14.16", - "clsx": "^2.0.0", - "csstype": "^3.1.2", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/types": { - "version": "7.2.8", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.8.tgz", - "integrity": "sha512-9u0ji+xspl96WPqvrYJF/iO+1tQ1L5GTaDOeG3vCR893yy7VcWwRNiVMmPdPNpMDqx0WV1wtEW9OMwK9acWJzQ==", - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/utils": { - "version": "5.14.16", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.16.tgz", - "integrity": "sha512-3xV31GposHkwRbQzwJJuooWpK2ybWdEdeUPtRjv/6vjomyi97F3+68l+QVj9tPTvmfSbr2sx5c/NuvDulrdRmA==", - "dependencies": { - "@babel/runtime": "^7.23.2", - "@types/prop-types": "^15.7.9", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1365,19 +1138,10 @@ "node": ">= 8" } }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, "node_modules/@types/babel__core": { - "version": "7.20.3", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.3.tgz", - "integrity": "sha512-54fjTSeSHwfan8AyHWrKbfBWiEUrNTZsUwPTDSNaaP1QDQIZbeNUg3a59E9D+375MzUw/x1vx2/0F5LBz+AeYA==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "dependencies": { "@babel/parser": "^7.20.7", @@ -1388,18 +1152,18 @@ } }, "node_modules/@types/babel__generator": { - "version": "7.6.6", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.6.tgz", - "integrity": "sha512-66BXMKb/sUWbMdBNdMvajU7i/44RkrA3z/Yt1c7R5xejt8qh84iU54yUWCtm0QwGJlDcf/gg4zd/x4mpLAlb/w==", + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", "dev": true, "dependencies": { "@babel/types": "^7.0.0" } }, "node_modules/@types/babel__template": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.3.tgz", - "integrity": "sha512-ciwyCLeuRfxboZ4isgdNZi/tkt06m8Tw6uGbBSBgWrnnZGNXiEyM27xc/PjXGQLqlZ6ylbgHMnm7ccF9tCkOeQ==", + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, "dependencies": { "@babel/parser": "^7.1.0", @@ -1407,43 +1171,45 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.3", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.3.tgz", - "integrity": "sha512-Lsh766rGEFbaxMIDH7Qa+Yha8cMVI3qAK6CHt3OR0YfxOIn5Z54iHiyDRycHrBqeIiqGa20Kpsv1cavfBKkRSw==", + "version": "7.20.4", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.4.tgz", + "integrity": "sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA==", "dev": true, "dependencies": { "@babel/types": "^7.20.7" } }, "node_modules/@types/json-schema": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", - "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "node_modules/@types/node": { - "version": "20.8.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", - "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==", + "version": "20.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", + "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@types/parse-json": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.1.tgz", - "integrity": "sha512-3YmXzzPAdOTVljVMkTMBdBEvlOLg2cDQaDhnnhT3nT9uDbnJzjWhKlzb+desT12Y7tGqaN6d+AbozcKzyL36Ng==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, "node_modules/@types/prop-types": { - "version": "15.7.9", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.9.tgz", - "integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==" + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", + "dev": true }, "node_modules/@types/react": { - "version": "18.2.34", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.34.tgz", - "integrity": "sha512-U6eW/alrRk37FU/MS2RYMjx0Va2JGIVXELTODaTIYgvWGCV4Y4TfTUzG8DdmpDNIT0Xpj/R7GfyHOJJrDttcvg==", + "version": "18.2.45", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.45.tgz", + "integrity": "sha512-TtAxCNrlrBp8GoeEp1npd5g+d/OejJHFxS3OWmrPBMFaVQMSN0OFySozJio5BHxTuTeug00AVXVAjfDSfk+lUg==", + "dev": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -1451,44 +1217,37 @@ } }, "node_modules/@types/react-dom": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.14.tgz", - "integrity": "sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ==", + "version": "18.2.18", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", + "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==", "dev": true, "dependencies": { "@types/react": "*" } }, - "node_modules/@types/react-transition-group": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.8.tgz", - "integrity": "sha512-QmQ22q+Pb+HQSn04NL3HtrqHwYMf4h3QKArOy5F8U5nEVMaihBs3SR10WiOM1iwPz5jIo8x/u11al+iEGZZrvg==", - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/scheduler": { - "version": "0.16.5", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.5.tgz", - "integrity": "sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==" + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", + "dev": true }, "node_modules/@types/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.9.1.tgz", - "integrity": "sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.15.0.tgz", + "integrity": "sha512-j5qoikQqPccq9QoBAupOP+CBu8BaJ8BLjaXSioDISeTZkVO3ig7oSIKh3H+rEpee7xCXtWwSB4KIL5l6hWZzpg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.9.1", - "@typescript-eslint/type-utils": "6.9.1", - "@typescript-eslint/utils": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1", + "@typescript-eslint/scope-manager": "6.15.0", + "@typescript-eslint/type-utils": "6.15.0", + "@typescript-eslint/utils": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -1514,15 +1273,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.9.1.tgz", - "integrity": "sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.15.0.tgz", + "integrity": "sha512-MkgKNnsjC6QwcMdlNAel24jjkEO/0hQaMDLqP4S9zq5HBAUJNQB6y+3DwLjX7b3l2b37eNAxMPLwb3/kh8VKdA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.9.1", - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/typescript-estree": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1", + "@typescript-eslint/scope-manager": "6.15.0", + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/typescript-estree": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0", "debug": "^4.3.4" }, "engines": { @@ -1542,13 +1301,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.9.1.tgz", - "integrity": "sha512-38IxvKB6NAne3g/+MyXMs2Cda/Sz+CEpmm+KLGEM8hx/CvnSRuw51i8ukfwB/B/sESdeTGet1NH1Wj7I0YXswg==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.15.0.tgz", + "integrity": "sha512-+BdvxYBltqrmgCNu4Li+fGDIkW9n//NrruzG9X1vBzaNK+ExVXPoGB71kneaVw/Jp+4rH/vaMAGC6JfMbHstVg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1" + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1559,13 +1318,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.9.1.tgz", - "integrity": "sha512-eh2oHaUKCK58qIeYp19F5V5TbpM52680sB4zNSz29VBQPTWIlE/hCj5P5B1AChxECe/fmZlspAWFuRniep1Skg==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.15.0.tgz", + "integrity": "sha512-CnmHKTfX6450Bo49hPg2OkIm/D/TVYV7jO1MCfPYGwf6x3GO0VU8YMO5AYMn+u3X05lRRxA4fWCz87GFQV6yVQ==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.9.1", - "@typescript-eslint/utils": "6.9.1", + "@typescript-eslint/typescript-estree": "6.15.0", + "@typescript-eslint/utils": "6.15.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -1586,9 +1345,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.9.1.tgz", - "integrity": "sha512-BUGslGOb14zUHOUmDB2FfT6SI1CcZEJYfF3qFwBeUrU6srJfzANonwRYHDpLBuzbq3HaoF2XL2hcr01c8f8OaQ==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.15.0.tgz", + "integrity": "sha512-yXjbt//E4T/ee8Ia1b5mGlbNj9fB9lJP4jqLbZualwpP2BCQ5is6BcWwxpIsY4XKAhmdv3hrW92GdtJbatC6dQ==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1599,13 +1358,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.1.tgz", - "integrity": "sha512-U+mUylTHfcqeO7mLWVQ5W/tMLXqVpRv61wm9ZtfE5egz7gtnmqVIw9ryh0mgIlkKk9rZLY3UHygsBSdB9/ftyw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.15.0.tgz", + "integrity": "sha512-7mVZJN7Hd15OmGuWrp2T9UvqR2Ecg+1j/Bp1jXUEY2GZKV6FXlOIoqVDmLpBiEiq3katvj/2n2mR0SDwtloCew==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1", + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1626,17 +1385,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.9.1.tgz", - "integrity": "sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.15.0.tgz", + "integrity": "sha512-eF82p0Wrrlt8fQSRL0bGXzK5nWPRV2dYQZdajcfzOD9+cQz9O7ugifrJxclB+xVOvWvagXfqS4Es7vpLP4augw==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.9.1", - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/typescript-estree": "6.9.1", + "@typescript-eslint/scope-manager": "6.15.0", + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/typescript-estree": "6.15.0", "semver": "^7.5.4" }, "engines": { @@ -1651,12 +1410,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.1.tgz", - "integrity": "sha512-MUaPUe/QRLEffARsmNfmpghuQkW436DvESW+h+M52w0coICHRfD6Np9/K6PdACwnrq1HmuLl+cSPZaJmeVPkSw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.15.0.tgz", + "integrity": "sha512-1zvtdC1a9h5Tb5jU9x3ADNXO9yjP8rXlaoChu0DQX40vf5ACVpYIVIZhIMZ6d5sDXH7vq4dsZBT1fEGj8D2n2w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/types": "6.15.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -1674,22 +1433,22 @@ "dev": true }, "node_modules/@vitejs/plugin-react": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.1.1.tgz", - "integrity": "sha512-Jie2HERK+uh27e+ORXXwEP5h0Y2lS9T2PRGbfebiHGlwzDO0dEnd2aNtOR/qjBlPb1YgxwAONeblL1xqLikLag==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", + "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==", "dev": true, "dependencies": { - "@babel/core": "^7.23.2", - "@babel/plugin-transform-react-jsx-self": "^7.22.5", - "@babel/plugin-transform-react-jsx-source": "^7.22.5", - "@types/babel__core": "^7.20.3", + "@babel/core": "^7.23.5", + "@babel/plugin-transform-react-jsx-self": "^7.23.3", + "@babel/plugin-transform-react-jsx-source": "^7.23.3", + "@types/babel__core": "^7.20.5", "react-refresh": "^0.14.0" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "peerDependencies": { - "vite": "^4.2.0" + "vite": "^4.2.0 || ^5.0.0" } }, "node_modules/acorn": { @@ -1829,9 +1588,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", "dev": true, "funding": [ { @@ -1848,9 +1607,9 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, "bin": { @@ -1869,9 +1628,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001561", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001561.tgz", - "integrity": "sha512-NTt0DNoKe958Q0BE0j0c1V9jbUzhBxHIEJy7asmGrpE0yG63KTV7PLHPnK2E1O9RsQrQ081I3NLuXGS6zht3cw==", + "version": "1.0.30001570", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz", + "integrity": "sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw==", "dev": true, "funding": [ { @@ -1949,9 +1708,10 @@ } }, "node_modules/clsx": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", - "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "dev": true, "engines": { "node": ">=6" } @@ -2010,9 +1770,9 @@ } }, "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/debug": { "version": "4.3.4", @@ -2061,19 +1821,10 @@ "node": ">=6.0.0" } }, - "node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, "node_modules/electron-to-chromium": { - "version": "1.4.576", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.576.tgz", - "integrity": "sha512-yXsZyXJfAqzWk1WKryr0Wl0MN2D47xodPvEEwlVePBnhU5E7raevLQR+E6b9JAD3GfL/7MbAL9ZtWQQPcLx7wA==", + "version": "1.4.615", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.615.tgz", + "integrity": "sha512-/bKPPcgZVUziECqDc+0HkT87+0zhaWSZHNXqF8FLd2lQcptpmUFwoCSWjCdOng9Gdq+afKArPdEg/0ZW461Eng==", "dev": true }, "node_modules/error-ex": { @@ -2142,15 +1893,15 @@ } }, "node_modules/eslint": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", - "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.53.0", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -2209,9 +1960,9 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.4.tgz", - "integrity": "sha512-eD83+65e8YPVg6603Om2iCIwcQJf/y7++MWm4tACtEswFLYMwxwVWAfwN+e19f5Ad/FOyyNg9Dfi5lXhH3Y3rA==", + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.5.tgz", + "integrity": "sha512-D53FYKJa+fDmZMtriODxvhwrO+IOqrxoEo21gMA0sjHdU6dPVH4OhyFip9ypl8HOF5RV5KdTo+rBQLvnY2cO8w==", "dev": true, "peerDependencies": { "eslint": ">=7" @@ -2295,9 +2046,9 @@ "dev": true }, "node_modules/eslint/node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -2396,9 +2147,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -2436,9 +2187,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", + "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -2490,9 +2241,9 @@ } }, "node_modules/flat-cache": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", - "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "dependencies": { "flatted": "^3.2.9", @@ -2500,7 +2251,7 @@ "rimraf": "^3.0.2" }, "engines": { - "node": ">=12.0.0" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/flatted": { @@ -2640,15 +2391,10 @@ "react-is": "^16.7.0" } }, - "node_modules/hoist-non-react-statics/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", "dev": true, "engines": { "node": ">= 4" @@ -2946,9 +2692,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, "funding": [ { @@ -2970,9 +2716,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, "node_modules/normalize-path": { @@ -2988,6 +2734,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -3135,9 +2882,9 @@ } }, "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.4.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", + "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", "dev": true, "funding": [ { @@ -3154,7 +2901,7 @@ } ], "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -3175,17 +2922,13 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3226,6 +2969,19 @@ "node": ">=0.10.0" } }, + "node_modules/react-contexify": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/react-contexify/-/react-contexify-6.0.0.tgz", + "integrity": "sha512-jMhz6yZI81Jv3UDj7TXqCkhdkCFEEmvwGCPXsQuA2ZUC8EbCuVQ6Cy8FzKMXa0y454XTDClBN2YFvvmoFlrFkg==", + "dev": true, + "dependencies": { + "clsx": "^1.2.1" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -3239,9 +2995,9 @@ } }, "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-refresh": { "version": "0.14.0", @@ -3252,19 +3008,17 @@ "node": ">=0.10.0" } }, - "node_modules/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "node_modules/react-toastify": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.3.tgz", + "integrity": "sha512-fPfb8ghtn/XMxw3LkxQBk3IyagNpF/LIKjOBflbexr2AWxAH1MJgvnESwEwBn9liLFXgTKWgBSdZpw9m4OTHTg==", + "dev": true, "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" + "clsx": "^1.1.1" }, "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" + "react": ">=16", + "react-dom": ">=16" } }, "node_modules/readdirp": { @@ -3280,9 +3034,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/resolve": { "version": "1.22.8", @@ -3591,9 +3345,9 @@ } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -3649,9 +3403,9 @@ } }, "node_modules/vite": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz", - "integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz", + "integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==", "dev": true, "dependencies": { "esbuild": "^0.18.10", diff --git a/frontend/package.json b/frontend/package.json index 576265c..d0c41df 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,12 +15,13 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@fontsource/roboto": "^5.0.8", - "@mui/icons-material": "^5.14.16", - "@mui/material": "^5.14.16", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { + "@fortawesome/fontawesome-svg-core": "^6.5.1", + "@fortawesome/free-solid-svg-icons": "^6.5.1", + "@fortawesome/react-fontawesome": "^0.2.0", "@types/node": "^20.8.0", "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", @@ -30,6 +31,8 @@ "eslint": "^8.45.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3", + "react-contexify": "^6.0.0", + "react-toastify": "^9.1.3", "sass": "^1.68.0", "typescript": "^5.0.2", "vite": "^4.4.5" diff --git a/frontend/src/components/Container/index.tsx b/frontend/src/components/Container/index.tsx new file mode 100644 index 0000000..48e3ca4 --- /dev/null +++ b/frontend/src/components/Container/index.tsx @@ -0,0 +1,30 @@ +import { ReactNode } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' + +import './style.scss'; + +interface ContainerProps { + title: string, + version: string, + children?: ReactNode +} + +export function Container({title, version, children}:ContainerProps) { + return (<> + <header> + <h1>{title}</h1> + <a className="btn" id="donate" href="https://github.com/sponsors/MainKronos" target="_blank"> + <FontAwesomeIcon icon="heart" size="2x"/> + </a> + </header> + + <main> + {children} + </main> + + <footer> + <a href={`https://github.com/MainKronos/Sonarr-AnimeDownloader/releases/tag/${version}`} target="_blank">Ver. {version}</a> + </footer> + + </>); +} \ No newline at end of file diff --git a/frontend/src/components/Container/style.scss b/frontend/src/components/Container/style.scss new file mode 100644 index 0000000..7a1426b --- /dev/null +++ b/frontend/src/components/Container/style.scss @@ -0,0 +1,76 @@ +@use "sass:color"; +@use '@/styles/index' as *; + + +header { + overflow: hidden; + text-align: center; + background-color: elevation(1); + box-shadow: 0 0 10px black; + margin-bottom: 60px; + z-index: 1; + position: fixed; + top: 0; + width: 100%; + + >h1 { + color: rgba(255, 255, 255, 0.60); + font-weight: 400; + user-select: none; + padding: 0 60px; + } + + >a#donate { + animation: HeartBeat 1.5s infinite; + position: absolute; + right: 0.67em; + top: 0.67em; + filter: drop-shadow(0 0 5px $base-color); + + @keyframes HeartBeat { + 0% { + transform: scale(0.8); + } + + 5% { + transform: scale(0.9); + } + + 10% { + transform: scale(0.8); + } + + 15% { + transform: scale(1); + } + + 50% { + transform: scale(0.8); + } + + 100% { + transform: scale(0.8); + } + } + } +} + +main { + margin: 180px auto 100px auto; + max-width: 1280px; + min-height: calc(100vh - 340px); + width: 70%; + + @media only screen and (max-width: 700px) { + width: 100%; + } +} + +footer { + overflow: hidden; + margin-top: 100px; + padding: 20px; + text-align: center; + background-color: elevation(1); + vertical-align: middle; +} \ No newline at end of file diff --git a/frontend/src/components/Footer/index.tsx b/frontend/src/components/Footer/index.tsx deleted file mode 100644 index 8e05ecb..0000000 --- a/frontend/src/components/Footer/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { ReactNode } from 'react'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; - -interface FooterProps { - content:ReactNode -} - -export function Footer({content}:FooterProps) { - return ( - <Paper square elevation={1} sx={{ position: 'fixed', bottom: 0, left: 0, right: 0, padding: '16px', borderTop: '1px solid', borderColor: 'divider' }}> - <Typography variant="body1" noWrap sx={{flex: 1, display: 'flex', justifyContent: 'center'}}> - {content} - </Typography> - </Paper> - ); -}; \ No newline at end of file diff --git a/frontend/src/components/Header/index.tsx b/frontend/src/components/Header/index.tsx deleted file mode 100644 index 66383d5..0000000 --- a/frontend/src/components/Header/index.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { ReactNode } from 'react'; -import AppBar from '@mui/material/AppBar'; -import Toolbar from '@mui/material/Toolbar'; -import Typography from '@mui/material/Typography'; - -interface HeaderProps { - title: string, - children?: ReactNode -} - -export function Header({ title, children }: HeaderProps) { - return ( - <AppBar position="fixed"> - <Toolbar sx={{ padding: "20px" }}> - {children} - - <Typography variant="h4" textAlign='center' sx={{ flex: 1, fontWeight: 500 }} component="h1"> - {title} - </Typography> - - </Toolbar> - </AppBar> - ); -}; \ No newline at end of file diff --git a/frontend/src/components/Navigator/index.tsx b/frontend/src/components/Navigator/index.tsx index 34c9479..ed3f13a 100644 --- a/frontend/src/components/Navigator/index.tsx +++ b/frontend/src/components/Navigator/index.tsx @@ -1,48 +1,24 @@ -import Drawer from '@mui/material/Drawer'; -import List from '@mui/material/List'; -import ListItemButton from '@mui/material/ListItemButton'; -import ListItemIcon from '@mui/material/ListItemIcon'; -import Box from '@mui/material/Box'; -import Link from '@mui/material/Link'; - -import SettingsIcon from '@mui/icons-material/Settings'; -import InboxIcon from '@mui/icons-material/Inbox'; -import ArticleIcon from '@mui/icons-material/Article'; - +import { useState } from 'react'; +import { ReactNode } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import './style.scss'; interface NavigatorProps { - drawerState: boolean, - setDrawerState: (state:boolean) => void, + children?: ReactNode } -export function Navigator({drawerState, setDrawerState}:NavigatorProps) { +export function Navigator({children}:NavigatorProps) { + const [drawerState, setDrawerState] = useState(false); - function closeDrawer() {setDrawerState(false)} + function toggleDrawer() {setDrawerState(!drawerState)} - return ( - <Drawer anchor="left" open={drawerState} onClose={(closeDrawer)}> - <Box sx={{ width: 250, paddingTop: 10 }}> - <List component="nav"> - <ListItemButton selected={true}> - <ListItemIcon> - <InboxIcon /> - </ListItemIcon> - <Link href="index.html" color="inherit" underline='none'>Index</Link> - </ListItemButton> - <ListItemButton> - <ListItemIcon> - <SettingsIcon /> - </ListItemIcon> - <Link href="settings.html" color="inherit" underline='none'>Settings</Link> - </ListItemButton> - <ListItemButton> - <ListItemIcon> - <ArticleIcon /> - </ListItemIcon> - <Link href="log.html" color="inherit" underline='none'>Logs</Link> - </ListItemButton> - </List> - </Box> - </Drawer> - ) + return (<> + <nav className={drawerState ? 'active' : ''} onClick={toggleDrawer}> + <div></div> + <div>{children}</div> + </nav> + <button onClick={toggleDrawer}> + <FontAwesomeIcon icon="bars" size="lg"/> + </button> + </>) } \ No newline at end of file diff --git a/frontend/src/components/Navigator/style.scss b/frontend/src/components/Navigator/style.scss new file mode 100644 index 0000000..f55cbe6 --- /dev/null +++ b/frontend/src/components/Navigator/style.scss @@ -0,0 +1,74 @@ +@use "sass:color"; +@use '@/styles/index' as *; + +nav { + height: 100%; + width: 0; + position: fixed; + z-index: 3; + top: 0; + left: 0; + background-color: elevation(2); + overflow-x: hidden; + padding-top: 60px; + transition: 0.5s; + box-shadow: 0 2px 2px 0 rgb(0 0 0 / 14%), 0 3px 1px -2px rgb(0 0 0 / 12%), 0 1px 5px 0 rgb(0 0 0 / 20%); + + /* overlay */ + > div:nth-of-type(1) { + position: fixed; + top: 0; + left: 0; + width: 0; + height: 100%; + background-color: rgba(0,0,0,0); + transition: background-color, left 500ms; + } + + /* content */ + > div:nth-of-type(2) { + position: relative; + top: 0; + left: 0; + padding: 10px; + text-align: center; + + > * { + display: block; + border-bottom: 1.5px solid $text-color; + color: $text-color; + cursor: pointer; + padding: 5px; + + &:hover { + color: $base-color; + border-bottom: 1.5px solid $base-color; + } + } + } + + &.active { + width: 200px; + + > div:nth-of-type(1) { + left: 200px; + background-color: rgba(0,0,0,0.2); + width: 100%; + } + } + + + button { + position: absolute; + left: 30px; + top: 30px; + z-index: 2; + background: none; + border: none; + color: $text-color; + cursor: pointer; + + &:hover { + color: $base-color; + } + } +} \ No newline at end of file diff --git a/frontend/src/components/index.tsx b/frontend/src/components/index.tsx index ff78911..5fdeb5d 100644 --- a/frontend/src/components/index.tsx +++ b/frontend/src/components/index.tsx @@ -1,3 +1,4 @@ -export {Footer} from './Footer'; -export {Header} from './Header'; -export {Navigator} from './Navigator'; \ No newline at end of file +// export {Footer} from './Footer'; +// export {Header} from './Header'; +export {Navigator} from './Navigator'; +export {Container} from './Container'; \ No newline at end of file diff --git a/frontend/src/pages/home/App.tsx b/frontend/src/pages/home/App.tsx index 49abfd6..8e1a230 100644 --- a/frontend/src/pages/home/App.tsx +++ b/frontend/src/pages/home/App.tsx @@ -1,53 +1,23 @@ // import * from "react"; import { useState, useEffect } from 'react'; +import { library } from '@fortawesome/fontawesome-svg-core' +import { fas } from '@fortawesome/free-solid-svg-icons' -import { Header, Footer, Navigator } from '@/components'; +import { Container, Navigator } from '@/components'; import '@fontsource/roboto/300.css'; import '@fontsource/roboto/400.css'; import '@fontsource/roboto/500.css'; import '@fontsource/roboto/700.css'; -import IconButton from '@mui/material/IconButton'; - -import Container from '@mui/material/Container'; -import Skeleton from '@mui/material/Skeleton'; -import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import ListItemButton from '@mui/material/ListItemButton'; -import ListItemText from '@mui/material/ListItemText'; -import Accordion from '@mui/material/Accordion'; -import AccordionSummary from '@mui/material/AccordionSummary'; -import AccordionDetails from '@mui/material/AccordionDetails'; -import Typography from '@mui/material/Typography'; -import Tabs from '@mui/material/Tabs'; -import Tab from '@mui/material/Tab'; -import Divider from '@mui/material/Divider'; -import Paper from '@mui/material/Paper'; -import Button from '@mui/material/Button'; -import Chip from '@mui/material/Chip'; -import Stack from '@mui/material/Stack'; - - -import { ThemeProvider } from '@mui/material/styles'; -import { CssBaseline } from '@mui/material'; - -import MenuIcon from '@mui/icons-material/Menu'; -import MovieIcon from '@mui/icons-material/Movie'; -import AddIcon from '@mui/icons-material/Add'; -import DownloadIcon from '@mui/icons-material/Download'; -import UploadIcon from '@mui/icons-material/Upload'; import { API } from '@/utils/API'; -import { theme } from '@/utils/globals' -import type { SerieTableEntry, SeasonsTableEntry } from '@/utils/types'; export default function App() { - const [drawerState, setDrawerState] = useState(false); - const [version, setVersion] = useState(''); + library.add(fas); - function openDrawer() { setDrawerState(true) } + const [version, setVersion] = useState(''); const api = new API('http://127.0.0.1:5000'); @@ -55,92 +25,19 @@ export default function App() { api.getVersion().then(res => setVersion(res)); }, []); - return ( - <ThemeProvider theme={theme}> - <CssBaseline /> - - <Header title='Tabella Di Conversione'> - <IconButton - size="large" - sx={{ position: "absolute" }} - onClick={openDrawer} - > - <MenuIcon fontSize='medium' /> - </IconButton> - </Header> - - <Navigator drawerState={drawerState} setDrawerState={setDrawerState} /> - - <SerieTable api={api} /> - - <Footer content={version ? "Ver. " + version : <Skeleton width="80px" />} /> - - </ThemeProvider> - ); -} - -function SerieTable({ api }: { api: API }) { - - const [data, setData] = useState([] as SerieTableEntry[]); - - useEffect(() => { - api.getTable().then(res => setData(res)); - }, []) - - return ( - <Container maxWidth="md" sx={{ py: 20 }}> - <Paper> - <Button aria-label="add" size="large" variant="text" fullWidth={true}> - <AddIcon /> - </Button> - </Paper> - - {data.map((serie, index) => ( - <Accordion key={index}> - <AccordionSummary expandIcon={<MovieIcon />}> - <Typography>{serie.title}</Typography> {serie.absolute && <Chip label="absolute" color="primary" size="small"/>} - </AccordionSummary> - <Divider /> - <AccordionDetails> - <SeasonTabs api={api} season={serie.seasons} /> - </AccordionDetails> - </Accordion> - ))} - - <Paper> - <Stack direction="row" > - <Button aria-label="download" size="large" variant="text" fullWidth={true}> - <DownloadIcon /> - </Button> - <Button aria-label="upload" size="large" variant="text" fullWidth={true}> - <UploadIcon /> - </Button> - </Stack> - </Paper> - - </Container> - ); -} - - -function SeasonTabs({ api, season }: { api: API, season: SeasonsTableEntry }) { - const [tab, setTab] = useState(0); - return (<> - <Tabs value={tab} onChange={(_, val) => val && setTab(val)} aria-label="basic tabs example"> - {Object.entries(season).map(([key, _]) => ( - <Tab label={key} key={key} /> - ))} - <Tab icon={<AddIcon />} aria-label="add" value={null}/> - </Tabs> - <List dense={true}> - {Object.values(season)[tab].map((val: string) => ( - <ListItem key={val}> - <ListItemButton component="a" href={val} target="_blank"> - <ListItemText primary={val} /> - </ListItemButton> - </ListItem> - ))} - </List> - </>); -} \ No newline at end of file + <Navigator> + <a>Home</a> + <a href="settings.html">Settings</a> + <a href="log.html">Log</a> + </Navigator> + + <Container + title='Tabella Di Conversione' + version={version} + > + + </Container> + </>); + +} diff --git a/frontend/src/pages/log/App.tsx b/frontend/src/pages/log/App.tsx index d776b3c..755c643 100644 --- a/frontend/src/pages/log/App.tsx +++ b/frontend/src/pages/log/App.tsx @@ -1,10 +1,9 @@ import { Component } from "react"; -import { Header } from "@/components"; export default class App extends Component { render() { - return ( - <Header title="Tabella Di Conversione"/> - ) + return (<></> + + ); } } \ No newline at end of file diff --git a/frontend/src/pages/settings/App.tsx b/frontend/src/pages/settings/App.tsx index f34a70e..ef3bc19 100644 --- a/frontend/src/pages/settings/App.tsx +++ b/frontend/src/pages/settings/App.tsx @@ -1,16 +1,10 @@ import { Component } from "react"; -import { Header, Navigator } from "@/components"; export default class App extends Component { render() { return ( <> - <Header title="Settings"/> - <Navigator> - <a>Home</a> - <a href="settings">Settings</a> - <a href="log">Log</a> - </Navigator> + </> ) } diff --git a/frontend/src/styles/_index.scss b/frontend/src/styles/_index.scss new file mode 100644 index 0000000..477e81b --- /dev/null +++ b/frontend/src/styles/_index.scss @@ -0,0 +1,73 @@ +@use "sass:color"; + +$base-color: #9FA8DA !default; +$background-color: #121212 !default; +$text-color: #bababa !default; + +html { + color: $text-color; + background-color: $background-color; + font-family: 'Roboto'; +} + +body { + margin: 0; +} + +a { + color: $text-color; + text-decoration: none; + width: fit-content; + margin: 10px; + + &:active, + &:visited { + color: $text-color; + } + + &:hover { + color: $base-color; + } +} + +@function elevation($level: 0) { + @if $level ==0 { + @return $background-color; + } + + @else if $level ==1 { + @return color.scale($background-color, $lightness: 5%); + } + + @else if $level ==2 { + @return color.scale($background-color, $lightness: 7%); + } + + @else if $level ==3 { + @return color.scale($background-color, $lightness: 8%); + } + + @else if 4 <=$level and $level <=5 { + @return color.scale($background-color, $lightness: 9%); + } + + @else if 6 <=$level and $level <=7 { + @return color.scale($background-color, $lightness: 11%); + } + + @else if 8 <=$level and $level <=11 { + @return color.scale($background-color, $lightness: 12%); + } + + @else if 12 <=$level and $level <=15 { + @return color.scale($background-color, $lightness: 14%); + } + + @else if 16 <=$level and $level <=23 { + @return color.scale($background-color, $lightness: 15%); + } + + @else { + @return color.scale($background-color, $lightness: 16%); + } +} \ No newline at end of file diff --git a/src/dev_api.py b/src/dev_api.py index 728cfd7..8fc6044 100644 --- a/src/dev_api.py +++ b/src/dev_api.py @@ -1,14 +1,8 @@ -import os import pathlib -import sys from components.backend.core.Core import Core, ctx from components.api import API -import pathlib -import sys, json -import uvicorn - ctx.DOWNLOAD_FOLDER = pathlib.Path('./tests/downloads').absolute() ctx.DATABASE_FOLDER = pathlib.Path("./tests/database").absolute() ctx.SCRIPT_FOLDER = pathlib.Path("./tests/script").absolute() From 280ce3b52bd0a37d1d2c94f08b6afd49d360de54 Mon Sep 17 00:00:00 2001 From: MainKronos <lorenzo.chesi@live.it> Date: Fri, 22 Dec 2023 23:54:34 +0100 Subject: [PATCH 20/39] table --- frontend/package-lock.json | 86 ---------------- frontend/package.json | 4 - frontend/src/components/Container/index.tsx | 3 +- frontend/src/components/Container/style.scss | 5 +- frontend/src/components/Navigator/index.tsx | 3 +- frontend/src/components/Navigator/style.scss | 2 +- frontend/src/components/Table/index.tsx | 82 +++++++++++++++ frontend/src/components/Table/style.scss | 102 +++++++++++++++++++ frontend/src/components/index.tsx | 1 - frontend/src/helper/ContextMenu/index.tsx | 38 +++++++ frontend/src/helper/ContextMenu/style.scss | 35 +++++++ frontend/src/helper/index.tsx | 1 + frontend/src/pages/home/App.tsx | 26 ++--- frontend/src/styles/_index.scss | 24 +++++ frontend/src/utils/API.ts | 12 ++- frontend/src/utils/globals.ts | 39 ------- frontend/src/utils/types.ts | 9 -- 17 files changed, 309 insertions(+), 163 deletions(-) create mode 100644 frontend/src/components/Table/index.tsx create mode 100644 frontend/src/components/Table/style.scss create mode 100644 frontend/src/helper/ContextMenu/index.tsx create mode 100644 frontend/src/helper/ContextMenu/style.scss create mode 100644 frontend/src/helper/index.tsx delete mode 100644 frontend/src/utils/globals.ts delete mode 100644 frontend/src/utils/types.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0bfe91b..d498c07 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,9 +15,6 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@fortawesome/fontawesome-svg-core": "^6.5.1", - "@fortawesome/free-solid-svg-icons": "^6.5.1", - "@fortawesome/react-fontawesome": "^0.2.0", "@types/node": "^20.8.0", "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", @@ -27,7 +24,6 @@ "eslint": "^8.45.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3", - "react-contexify": "^6.0.0", "react-toastify": "^9.1.3", "sass": "^1.68.0", "typescript": "^5.0.2", @@ -973,55 +969,6 @@ "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.0.8.tgz", "integrity": "sha512-XxPltXs5R31D6UZeLIV1td3wTXU3jzd3f2DLsXI8tytMGBkIsGcc9sIyiupRtA8y73HAhuSCeweOoBqf6DbWCA==" }, - "node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz", - "integrity": "sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==", - "dev": true, - "hasInstallScript": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/fontawesome-svg-core": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.1.tgz", - "integrity": "sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.1.tgz", - "integrity": "sha512-S1PPfU3mIJa59biTtXJz1oI0+KAXW6bkAb31XKhxdxtuXDiUIFsih4JR1v5BbxY7hVHsD1RKq+jRkVRaf773NQ==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/react-fontawesome": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz", - "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==", - "dev": true, - "dependencies": { - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "@fortawesome/fontawesome-svg-core": "~1 || ~6", - "react": ">=16.3" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", @@ -2730,15 +2677,6 @@ "node": ">=0.10.0" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2918,17 +2856,6 @@ "node": ">= 0.8.0" } }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2969,19 +2896,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-contexify": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/react-contexify/-/react-contexify-6.0.0.tgz", - "integrity": "sha512-jMhz6yZI81Jv3UDj7TXqCkhdkCFEEmvwGCPXsQuA2ZUC8EbCuVQ6Cy8FzKMXa0y454XTDClBN2YFvvmoFlrFkg==", - "dev": true, - "dependencies": { - "clsx": "^1.2.1" - }, - "peerDependencies": { - "react": ">=16", - "react-dom": ">=16" - } - }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index d0c41df..9303051 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,9 +19,6 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@fortawesome/fontawesome-svg-core": "^6.5.1", - "@fortawesome/free-solid-svg-icons": "^6.5.1", - "@fortawesome/react-fontawesome": "^0.2.0", "@types/node": "^20.8.0", "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", @@ -31,7 +28,6 @@ "eslint": "^8.45.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3", - "react-contexify": "^6.0.0", "react-toastify": "^9.1.3", "sass": "^1.68.0", "typescript": "^5.0.2", diff --git a/frontend/src/components/Container/index.tsx b/frontend/src/components/Container/index.tsx index 48e3ca4..0055134 100644 --- a/frontend/src/components/Container/index.tsx +++ b/frontend/src/components/Container/index.tsx @@ -1,5 +1,4 @@ import { ReactNode } from 'react'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import './style.scss'; @@ -14,7 +13,7 @@ export function Container({title, version, children}:ContainerProps) { <header> <h1>{title}</h1> <a className="btn" id="donate" href="https://github.com/sponsors/MainKronos" target="_blank"> - <FontAwesomeIcon icon="heart" size="2x"/> + <i>favorite</i> </a> </header> diff --git a/frontend/src/components/Container/style.scss b/frontend/src/components/Container/style.scss index 7a1426b..d9afc74 100644 --- a/frontend/src/components/Container/style.scss +++ b/frontend/src/components/Container/style.scss @@ -23,8 +23,9 @@ header { >a#donate { animation: HeartBeat 1.5s infinite; position: absolute; - right: 0.67em; - top: 0.67em; + right: 0.5em; + top: 0.5em; + font-size: 1.5rem; filter: drop-shadow(0 0 5px $base-color); @keyframes HeartBeat { diff --git a/frontend/src/components/Navigator/index.tsx b/frontend/src/components/Navigator/index.tsx index ed3f13a..3ea66f1 100644 --- a/frontend/src/components/Navigator/index.tsx +++ b/frontend/src/components/Navigator/index.tsx @@ -1,6 +1,5 @@ import { useState } from 'react'; import { ReactNode } from 'react'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import './style.scss'; interface NavigatorProps { @@ -18,7 +17,7 @@ export function Navigator({children}:NavigatorProps) { <div>{children}</div> </nav> <button onClick={toggleDrawer}> - <FontAwesomeIcon icon="bars" size="lg"/> + <i>menu</i> </button> </>) } \ No newline at end of file diff --git a/frontend/src/components/Navigator/style.scss b/frontend/src/components/Navigator/style.scss index f55cbe6..e3af671 100644 --- a/frontend/src/components/Navigator/style.scss +++ b/frontend/src/components/Navigator/style.scss @@ -58,7 +58,7 @@ nav { } + button { - position: absolute; + position: sticky; left: 30px; top: 30px; z-index: 2; diff --git a/frontend/src/components/Table/index.tsx b/frontend/src/components/Table/index.tsx new file mode 100644 index 0000000..c59b08b --- /dev/null +++ b/frontend/src/components/Table/index.tsx @@ -0,0 +1,82 @@ +import { useState, useEffect } from 'react'; +import { Menu } from '@/helper'; + +import type { ReactNode } from 'react'; +import type { API, SerieTableEntry } from '@/utils/API'; + +import './style.scss'; + +const MENU_ID = "CTX" + +interface TableProps { + api: API +} + +export function Table({ api }: TableProps) { + + const [table, setTable] = useState([] as SerieTableEntry[]); + + // SYNC DATA + useEffect(() => { + api.getTable().then(res => setTable(res)); + }, []); + + function test() { console.log('lo') } + + function displayMenu(event: MouseEvent) { + Menu.show(event, { + 'Copy': test, + 'Edit': test, + 'Delete': test + }) + } + + return (<> + + {table.map(entry => + <details key={entry.title} onContextMenu={displayMenu as any}> + <summary> + <i>movie</i>{entry.title} + </summary> + + <Tabs + labels={Object.keys(entry.seasons)} + contents={Object.values(entry.seasons).map(season => + season.map(link => <a href={link} target="_blank" key={link}>{link}</a>) + )} + /> + + </details> + )} + + + </>); +} + +interface TabsProps { + labels: ReactNode[], + contents: ReactNode[] +} +function Tabs({labels, contents}:TabsProps){ + + const [tab, setTab] = useState(0); + + return (<> + <ul> + {labels.map((value, index) => + <li + key={value as string} + onClick={() => setTab(index)} + className={tab == index ? 'active' : ''} + >{value}</li> + )} + <li><button><i>add</i></button></li> + </ul> + <section> + {contents[tab]} + <button><i>add</i></button> + </section> + + </>); +} + diff --git a/frontend/src/components/Table/style.scss b/frontend/src/components/Table/style.scss new file mode 100644 index 0000000..6315ef0 --- /dev/null +++ b/frontend/src/components/Table/style.scss @@ -0,0 +1,102 @@ +@use "sass:color"; +@use '@/styles/index' as *; + +details { + user-select: none; + background-color: elevation(1); + margin: 0 30px; + transition: margin 0.1s; + + &[open] { + background-color: elevation(2); + border-bottom: 1px solid $background-color; + margin: 30px 0; + border-radius: 4px; + + >summary { + box-shadow: 0px 1px 2px -1px black; + } + } + + >summary { + cursor: pointer; + color: $text-color; + font-size: 1rem; + font-weight: 400; + padding: 15px; + border-bottom: 1px solid $background-color; + transition: box-shadow 0.5s; + transition: color 0.2s; + + >i { + padding-right: 5px; + } + + &::marker { + content: none; + } + + &:hover { + color: $base-color; + } + } + + >ul { + display: block; + width: 100%; + margin: 0 auto; + padding: 0; + font-size: 16px; + display: flex; + justify-content: flex-start; + border-bottom: 2px solid color.change($color: $text-color, $lightness: 40%); + list-style-type: none; + + > li { + padding: 10px 50px; + cursor: pointer; + border-bottom: 2px solid color.change($color: $text-color, $lightness: 40%); + margin: 0 0 -2px 0; + transition-property: color, border; + transition-duration: 0.3s; + text-align: center; + + &:hover { + color: $base-color; + } + + &.active { + color: $base-color; + border-bottom: 2px solid $base-color; + } + + >button{ + padding: 0; + margin: -2px; + border: 0; + } + } + + + section>* { + display: block; + + margin: 20px; + width: calc(100% - 40px); + } + } + + button { + padding: 5px; + cursor: pointer; + color: color.change($color: $text-color, $lightness: 40%); + margin: 0; + background-color: transparent; + border: 2px solid color.change($color: $text-color, $lightness: 40%); + border-radius: 4px; + + &:hover { + color: $base-color; + border-color: $base-color; + } + } +} \ No newline at end of file diff --git a/frontend/src/components/index.tsx b/frontend/src/components/index.tsx index 5fdeb5d..d12f8b4 100644 --- a/frontend/src/components/index.tsx +++ b/frontend/src/components/index.tsx @@ -1,4 +1,3 @@ // export {Footer} from './Footer'; -// export {Header} from './Header'; export {Navigator} from './Navigator'; export {Container} from './Container'; \ No newline at end of file diff --git a/frontend/src/helper/ContextMenu/index.tsx b/frontend/src/helper/ContextMenu/index.tsx new file mode 100644 index 0000000..078503d --- /dev/null +++ b/frontend/src/helper/ContextMenu/index.tsx @@ -0,0 +1,38 @@ +import './style.scss'; + +class ContextMenu { + menu: HTMLUListElement; + + constructor(){ + const menu = document.createElement('ul'); + this.menu = menu; + menu.id = 'menu'; + document.body.prepend(menu); + + document.addEventListener('click', this.hide); + } + + hide = (event:Event) => { + this.menu.classList.remove('active'); + while(this.menu.firstChild) this.menu.removeChild(this.menu.firstChild); + } + + show = (event:MouseEvent, callbacks:{[label:string]: EventListener}) => { + event.preventDefault(); + + if(this.menu.classList.contains('active')) this.hide(event); + + Object.entries(callbacks).map(([label, callback]) => { + const li = document.createElement('li'); + li.textContent = label; + li.addEventListener('click', callback); + this.menu.appendChild(li); + }); + + this.menu.style.top = `${event.pageY}px`; + this.menu.style.left = `${event.pageX}px`; + this.menu.classList.add('active'); + } +} + +export const Menu = new ContextMenu(); \ No newline at end of file diff --git a/frontend/src/helper/ContextMenu/style.scss b/frontend/src/helper/ContextMenu/style.scss new file mode 100644 index 0000000..027cf7a --- /dev/null +++ b/frontend/src/helper/ContextMenu/style.scss @@ -0,0 +1,35 @@ +@use "sass:color"; +@use '@/styles/index' as *; + +ul#menu { + color: rgba(255, 255, 255, 0.7); + position: absolute; + background-color: elevation(2); + padding: 5px; + border-radius: 4px; + border: 1px solid rgba(0, 0, 0, 0.5); + cursor: pointer; + user-select: none; + margin: 0; + display: none; + box-shadow: 0 2px 2px 0 rgb(0 0 0 / 14%), 0 3px 1px -2px rgb(0 0 0 / 12%), 0 1px 5px 0 rgb(0 0 0 / 20%); + z-index: 4; + min-width: 100px; + + &.active { + display: block; + } + + > li { + padding: 5px 10px; + list-style-type: none; + + &:not(:last-child) { + border-bottom: 1px solid rgba(0, 0, 0, 0.5); + } + + &:hover { + color: $base-color; + } + } +} \ No newline at end of file diff --git a/frontend/src/helper/index.tsx b/frontend/src/helper/index.tsx new file mode 100644 index 0000000..16760d4 --- /dev/null +++ b/frontend/src/helper/index.tsx @@ -0,0 +1 @@ +export {Menu} from './ContextMenu'; \ No newline at end of file diff --git a/frontend/src/pages/home/App.tsx b/frontend/src/pages/home/App.tsx index 8e1a230..2767172 100644 --- a/frontend/src/pages/home/App.tsx +++ b/frontend/src/pages/home/App.tsx @@ -1,8 +1,5 @@ // import * from "react"; import { useState, useEffect } from 'react'; -import { library } from '@fortawesome/fontawesome-svg-core' -import { fas } from '@fortawesome/free-solid-svg-icons' - import { Container, Navigator } from '@/components'; import '@fontsource/roboto/300.css'; @@ -12,32 +9,31 @@ import '@fontsource/roboto/700.css'; import { API } from '@/utils/API'; +import { Table } from '@/components/Table'; export default function App() { - library.add(fas); - - const [version, setVersion] = useState(''); + const [version, setVersion] = useState(''); - const api = new API('http://127.0.0.1:5000'); + const api = new API('http://127.0.0.1:5000'); - useEffect(() => { - api.getVersion().then(res => setVersion(res)); - }, []); + useEffect(() => { + api.getVersion().then(res => setVersion(res)); + }, []); - return (<> + return (<> <Navigator> <a>Home</a> - <a href="settings.html">Settings</a> - <a href="log.html">Log</a> + <a href="settings.html">Settings</a> + <a href="log.html">Log</a> </Navigator> <Container title='Tabella Di Conversione' version={version} > - + <Table api={api}/> </Container> </>); - + } diff --git a/frontend/src/styles/_index.scss b/frontend/src/styles/_index.scss index 477e81b..221bd07 100644 --- a/frontend/src/styles/_index.scss +++ b/frontend/src/styles/_index.scss @@ -1,5 +1,12 @@ @use "sass:color"; +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: url(https://fonts.gstatic.com/s/materialicons/v140/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2'); +} + $base-color: #9FA8DA !default; $background-color: #121212 !default; $text-color: #bababa !default; @@ -30,6 +37,23 @@ a { } } +i { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: 'liga'; + -webkit-font-smoothing: antialiased; + vertical-align:text-bottom; +} + @function elevation($level: 0) { @if $level ==0 { @return $background-color; diff --git a/frontend/src/utils/API.ts b/frontend/src/utils/API.ts index de641e8..67b70a2 100644 --- a/frontend/src/utils/API.ts +++ b/frontend/src/utils/API.ts @@ -1,5 +1,3 @@ -import type {SerieTableEntry} from './types'; - export class API { backend: string; constructor(backend:string) { @@ -16,4 +14,14 @@ export class API { const res = await fetch(this.backend + '/table'); return await res.json() } +} + +export interface SerieTableEntry { + title: string, + absolute: boolean, + seasons: SeasonsTableEntry +} + +export interface SeasonsTableEntry { + [season:string]: string[] } \ No newline at end of file diff --git a/frontend/src/utils/globals.ts b/frontend/src/utils/globals.ts deleted file mode 100644 index a5f6c37..0000000 --- a/frontend/src/utils/globals.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { createTheme } from '@mui/material/styles'; - -export const theme = createTheme({ - palette: { - mode: 'dark', - primary: { - main: '#9FA8DA', - }, - secondary: { - main: '#9FA8DA', - }, - text: { - primary: 'rgba(255,255,255,0.6)', - }, - }, - components: { - MuiAccordionSummary: { - styleOverrides: { - root: { - flexDirection: 'row-reverse', - }, - content: { - justifyContent: 'space-between' - }, - expandIconWrapper: { - marginRight: "10px", - color: 'rgba(255,255,255,0.6)' - }, - } - }, - MuiTabs: { - styleOverrides: { - root: { - borderBottom: '1px solid rgba(255, 255, 255, 0.12)', - } - } - } - } -}); \ No newline at end of file diff --git a/frontend/src/utils/types.ts b/frontend/src/utils/types.ts deleted file mode 100644 index d5d0cfe..0000000 --- a/frontend/src/utils/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface SerieTableEntry { - title: string, - absolute: boolean, - seasons: SeasonsTableEntry -} - -export interface SeasonsTableEntry { - [season:string]: string[] -} \ No newline at end of file From 1e043584dc3399716e2ee5e5d69628a70bf4326b Mon Sep 17 00:00:00 2001 From: MainKronos <lorenzo.chesi@live.it> Date: Sun, 24 Dec 2023 17:34:48 +0100 Subject: [PATCH 21/39] table --- frontend/src/components/Table/index.tsx | 194 +++++++++++++++++++---- frontend/src/components/Table/style.scss | 19 +-- frontend/src/helper/Toast/index.tsx | 22 +++ frontend/src/helper/Toast/style.scss | 14 ++ frontend/src/helper/index.tsx | 1 + frontend/src/pages/home/App.tsx | 2 + frontend/src/styles/_colors.scss | 3 + frontend/src/styles/_fonts.scss | 6 + frontend/src/styles/_functions.scss | 45 ++++++ frontend/src/styles/_index.scss | 55 +------ frontend/src/styles/_inputs.scss | 109 +++++++++++++ frontend/src/utils/API.ts | 66 +++++++- src/components/api/__init__.py | 2 + src/components/api/routes/Settings.py | 7 + src/components/api/routes/Table.py | 7 + src/components/api/routes/Tags.py | 7 + 16 files changed, 461 insertions(+), 98 deletions(-) create mode 100644 frontend/src/helper/Toast/index.tsx create mode 100644 frontend/src/helper/Toast/style.scss create mode 100644 frontend/src/styles/_colors.scss create mode 100644 frontend/src/styles/_fonts.scss create mode 100644 frontend/src/styles/_functions.scss create mode 100644 frontend/src/styles/_inputs.scss diff --git a/frontend/src/components/Table/index.tsx b/frontend/src/components/Table/index.tsx index c59b08b..e0fa178 100644 --- a/frontend/src/components/Table/index.tsx +++ b/frontend/src/components/Table/index.tsx @@ -1,13 +1,11 @@ import { useState, useEffect } from 'react'; -import { Menu } from '@/helper'; +import { Menu, toast } from '@/helper'; import type { ReactNode } from 'react'; import type { API, SerieTableEntry } from '@/utils/API'; import './style.scss'; -const MENU_ID = "CTX" - interface TableProps { api: API } @@ -15,57 +13,143 @@ interface TableProps { export function Table({ api }: TableProps) { const [table, setTable] = useState([] as SerieTableEntry[]); + const [toSync, setToSync] = useState(true); // SYNC DATA useEffect(() => { - api.getTable().then(res => setTable(res)); - }, []); + if(toSync){ + api.getTable().then(res => setTable(res)); + setToSync(false); + } + }, [toSync]); - function test() { console.log('lo') } + return (<> - function displayMenu(event: MouseEvent) { - Menu.show(event, { - 'Copy': test, - 'Edit': test, - 'Delete': test - }) - } + {table.map(entry => + <TableEntry + api={api} + entry={entry} + onUpdate={() => setToSync(true)} + key={entry.title} + /> + )} - return (<> + </>); +} - {table.map(entry => - <details key={entry.title} onContextMenu={displayMenu as any}> - <summary> - <i>movie</i>{entry.title} - </summary> - - <Tabs - labels={Object.keys(entry.seasons)} - contents={Object.values(entry.seasons).map(season => - season.map(link => <a href={link} target="_blank" key={link}>{link}</a>) - )} - /> +interface TableEntryProps { + api: API, + entry: SerieTableEntry + onUpdate: () => void +} - </details> - )} +function TableEntry({api, entry, onUpdate}: TableEntryProps) { + const [editTitle, setEditTitle] = useState(false); + const [editSeasons, setEditSasons] = useState(Object.keys(entry.seasons).map(() => false)); + function setEditSasonByIndex(value:boolean, index:number){ + setEditSasons(editSeasons.map((v,i) => i === index ? value : v)) + } - </>); + return ( + <details> + <summary + onContextMenu={e => Menu.show(e as any, { + 'Copy': () => navigator.clipboard.writeText(entry.title), + 'Edit': () => setEditTitle(true), + 'Delete': () => { + api.deleteSerie(entry.title) + .then(res => { + toast.success(res.message); + onUpdate(); + }) + } + })} + > + <i>movie</i> + <EditableNode + type='text' + defaultValue={entry.title} + activeState={[editTitle, setEditTitle]} + onSubmit={(content) => { + api.editSerie(entry.title, content) + .then(res => { + toast.success(res.message); + onUpdate(); + }) + }} + > + {entry.title} + </EditableNode> + </summary> + + <Tabs + labels={Object.keys(entry.seasons).map((season, index) => + <EditableNode + type='text' + defaultValue={season} + activeState={[ + editSeasons[index], + (state:boolean) => setEditSasonByIndex(state, index) + ]} + onSubmit={(content) => { + api.editSeason(entry.title, season, content) + .then(res => { + toast.success(res.message); + onUpdate(); + }) + }} + > + <span + onContextMenu={e => Menu.show(e as any, { + 'Copy': () => navigator.clipboard.writeText(season), + 'Edit': () => setEditSasonByIndex(true, index), + 'Delete': () => { + api.deleteSeason(entry.title, season) + .then(res => { + toast.success(res.message); + onUpdate(); + }) + } + })} + > + {season} + </span> + </EditableNode> + )} + contents={Object.values(entry.seasons).map(links => + links.map(link => + <a + href={link} + target="_blank" + key={link} + onContextMenu={e => Menu.show(e as any, { + 'Copy': () => navigator.clipboard.writeText(link) + })} + > + {link} + </a> + ) + )} + /> + + </details> + ) } interface TabsProps { labels: ReactNode[], contents: ReactNode[] } -function Tabs({labels, contents}:TabsProps){ +function Tabs({ labels, contents }: TabsProps) { const [tab, setTab] = useState(0); return (<> - <ul> - {labels.map((value, index) => - <li - key={value as string} + <ul> + {labels.map((value, index) => + <li + key={index} onClick={() => setTab(index)} className={tab == index ? 'active' : ''} >{value}</li> @@ -80,3 +164,45 @@ function Tabs({labels, contents}:TabsProps){ </>); } +interface EditableNodeProps<T extends string | number> { + type: string, + activeState: [boolean, (active: boolean) => void], + onSubmit: (content: T) => void, + defaultValue?: T, + placeholder?: string, + pattern?: string, + children: ReactNode +} +function EditableNode<T extends string | number>({ type, defaultValue, activeState, onSubmit, placeholder, pattern, children }: EditableNodeProps<T>) { + const [content, setContent] = useState(defaultValue ?? '' as T); + const [active, setActive] = activeState; + + if (!active) { + return children; + } else { + return ( + <form + onSubmit={(e) => { + e.preventDefault(); + setActive(false); + onSubmit(content); + }} + > + <input + autoFocus={true} + type={type} + value={content} + placeholder={placeholder} + pattern={pattern} + + onChange={e => setContent(e.target.value as T)} + + onBlur={() => setActive(false)} + onKeyDown={e => e.key == 'Escape' && setActive(false)} + /> + </form> + + ); + } + +} \ No newline at end of file diff --git a/frontend/src/components/Table/style.scss b/frontend/src/components/Table/style.scss index 6315ef0..f4279fd 100644 --- a/frontend/src/components/Table/style.scss +++ b/frontend/src/components/Table/style.scss @@ -27,6 +27,8 @@ details { border-bottom: 1px solid $background-color; transition: box-shadow 0.5s; transition: color 0.2s; + display: flex; + align-items: center; >i { padding-right: 5px; @@ -85,18 +87,9 @@ details { } } - button { - padding: 5px; - cursor: pointer; - color: color.change($color: $text-color, $lightness: 40%); - margin: 0; - background-color: transparent; - border: 2px solid color.change($color: $text-color, $lightness: 40%); - border-radius: 4px; - - &:hover { - color: $base-color; - border-color: $base-color; - } + form { + display: inline; + flex-grow: 1; + @extend %input; } } \ No newline at end of file diff --git a/frontend/src/helper/Toast/index.tsx b/frontend/src/helper/Toast/index.tsx new file mode 100644 index 0000000..18a7a06 --- /dev/null +++ b/frontend/src/helper/Toast/index.tsx @@ -0,0 +1,22 @@ +import { ToastContainer as Container } from 'react-toastify'; + +import 'react-toastify/dist/ReactToastify.css'; +import './style.scss'; + +export {toast} from 'react-toastify'; + +export function ToastContainer(){ + return <Container + position="top-right" + autoClose={5000} + hideProgressBar={false} + newestOnTop + closeOnClick + rtl={false} + pauseOnFocusLoss + draggable={false} + pauseOnHover={false} + theme="dark" + /> +} + diff --git a/frontend/src/helper/Toast/style.scss b/frontend/src/helper/Toast/style.scss new file mode 100644 index 0000000..1bc910f --- /dev/null +++ b/frontend/src/helper/Toast/style.scss @@ -0,0 +1,14 @@ +@use "sass:color"; +@use '@/styles/index' as *; + +:root { + --toastify-font-family: 'Roboto'; + --toastify-color-dark: #{elevation(4)}; + --toastify-color-info: #{$base-color}; + --toastify-color-success: #{$base-color}; + --toastify-color-warning: #{$base-color}; + --toastify-color-error: #{$base-color}; + --toastify-spinner-color: #{$base-color}; + --toastify-text-color-dark: #{$text-color}; + --toastify-color-progress-dark: #{$base-color}; +} \ No newline at end of file diff --git a/frontend/src/helper/index.tsx b/frontend/src/helper/index.tsx index 16760d4..e95ca60 100644 --- a/frontend/src/helper/index.tsx +++ b/frontend/src/helper/index.tsx @@ -1 +1,2 @@ +export {ToastContainer, toast} from './Toast'; export {Menu} from './ContextMenu'; \ No newline at end of file diff --git a/frontend/src/pages/home/App.tsx b/frontend/src/pages/home/App.tsx index 2767172..a9903a0 100644 --- a/frontend/src/pages/home/App.tsx +++ b/frontend/src/pages/home/App.tsx @@ -10,6 +10,7 @@ import '@fontsource/roboto/700.css'; import { API } from '@/utils/API'; import { Table } from '@/components/Table'; +import { ToastContainer } from '@/helper'; export default function App() { @@ -34,6 +35,7 @@ export default function App() { > <Table api={api}/> </Container> + <ToastContainer/> </>); } diff --git a/frontend/src/styles/_colors.scss b/frontend/src/styles/_colors.scss new file mode 100644 index 0000000..e897b75 --- /dev/null +++ b/frontend/src/styles/_colors.scss @@ -0,0 +1,3 @@ +$base-color: #9FA8DA !default; +$background-color: #121212 !default; +$text-color: #bababa !default; \ No newline at end of file diff --git a/frontend/src/styles/_fonts.scss b/frontend/src/styles/_fonts.scss new file mode 100644 index 0000000..ae751ef --- /dev/null +++ b/frontend/src/styles/_fonts.scss @@ -0,0 +1,6 @@ +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: url(https://fonts.gstatic.com/s/materialicons/v140/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2'); +} \ No newline at end of file diff --git a/frontend/src/styles/_functions.scss b/frontend/src/styles/_functions.scss new file mode 100644 index 0000000..47a3e21 --- /dev/null +++ b/frontend/src/styles/_functions.scss @@ -0,0 +1,45 @@ +@use "sass:color"; + +@use "./colors" as *; + +@function elevation($level: 0) { + @if $level ==0 { + @return $background-color; + } + + @else if $level ==1 { + @return color.scale($background-color, $lightness: 5%); + } + + @else if $level ==2 { + @return color.scale($background-color, $lightness: 7%); + } + + @else if $level ==3 { + @return color.scale($background-color, $lightness: 8%); + } + + @else if 4 <=$level and $level <=5 { + @return color.scale($background-color, $lightness: 9%); + } + + @else if 6 <=$level and $level <=7 { + @return color.scale($background-color, $lightness: 11%); + } + + @else if 8 <=$level and $level <=11 { + @return color.scale($background-color, $lightness: 12%); + } + + @else if 12 <=$level and $level <=15 { + @return color.scale($background-color, $lightness: 14%); + } + + @else if 16 <=$level and $level <=23 { + @return color.scale($background-color, $lightness: 15%); + } + + @else { + @return color.scale($background-color, $lightness: 16%); + } +} \ No newline at end of file diff --git a/frontend/src/styles/_index.scss b/frontend/src/styles/_index.scss index 221bd07..1673dae 100644 --- a/frontend/src/styles/_index.scss +++ b/frontend/src/styles/_index.scss @@ -1,15 +1,11 @@ @use "sass:color"; -@font-face { - font-family: 'Material Icons'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/materialicons/v140/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2'); -} +@use "./colors" as *; +@use "./fonts" as *; -$base-color: #9FA8DA !default; -$background-color: #121212 !default; -$text-color: #bababa !default; +@forward "./colors"; +@forward "./functions"; +@forward "./inputs"; html { color: $text-color; @@ -54,44 +50,3 @@ i { vertical-align:text-bottom; } -@function elevation($level: 0) { - @if $level ==0 { - @return $background-color; - } - - @else if $level ==1 { - @return color.scale($background-color, $lightness: 5%); - } - - @else if $level ==2 { - @return color.scale($background-color, $lightness: 7%); - } - - @else if $level ==3 { - @return color.scale($background-color, $lightness: 8%); - } - - @else if 4 <=$level and $level <=5 { - @return color.scale($background-color, $lightness: 9%); - } - - @else if 6 <=$level and $level <=7 { - @return color.scale($background-color, $lightness: 11%); - } - - @else if 8 <=$level and $level <=11 { - @return color.scale($background-color, $lightness: 12%); - } - - @else if 12 <=$level and $level <=15 { - @return color.scale($background-color, $lightness: 14%); - } - - @else if 16 <=$level and $level <=23 { - @return color.scale($background-color, $lightness: 15%); - } - - @else { - @return color.scale($background-color, $lightness: 16%); - } -} \ No newline at end of file diff --git a/frontend/src/styles/_inputs.scss b/frontend/src/styles/_inputs.scss new file mode 100644 index 0000000..10d4fbd --- /dev/null +++ b/frontend/src/styles/_inputs.scss @@ -0,0 +1,109 @@ +@use "sass:color"; +@use "./colors" as *; + +%input { + position: relative; + + + > input[type=text], > input[type=password], > input[type=email], > input[type=url], > input[type=number], > input[type=date], > input[type=datetime-local], > select { + appearance: textfield; + background: transparent; + font-size: 1rem; + font-weight: 400; + padding: 5px; + border: 2px solid darken($text-color, 30%); + color: $text-color; + border-radius: 4px; + width: calc(100% - 10px); + outline: none; + + &:disabled{ + border-color: darken($background-color, 3%); + } + + &:invalid { + border-color: color.mix($text-color, red, 65%); + } + + &::placeholder{ + color: $text-color; + } + + &:focus:not(:read-only){ + border-color: $base-color; + + label {color: $base-color;} + } + + + label { + position: absolute; + top: 0; + left: 20px; + padding: 0 10px; + background-color: $background-color; + font-size: 14px; + } + } + + > select { + width: 100%; + } + + > input[type=checkbox]{ + opacity: 0; + width: 0; + height: 0; + margin: 0; + + + label { + user-select: none; + cursor: pointer; + font-size: 14px; + + &::before { + font-weight: bold; + margin-right: 5px; + text-align: center; + font-size: 15px; + color: $base-color; + width: 20px; + height: 20px; + display: inline-block; + content: ' '; + vertical-align: middle; + border-radius: 4px; + border: 2px solid darken($text-color, 30%); + } + } + + &:checked + label::before {content: '✓';} + } +} + +fieldset { + border: 2px solid darken($text-color, 30%); + border-radius: 4px; + + > legend { + padding: 0 10px; + } + + > hr { + width: 100%; + border: 1px solid darken($text-color, 30%); + } +} + +button { + padding: 5px; + cursor: pointer; + color: color.change($color: $text-color, $lightness: 40%); + margin: 0; + background-color: transparent; + border: 2px solid color.change($color: $text-color, $lightness: 40%); + border-radius: 4px; + + &:hover { + color: $base-color; + border-color: $base-color; + } +} \ No newline at end of file diff --git a/frontend/src/utils/API.ts b/frontend/src/utils/API.ts index 67b70a2..0c71210 100644 --- a/frontend/src/utils/API.ts +++ b/frontend/src/utils/API.ts @@ -11,9 +11,73 @@ export class API { } async getTable():Promise<SerieTableEntry[]>{ - const res = await fetch(this.backend + '/table'); + const res = await fetch(this.backend + '/table/'); return await res.json() } + + async addSerie(title:string, absolute:boolean=false) { + const res = await fetch(this.backend + '/table/', { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({title:title, absolute:absolute}) + }); + return await res.json() + } + + async deleteSerie(title:string){ + const res = await fetch(encodeURI(this.backend + `/table/${title}`), { + method: "DELETE", + headers: { + "Content-Type": "application/json" + } + }); + return await res.json() + } + + async editSerie(title:string, newTitle:string){ + const res = await fetch(encodeURI(this.backend + `/table/${title}`), { + method: "PATCH", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({title:newTitle}) + }); + return await res.json() + } + + async addSeason(title:string, season:string){ + const res = await fetch(encodeURI(this.backend + `/table/${title}`), { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({season:season}) + }); + return await res.json() + } + + async deleteSeason(title:string, season:string){ + const res = await fetch(encodeURI(this.backend + `/table/${title}/${season}`), { + method: "DELETE", + headers: { + "Content-Type": "application/json" + } + }); + return await res.json() + } + + async editSeason(title:string, season:string, newSeason:string){ + const res = await fetch(encodeURI(this.backend + `/table/${title}/${season}`), { + method: "PATCH", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({season:newSeason}) + }); + return await res.json() + } } export interface SerieTableEntry { diff --git a/src/components/api/__init__.py b/src/components/api/__init__.py index 013e21d..7b994f1 100644 --- a/src/components/api/__init__.py +++ b/src/components/api/__init__.py @@ -22,6 +22,8 @@ def API(core:Core) -> APIFlask: @app.after_request def cors(res): res.headers['Access-Control-Allow-Origin'] = '*' + res.headers['Access-Control-Allow-Headers'] = '*' + res.headers['Access-Control-Allow-Methods'] = '*' return res api = APIBlueprint('api', __name__, url_prefix='/api', tag='General') diff --git a/src/components/api/routes/Settings.py b/src/components/api/routes/Settings.py index 95f60c3..d2b2cc6 100644 --- a/src/components/api/routes/Settings.py +++ b/src/components/api/routes/Settings.py @@ -5,6 +5,13 @@ def Settings(core:Core) -> APIBlueprint: route = APIBlueprint('settings', __name__, url_prefix='/settings', tag='Settings') + + @route.after_request + def cors(res): + res.headers['Access-Control-Allow-Origin'] = '*' + res.headers['Access-Control-Allow-Headers'] = '*' + res.headers['Access-Control-Allow-Methods'] = '*' + return res @route.get('/') def get_settings(): diff --git a/src/components/api/routes/Table.py b/src/components/api/routes/Table.py index 6fd51a0..1a1dbfc 100644 --- a/src/components/api/routes/Table.py +++ b/src/components/api/routes/Table.py @@ -4,6 +4,13 @@ def Table(core:Core) -> APIBlueprint: route = APIBlueprint('table', __name__, url_prefix='/table', tag='Table') + + @route.after_request + def cors(res): + res.headers['Access-Control-Allow-Origin'] = '*' + res.headers['Access-Control-Allow-Headers'] = '*' + res.headers['Access-Control-Allow-Methods'] = '*' + return res @route.get("/") def get_table() -> list[dict]: diff --git a/src/components/api/routes/Tags.py b/src/components/api/routes/Tags.py index 1f4c304..b30982c 100644 --- a/src/components/api/routes/Tags.py +++ b/src/components/api/routes/Tags.py @@ -5,6 +5,13 @@ def Tags(core:Core) -> APIBlueprint: route = APIBlueprint('tags', __name__, url_prefix='/tags', tag='Tags') + + @route.after_request + def cors(res): + res.headers['Access-Control-Allow-Origin'] = '*' + res.headers['Access-Control-Allow-Headers'] = '*' + res.headers['Access-Control-Allow-Methods'] = '*' + return res @route.get('/') def get_tags(): From 94f573e23050398cb4322a6eec8693b7dfa7de9c Mon Sep 17 00:00:00 2001 From: MainKronos <lorenzo.chesi@live.it> Date: Sun, 24 Dec 2023 21:46:48 +0100 Subject: [PATCH 22/39] editor --- frontend/src/components/Table/index.tsx | 88 ++++++++++++++++++------- frontend/src/utils/API.ts | 86 ++++++++++++++++-------- tests/database/table.json | 2 +- 3 files changed, 126 insertions(+), 50 deletions(-) diff --git a/frontend/src/components/Table/index.tsx b/frontend/src/components/Table/index.tsx index e0fa178..b0e43a9 100644 --- a/frontend/src/components/Table/index.tsx +++ b/frontend/src/components/Table/index.tsx @@ -45,11 +45,23 @@ interface TableEntryProps { function TableEntry({api, entry, onUpdate}: TableEntryProps) { const [editTitle, setEditTitle] = useState(false); - const [editSeasons, setEditSasons] = useState(Object.keys(entry.seasons).map(() => false)); - - function setEditSasonByIndex(value:boolean, index:number){ - setEditSasons(editSeasons.map((v,i) => i === index ? value : v)) - } + const [editSeasons, setEditSasons] = useState( + Object.fromEntries( + Object.keys(entry.seasons) + .map((season) => [season, false]) + ) + ); + const [editLinks, setEditLinks] = useState( + Object.fromEntries( + Object.entries(entry.seasons) + .map(([season, links]) => [ + season, + Object.fromEntries( + links.map((link) => [link, false]) + ) + ]) + ) + ); return ( <details> @@ -62,7 +74,7 @@ function TableEntry({api, entry, onUpdate}: TableEntryProps) { .then(res => { toast.success(res.message); onUpdate(); - }) + }); } })} > @@ -76,7 +88,7 @@ function TableEntry({api, entry, onUpdate}: TableEntryProps) { .then(res => { toast.success(res.message); onUpdate(); - }) + }); }} > {entry.title} @@ -84,26 +96,26 @@ function TableEntry({api, entry, onUpdate}: TableEntryProps) { </summary> <Tabs - labels={Object.keys(entry.seasons).map((season, index) => + labels={Object.keys(entry.seasons).map(season => <EditableNode type='text' defaultValue={season} activeState={[ - editSeasons[index], - (state:boolean) => setEditSasonByIndex(state, index) + editSeasons[season], + (state:boolean) => setEditSasons({...editSeasons, [season]:state}) ]} onSubmit={(content) => { api.editSeason(entry.title, season, content) .then(res => { toast.success(res.message); onUpdate(); - }) + }); }} > <span onContextMenu={e => Menu.show(e as any, { 'Copy': () => navigator.clipboard.writeText(season), - 'Edit': () => setEditSasonByIndex(true, index), + 'Edit': () => setEditSasons({...editSeasons, [season]:true}), 'Delete': () => { api.deleteSeason(entry.title, season) .then(res => { @@ -117,18 +129,50 @@ function TableEntry({api, entry, onUpdate}: TableEntryProps) { </span> </EditableNode> )} - contents={Object.values(entry.seasons).map(links => + contents={Object.entries(entry.seasons).map(([season, links]) => links.map(link => - <a - href={link} - target="_blank" - key={link} - onContextMenu={e => Menu.show(e as any, { - 'Copy': () => navigator.clipboard.writeText(link) - })} + <EditableNode + type='text' + defaultValue={link} + activeState={[ + editLinks[season][link], + (state:boolean) => setEditLinks({ + ...editLinks, [season]:{ + ...editLinks[season], [link]: state + } + }) + ]} + onSubmit={(content) => { + api.editLink(entry.title, season, link, content) + .then(res => { + toast.success(res.message); + onUpdate(); + }); + }} > - {link} - </a> + <a + href={link} + target="_blank" + key={link} + onContextMenu={e => Menu.show(e as any, { + 'Copy': () => navigator.clipboard.writeText(link), + 'Edit': () => setEditLinks({ + ...editLinks, [season]:{ + ...editLinks[season], [link]: true + } + }), + 'Delete': () => { + api.deleteLink(entry.title, season, link) + .then(res => { + toast.success(res.message); + onUpdate(); + }); + } + })} + > + {link} + </a> + </EditableNode> ) )} /> diff --git a/frontend/src/utils/API.ts b/frontend/src/utils/API.ts index 0c71210..88511ca 100644 --- a/frontend/src/utils/API.ts +++ b/frontend/src/utils/API.ts @@ -1,32 +1,32 @@ export class API { - backend: string; - constructor(backend:string) { - this.backend = backend + '/api'; - } + backend: string; + constructor(backend: string) { + this.backend = backend + '/api'; + } - async getVersion():Promise<string>{ - const res = await fetch(this.backend + '/version'); - const data = await res.json() - return data.version; - } + async getVersion(): Promise<string> { + const res = await fetch(this.backend + '/version'); + const data = await res.json() + return data.version; + } - async getTable():Promise<SerieTableEntry[]>{ - const res = await fetch(this.backend + '/table/'); - return await res.json() - } + async getTable(): Promise<SerieTableEntry[]> { + const res = await fetch(this.backend + '/table/'); + return await res.json() + } - async addSerie(title:string, absolute:boolean=false) { + async addSerie(title: string, absolute: boolean = false) { const res = await fetch(this.backend + '/table/', { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({title:title, absolute:absolute}) + body: JSON.stringify({ title: title, absolute: absolute }) }); return await res.json() } - async deleteSerie(title:string){ + async deleteSerie(title: string) { const res = await fetch(encodeURI(this.backend + `/table/${title}`), { method: "DELETE", headers: { @@ -36,29 +36,29 @@ export class API { return await res.json() } - async editSerie(title:string, newTitle:string){ + async editSerie(title: string, newTitle: string) { const res = await fetch(encodeURI(this.backend + `/table/${title}`), { method: "PATCH", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({title:newTitle}) + body: JSON.stringify({ title: newTitle }) }); return await res.json() } - async addSeason(title:string, season:string){ + async addSeason(title: string, season: string) { const res = await fetch(encodeURI(this.backend + `/table/${title}`), { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({season:season}) + body: JSON.stringify({ season: season }) }); return await res.json() } - async deleteSeason(title:string, season:string){ + async deleteSeason(title: string, season: string) { const res = await fetch(encodeURI(this.backend + `/table/${title}/${season}`), { method: "DELETE", headers: { @@ -68,24 +68,56 @@ export class API { return await res.json() } - async editSeason(title:string, season:string, newSeason:string){ + async editSeason(title: string, season: string, newSeason: string) { const res = await fetch(encodeURI(this.backend + `/table/${title}/${season}`), { method: "PATCH", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({season:newSeason}) + body: JSON.stringify({ season: newSeason }) + }); + return await res.json() + } + + async addLinks(title: string, season: string, links: string[]) { + const res = await fetch(encodeURI(this.backend + `/table/${title}/${season}`), { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ links: links }) + }); + return await res.json() + } + + async deleteLink(title:string, season:string, link:string){ + const res = await fetch(encodeURI(this.backend + `/table/${title}/${season}/${link}`), { + method: "DELETE", + headers: { + "Content-Type": "application/json" + } + }); + return await res.json() + } + + async editLink(title:string, season:string, link:string, newLink: string){ + const res = await fetch(encodeURI(this.backend + `/table/${title}/${season}/${link}`), { + method: "PATCH", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ link: newLink }) }); return await res.json() } } export interface SerieTableEntry { - title: string, - absolute: boolean, - seasons: SeasonsTableEntry + title: string, + absolute: boolean, + seasons: SeasonsTableEntry } export interface SeasonsTableEntry { - [season:string]: string[] + [season: string]: string[] } \ No newline at end of file diff --git a/tests/database/table.json b/tests/database/table.json index 338e004..4541b9f 100644 --- a/tests/database/table.json +++ b/tests/database/table.json @@ -1 +1 @@ -[{"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/86.kdabE", "https://www.animeworld.tv/play/86-eighty-six-2.cWHjp"]}, "title": "86: Eighty Six"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/kakkou-no-iinazuke.Q6ykQ"]}, "title": "A Couple of Cuckoos"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/aharen-san-wa-hakarenai.SehbY"]}, "title": "Aharen-san wa Hakarenai"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/akebi-chan-no-sailor-fuku.6t2t7"]}, "title": "Akebi's Sailor Uniform"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/arifureta-shokugyou-de-sekai-saikyou-2.ihBQk"]}, "title": "Arifureta: From Commonplace to World's Strongest"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/ascendance-of-a-bookworm.paCPb", "https://www.animeworld.tv/play/ascendance-of-a-bookworm-2.Q0Rrm", "https://www.animeworld.tv/play/ascendance-of-a-bookworm-3.l5fru"]}, "title": "Ascendance of a Bookworm"}, {"absolute": false, "seasons": {"4": ["https://www.animeworld.tv/play/lattacco-dei-giganti-4.OH0AI", "https://www.animeworld.tv/play/lattacco-dei-giganti-4-parte-2.IsVqa"]}, "title": "Attack on Titan"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/bakuten.MfCW1"]}, "title": "Backflip!!"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/shin-no-nakama-ja-nai-to-yuusha-no-party-wo-oidasareta-node-henkyou-de-slow-life-suru-koto-ni-shima.JpINB"]}, "title": "Banished from the Hero's Party, I Decided to Live a Quiet Life in the Countryside"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/deatte-5-byou-de-battle.njUd3"]}, "title": "Battle Game in 5 Seconds"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/beastars-2.Jdlq8"]}, "title": "Beastars"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/blue-period.S2fQi"]}, "title": "Blue Period"}, {"title": "Bocchi the Rock!", "seasons": {"1": ["https://www.animeworld.tv/play/bocchi-the-rock.LRnn4"]}, "absolute": false}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/jaku-chara-tomozaki-kun.RDPHq"]}, "title": "Bottom-Tier Character Tomozaki"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/180-byou-de-kimi-no-mimi-wo-shiawase-ni-dekiru-ka.N9OZX"]}, "title": "Can I Make Your Ears Happy in 180 Seconds?"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/cells-at-work-2.pE6uS"]}, "title": "Cells at Work!"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/cells-at-work-code-black.w4Eor"]}, "title": "Cells at Work! Code Black"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/sentouin-hakenshimasu.6whgN"]}, "title": "Combatants Will Be Dispatched!"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/demon-slayer-kimetsu-no-yaiba-2.YIoa-"], "3": ["https://www.animeworld.tv/play/demon-slayer-kimetsu-no-yaiba-entertainment-district-arc.IFmFs"]}, "title": "Demon Slayer: Kimetsu no Yaiba"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/cheat-kusushi-no-slow-life-isekai-ni-tsukurou-drugstore.sfvL-"]}, "title": "Drug Store in Another World: The Slow Life of a Cheat Pharmacist"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/edens-zero.A3qCl"]}, "title": "EDENS ZERO"}, {"absolute": false, "seasons": {"3": ["https://www.animeworld.tv/play/fruits-basket-the-final.frf-n"]}, "title": "Fruits Basket (2019)"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/kyuukyoku-shinka-shita-full-dive-rpg-ga-genjitsu-yori-mo-kusoge-dattara.-nptV"]}, "title": "Full Dive: This Ultimate Next-Gen Full Dive RPG Is Even Shittier Than Real Life!"}, {"title": "GOBLIN SLAYER", "absolute": false, "seasons": {"2": ["https://www.animeworld.so/play/goblin-slayer.cSbFz"]}}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/gunslinger-girl.vIUhE"]}, "title": "Gunslinger Girl"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/haibane-renmei.4yJAg"]}, "title": "Haibane Renmei"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/higurashi-no-naku-koro-ni-2020.DoJuQ"], "2": ["https://www.animeworld.tv/play/higurashi-no-naku-koro-ni-sotsu.j7iq7"]}, "title": "Higurashi: When They Cry \u2013 GOU"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/horimiya.Mse3-"]}, "title": "Horimiya"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/genjitsu-shugi-yuusha-no-oukoku-saikenki.LpDFM", "https://www.animeworld.tv/play/genjitsu-shugi-yuusha-no-oukoku-saikenki-parte-2.5cDX2"]}, "title": "How a Realist Hero Rebuilt the Kingdom"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/yuusha-yamemasu.pUnEf"]}, "title": "I'm Quitting Heroing"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/tsuki-to-laika-to-nosferatu.oon50"]}, "title": "Irina: The Vampire Cosmonaut"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/kemono-jihen.xb1V1"]}, "title": "Kemono Jihen"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/komi-cant-communicate-2.HgYCo"]}, "title": "Komi Can't Communicate"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/fantasy-bishoujo-juniku-ojisan-to.ZhCYX"]}, "title": "Life With an Ordinary Guy Who Reincarnated Into a Total Fantasy Knockout"}, {"absolute": false, "seasons": {"3": ["https://www.animeworld.tv/play/log-horizon-3.GGizW"]}, "title": "Log Horizon"}, {"title": "Made in Abyss", "seasons": {"2": ["https://www.animeworld.tv/play/made-in-abyss-2.T7h4U"]}, "absolute": false}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/mieruko-chan.M7FrP"]}, "title": "Mieruko-chan"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/moriarty-the-patriot.m9UeA", "https://www.animeworld.tv/play/moriarty-the-patriot-2.la8cg"]}, "title": "Moriarty the Patriot"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/megami-ryou-no-ryoubo-kun.48hLw"]}, "title": "Mother of the Goddess' Dormitory"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/mushoku-tensei-isekai-ittara-honki-dasu.FwbNc", "https://www.animeworld.tv/play/mushoku-tensei-isekai-ittara-honki-dasu-2.UNNMY"]}, "title": "Mushoku Tensei: Jobless Reincarnation"}, {"title": "My Happy Marriage", "absolute": false, "seasons": {"1": ["https://www.animeworld.so/play/watashi-no-shiawase-na-kekkon.vZoGM"]}}, {"absolute": false, "seasons": {"5": ["https://www.animeworld.tv/play/boku-no-hero-academia-5.JosMp"]}, "title": "My Hero Academia"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/my-life-as-a-villainess-all-routes-lead-to-doom-2.8RcIg"]}, "title": "My Next Life as a Villainess: All Routes Lead to Doom!"}, {"absolute": true, "seasons": {"absolute": ["https://www.animeworld.tv/play/one-piece-subita.qzG-L"]}, "title": "One Piece"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/orient.myAXD"]}, "title": "Orient"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/osananajimi-ga-zettai-ni-makenai-love-comedy.J2uW-"]}, "title": "Osamake: Romcom Where the Childhood Friend Won't Lose"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/puraore-pride-of-orange.OnxR3"]}, "title": "PuraOre! ~Pride of Orange~"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/ousama-ranking.W-Hq7"]}, "title": "Ranking of Kings"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/re-main.99hiG"]}, "title": "Re-Main"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/kaifuku-jutsushi-no-yarinaoshi.ol0ZN"]}, "title": "Redo of Healer"}, {"title": "Reincarnated as a Sword", "seasons": {"1": ["https://www.animeworld.tv/play/tensei-shitara-ken-deshita.ABj8v"]}, "absolute": false}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/bokutachi-no-remake.lB6Bh"]}, "title": "Remake Our Life!"}, {"title": "Rent-a-Girlfriend", "seasons": {"2": ["https://www.animeworld.tv/play/rent-a-girlfriend-2.qRp7e"]}, "absolute": false}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/baraou-no-souretsu.LVB1N"]}, "title": "Requiem of the Rose King"}, {"title": "SPY x FAMILY", "seasons": {"1": ["https://www.animeworld.tv/play/spy-x-family.iyICA"]}, "absolute": false}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/sasaki-to-miyano.mij7d"]}, "title": "Sasaki and Miyano"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/kuzu-no-honkai.MONpm"]}, "title": "Scum's Wish"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/seirei-gensouki.NgIKG/"]}, "title": "Seirei Gensouki: Spirit Chronicles"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/shadows-house.rCg-x"]}, "title": "Shadows House"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/sonny-boy.7Utce"]}, "title": "Sonny Boy"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/summertime-render.GDU38"]}, "title": "Summer Time Rendering"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/tatoeba-last-dungeon-mae-no-mura-no-shounen-ga-joban-no-machi-de-kurasu-youna-monogatari.EqCF-"]}, "title": "Suppose a Kid From the Last Dungeon Boonies Moved to a Starter Town?"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/taishou-otome-otogibanashi.rMAeY"]}, "title": "Taisho Otome Fairy Tale"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/karakai-jouzu-no-takagi-san.sKOCK"], "2": ["https://www.animeworld.tv/play/karakai-jouzu-no-takagi-san-2.M3Myw"], "3": ["https://www.animeworld.tv/play/karakai-jouzu-no-takagi-san-3.0ETgJ"]}, "title": "Teasing Master Takagi-san"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/tensei-shitara-slime-datta-ken-2.jregU", "https://www.animeworld.tv/play/tensei-shitara-slime-datta-ken-2nd-season-part-2.0K6vE"]}, "title": "That Time I Got Reincarnated as a Slime"}, {"title": "The Apothecary Diaries", "absolute": false, "seasons": {"1": ["https://www.animeworld.so/play/the-apothecary-diaries.3KzbH"]}}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/shiroi-suna-no-aquatope.wCt0J"]}, "title": "The Aquatope on White Sand"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/vanitas-no-carte.Z2zwI"]}, "title": "The Case Study of Vanitas"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/the-detective-is-already-dead.wWFQN"]}, "title": "The Detective Is Already Dead"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/the-duke-of-death-and-his-maid.YkcXB"]}, "title": "The Duke of Death and His Maid"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/meikyuu-black-company.JOXcZ"]}, "title": "The Dungeon of Black Company"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/tensai-ouji-no-akaji-kokka-saisei-jutsu.iwtzm"]}, "title": "The Genius Prince's Guide to Raising a Nation Out of Debt"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/the-heike-story.zQCZz"]}, "title": "The Heike Story"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/heion-sedai-no-idaten-tachi.1TB--"]}, "title": "The Idaten Deities Know Only Peace"}, {"title": "The Most Heretical Last Boss Queen: From Villainess to Savior", "absolute": false, "seasons": {"1": ["https://www.animeworld.so/play/the-most-heretical-last-boss-queen-from-villainess-to-savior.6KetrA"]}}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/the-promised-neverland-2.HPGKr"]}, "title": "The Promised Neverland"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/the-rising-of-the-shield-hero-2.lgwm2"]}, "title": "The Rising of the Shield Hero"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/the-saints-magic-power-is-omnipotent.iKqoM"]}, "title": "The Saint's Magic Power Is Omnipotent"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/the-worlds-finest-assassin-gets-reincarnated-in-another-world-as-an-aristocrat.WFG1J"]}, "title": "The World's Finest Assassin Gets Reincarnated in Another World as an Aristocrat"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/fumetsu-no-anata-e.tY9Ls"]}, "title": "To Your Eternity"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/tokyo-revengers.Zkczy"]}, "title": "Tokyo Revengers"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/tsuki-ga-michibiku-isekai-douchuu.qTZiB"]}, "title": "Tsukimichi -Moonlit Fantasy-"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/vivy-fluorite-eyes-song.3vh3i"]}, "title": "Vivy -Fluorite Eye's Song-"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/wonder-egg-priority.8k2by"]}, "title": "Wonder Egg Priority"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/world-trigger.L3ftn"], "2": ["https://www.animeworld.tv/play/world-trigger-2.VHfyT"], "3": ["https://www.animeworld.tv/play/world-trigger-3.1tSD0"]}, "title": "World Trigger"}] \ No newline at end of file +[{"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/86-eighty-six-2.cWHjp"]}, "title": "86: Eighty Six"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/kakkou-no-iinazuke.Q6ykQ"]}, "title": "A Couple of Cuckoos"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/aharen-san-wa-hakarenai.SehbY"]}, "title": "Aharen-san wa Hakarenai"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/akebi-chan-no-sailor-fuku.6t2t7"]}, "title": "Akebi's Sailor Uniform"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/arifureta-shokugyou-de-sekai-saikyou-2.ihBQk"]}, "title": "Arifureta: From Commonplace to World's Strongest"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/ascendance-of-a-bookworm.paCPb", "https://www.animeworld.tv/play/ascendance-of-a-bookworm-2.Q0Rrm", "https://www.animeworld.tv/play/ascendance-of-a-bookworm-3.l5fru"]}, "title": "Ascendance of a Bookworm"}, {"absolute": false, "seasons": {"4": ["https://www.animeworld.tv/play/lattacco-dei-giganti-4.OH0AI", "https://www.animeworld.tv/play/lattacco-dei-giganti-4-parte-2.IsVqa"]}, "title": "Attack on Titan"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/bakuten.MfCW1"]}, "title": "Backflip!!"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/shin-no-nakama-ja-nai-to-yuusha-no-party-wo-oidasareta-node-henkyou-de-slow-life-suru-koto-ni-shima.JpINB"]}, "title": "Banished from the Hero's Party, I Decided to Live a Quiet Life in the Countryside"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/deatte-5-byou-de-battle.njUd3"]}, "title": "Battle Game in 5 Seconds"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/beastars-2.Jdlq8"]}, "title": "Beastars"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/blue-period.S2fQi"]}, "title": "Blue Period"}, {"title": "Bocchi the Rock!", "seasons": {"1": ["https://www.animeworld.tv/play/bocchi-the-rock.LRnn4"]}, "absolute": false}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/jaku-chara-tomozaki-kun.RDPHq"]}, "title": "Bottom-Tier Character Tomozaki"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/180-byou-de-kimi-no-mimi-wo-shiawase-ni-dekiru-ka.N9OZX"]}, "title": "Can I Make Your Ears Happy in 180 Seconds?"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/cells-at-work-2.pE6uS"]}, "title": "Cells at Work!"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/cells-at-work-code-black.w4Eor"]}, "title": "Cells at Work! Code Black"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/sentouin-hakenshimasu.6whgN"]}, "title": "Combatants Will Be Dispatched!"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/demon-slayer-kimetsu-no-yaiba-2.YIoa-"], "3": ["https://www.animeworld.tv/play/demon-slayer-kimetsu-no-yaiba-entertainment-district-arc.IFmFs"]}, "title": "Demon Slayer: Kimetsu no Yaiba"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/cheat-kusushi-no-slow-life-isekai-ni-tsukurou-drugstore.sfvL-"]}, "title": "Drug Store in Another World: The Slow Life of a Cheat Pharmacist"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/edens-zero.A3qCl"]}, "title": "EDENS ZERO"}, {"absolute": false, "seasons": {"3": ["https://www.animeworld.tv/play/fruits-basket-the-final.frf-n"]}, "title": "Fruits Basket (2019)"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/kyuukyoku-shinka-shita-full-dive-rpg-ga-genjitsu-yori-mo-kusoge-dattara.-nptV"]}, "title": "Full Dive: This Ultimate Next-Gen Full Dive RPG Is Even Shittier Than Real Life!"}, {"title": "GOBLIN SLAYER", "absolute": false, "seasons": {"2": ["https://www.animeworld.so/play/goblin-slayer.cSbFz"]}}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/gunslinger-girl.vIUhE"]}, "title": "Gunslinger Girl"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/haibane-renmei.4yJAg"]}, "title": "Haibane Renmei"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/higurashi-no-naku-koro-ni-2020.DoJuQ"], "2": ["https://www.animeworld.tv/play/higurashi-no-naku-koro-ni-sotsu.j7iq7"]}, "title": "Higurashi: When They Cry \u2013 GOU"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/horimiya.Mse3-"]}, "title": "Horimiya"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/genjitsu-shugi-yuusha-no-oukoku-saikenki.LpDFM", "https://www.animeworld.tv/play/genjitsu-shugi-yuusha-no-oukoku-saikenki-parte-2.5cDX2"]}, "title": "How a Realist Hero Rebuilt the Kingdom"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/yuusha-yamemasu.pUnEf"]}, "title": "I'm Quitting Heroing"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/tsuki-to-laika-to-nosferatu.oon50"]}, "title": "Irina: The Vampire Cosmonaut"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/kemono-jihen.xb1V1"]}, "title": "Kemono Jihen"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/komi-cant-communicate-2.HgYCo"]}, "title": "Komi Can't Communicate"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/fantasy-bishoujo-juniku-ojisan-to.ZhCYX"]}, "title": "Life With an Ordinary Guy Who Reincarnated Into a Total Fantasy Knockout"}, {"absolute": false, "seasons": {"3": ["https://www.animeworld.tv/play/log-horizon-3.GGizW"]}, "title": "Log Horizon"}, {"title": "Made in Abyss", "seasons": {"2": ["https://www.animeworld.tv/play/made-in-abyss-2.T7h4U"]}, "absolute": false}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/mieruko-chan.M7FrP"]}, "title": "Mieruko-chan"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/moriarty-the-patriot.m9UeA", "https://www.animeworld.tv/play/moriarty-the-patriot-2.la8cg"]}, "title": "Moriarty the Patriot"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/megami-ryou-no-ryoubo-kun.48hLw"]}, "title": "Mother of the Goddess' Dormitory"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/mushoku-tensei-isekai-ittara-honki-dasu.FwbNc", "https://www.animeworld.tv/play/mushoku-tensei-isekai-ittara-honki-dasu-2.UNNMY"]}, "title": "Mushoku Tensei: Jobless Reincarnation"}, {"title": "My Happy Marriage", "absolute": false, "seasons": {"1": ["https://www.animeworld.so/play/watashi-no-shiawase-na-kekkon.vZoGM"]}}, {"absolute": false, "seasons": {"5": ["https://www.animeworld.tv/play/boku-no-hero-academia-5.JosMp"]}, "title": "My Hero Academia"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/my-life-as-a-villainess-all-routes-lead-to-doom-2.8RcIg"]}, "title": "My Next Life as a Villainess: All Routes Lead to Doom!"}, {"absolute": true, "seasons": {"absolute": ["https://www.animeworld.tv/play/one-piece-subita.qzG-L"]}, "title": "One Piece"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/orient.myAXD"]}, "title": "Orient"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/osananajimi-ga-zettai-ni-makenai-love-comedy.J2uW-"]}, "title": "Osamake: Romcom Where the Childhood Friend Won't Lose"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/puraore-pride-of-orange.OnxR3"]}, "title": "PuraOre! ~Pride of Orange~"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/ousama-ranking.W-Hq7"]}, "title": "Ranking of Kings"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/re-main.99hiG"]}, "title": "Re-Main"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/kaifuku-jutsushi-no-yarinaoshi.ol0ZN"]}, "title": "Redo of Healer"}, {"title": "Reincarnated as a Sword", "seasons": {"1": ["https://www.animeworld.tv/play/tensei-shitara-ken-deshita.ABj8v"]}, "absolute": false}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/bokutachi-no-remake.lB6Bh"]}, "title": "Remake Our Life!"}, {"title": "Rent-a-Girlfriend", "seasons": {"2": ["https://www.animeworld.tv/play/rent-a-girlfriend-2.qRp7e"]}, "absolute": false}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/baraou-no-souretsu.LVB1N"]}, "title": "Requiem of the Rose King"}, {"title": "SPY x FAMILY", "seasons": {"1": ["https://www.animeworld.tv/play/spy-x-family.iyICA"]}, "absolute": false}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/sasaki-to-miyano.mij7d"]}, "title": "Sasaki and Miyano"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/kuzu-no-honkai.MONpm"]}, "title": "Scum's Wish"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/seirei-gensouki.NgIKG/"]}, "title": "Seirei Gensouki: Spirit Chronicles"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/shadows-house.rCg-x"]}, "title": "Shadows House"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/sonny-boy.7Utce"]}, "title": "Sonny Boy"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/summertime-render.GDU38"]}, "title": "Summer Time Rendering"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/tatoeba-last-dungeon-mae-no-mura-no-shounen-ga-joban-no-machi-de-kurasu-youna-monogatari.EqCF-"]}, "title": "Suppose a Kid From the Last Dungeon Boonies Moved to a Starter Town?"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/taishou-otome-otogibanashi.rMAeY"]}, "title": "Taisho Otome Fairy Tale"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/karakai-jouzu-no-takagi-san.sKOCK"], "2": ["https://www.animeworld.tv/play/karakai-jouzu-no-takagi-san-2.M3Myw"], "3": ["https://www.animeworld.tv/play/karakai-jouzu-no-takagi-san-3.0ETgJ"]}, "title": "Teasing Master Takagi-san"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/tensei-shitara-slime-datta-ken-2.jregU", "https://www.animeworld.tv/play/tensei-shitara-slime-datta-ken-2nd-season-part-2.0K6vE"]}, "title": "That Time I Got Reincarnated as a Slime"}, {"title": "The Apothecary Diaries", "absolute": false, "seasons": {"1": ["https://www.animeworld.so/play/the-apothecary-diaries.3KzbH"]}}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/shiroi-suna-no-aquatope.wCt0J"]}, "title": "The Aquatope on White Sand"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/vanitas-no-carte.Z2zwI"]}, "title": "The Case Study of Vanitas"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/the-detective-is-already-dead.wWFQN"]}, "title": "The Detective Is Already Dead"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/the-duke-of-death-and-his-maid.YkcXB"]}, "title": "The Duke of Death and His Maid"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/meikyuu-black-company.JOXcZ"]}, "title": "The Dungeon of Black Company"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/tensai-ouji-no-akaji-kokka-saisei-jutsu.iwtzm"]}, "title": "The Genius Prince's Guide to Raising a Nation Out of Debt"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/the-heike-story.zQCZz"]}, "title": "The Heike Story"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/heion-sedai-no-idaten-tachi.1TB--"]}, "title": "The Idaten Deities Know Only Peace"}, {"title": "The Most Heretical Last Boss Queen: From Villainess to Savior", "absolute": false, "seasons": {"1": ["https://www.animeworld.so/play/the-most-heretical-last-boss-queen-from-villainess-to-savior.6KetrA"]}}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/the-promised-neverland-2.HPGKr"]}, "title": "The Promised Neverland"}, {"absolute": false, "seasons": {"2": ["https://www.animeworld.tv/play/the-rising-of-the-shield-hero-2.lgwm2"]}, "title": "The Rising of the Shield Hero"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/the-saints-magic-power-is-omnipotent.iKqoM"]}, "title": "The Saint's Magic Power Is Omnipotent"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/the-worlds-finest-assassin-gets-reincarnated-in-another-world-as-an-aristocrat.WFG1J"]}, "title": "The World's Finest Assassin Gets Reincarnated in Another World as an Aristocrat"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/fumetsu-no-anata-e.tY9Ls"]}, "title": "To Your Eternity"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/tokyo-revengers.Zkczy"]}, "title": "Tokyo Revengers"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/tsuki-ga-michibiku-isekai-douchuu.qTZiB"]}, "title": "Tsukimichi -Moonlit Fantasy-"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/vivy-fluorite-eyes-song.3vh3i"]}, "title": "Vivy -Fluorite Eye's Song-"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/wonder-egg-priority.8k2by"]}, "title": "Wonder Egg Priority"}, {"absolute": false, "seasons": {"1": ["https://www.animeworld.tv/play/world-trigger.L3ftn"], "2": ["https://www.animeworld.tv/play/world-trigger-2.VHfyT"], "3": ["https://www.animeworld.tv/play/world-trigger-3.1tSD0"]}, "title": "World Trigger"}] \ No newline at end of file From ecabad798f9b1cb774b602d14a1ad2481120b33f Mon Sep 17 00:00:00 2001 From: MainKronos <lorenzo.chesi@live.it> Date: Thu, 28 Dec 2023 17:18:57 +0100 Subject: [PATCH 23/39] table style --- frontend/src/components/Badge/index.tsx | 10 + frontend/src/components/Badge/style.scss | 13 + frontend/src/components/Container/index.tsx | 11 +- frontend/src/components/Container/style.scss | 15 ++ frontend/src/components/Modal/index.tsx | 25 ++ frontend/src/components/Modal/style.scss | 37 +++ frontend/src/components/Navigator/index.tsx | 11 +- frontend/src/components/Navigator/style.scss | 15 -- frontend/src/components/Table/index.tsx | 237 ++++++++++++++----- frontend/src/components/Table/style.scss | 65 ++++- frontend/src/components/index.tsx | 8 +- frontend/src/pages/home/App.tsx | 13 +- frontend/src/pages/home/style.scss | 13 + frontend/src/styles/_index.scss | 1 + frontend/src/styles/_inputs.scss | 21 +- 15 files changed, 388 insertions(+), 107 deletions(-) create mode 100644 frontend/src/components/Badge/index.tsx create mode 100644 frontend/src/components/Badge/style.scss create mode 100644 frontend/src/components/Modal/index.tsx create mode 100644 frontend/src/components/Modal/style.scss create mode 100644 frontend/src/pages/home/style.scss diff --git a/frontend/src/components/Badge/index.tsx b/frontend/src/components/Badge/index.tsx new file mode 100644 index 0000000..383ad14 --- /dev/null +++ b/frontend/src/components/Badge/index.tsx @@ -0,0 +1,10 @@ +import './style.scss'; + +interface BadgeProps { + title:string +} +export function Badge({title}:BadgeProps){ + return ( + <span className='badge'>{title}</span> + ) +} \ No newline at end of file diff --git a/frontend/src/components/Badge/style.scss b/frontend/src/components/Badge/style.scss new file mode 100644 index 0000000..8865428 --- /dev/null +++ b/frontend/src/components/Badge/style.scss @@ -0,0 +1,13 @@ +@use "sass:color"; +@use '@/styles/index' as *; + +span.badge { + // float: right; + margin-left: auto; + background-color: $base-color; + padding: 3px; + border-radius: 3px; + color: white !important; + font-weight: 300; + font-weight: bold; +} \ No newline at end of file diff --git a/frontend/src/components/Container/index.tsx b/frontend/src/components/Container/index.tsx index 0055134..738ac8e 100644 --- a/frontend/src/components/Container/index.tsx +++ b/frontend/src/components/Container/index.tsx @@ -5,16 +5,23 @@ import './style.scss'; interface ContainerProps { title: string, version: string, - children?: ReactNode + navigatorState?: [boolean, (state:boolean)=> void] + children?: ReactNode, } -export function Container({title, version, children}:ContainerProps) { +export function Container({title, version, navigatorState, children}:ContainerProps) { + return (<> <header> <h1>{title}</h1> <a className="btn" id="donate" href="https://github.com/sponsors/MainKronos" target="_blank"> <i>favorite</i> </a> + {navigatorState && + <button onClick={() => navigatorState[1](!navigatorState[0])}> + <i>menu</i> + </button> + } </header> <main> diff --git a/frontend/src/components/Container/style.scss b/frontend/src/components/Container/style.scss index d9afc74..07ac24c 100644 --- a/frontend/src/components/Container/style.scss +++ b/frontend/src/components/Container/style.scss @@ -54,6 +54,21 @@ header { } } } + + > button { + position: absolute; + left: 20px; + top: 20px; + z-index: 2; + background: none; + border: none; + color: $text-color; + cursor: pointer; + + &:hover { + color: $base-color; + } + } } main { diff --git a/frontend/src/components/Modal/index.tsx b/frontend/src/components/Modal/index.tsx new file mode 100644 index 0000000..636284c --- /dev/null +++ b/frontend/src/components/Modal/index.tsx @@ -0,0 +1,25 @@ +import type { ReactNode } from 'react'; + +import './style.scss'; + +interface ModalProps{ + activationState: [boolean, (state:boolean)=> void] + children?: ReactNode +} + +export function Modal({activationState, children}:ModalProps){ + const [state, setState] = activationState; + + document.body.style.overflow = state ? 'hidden' : 'auto'; + + if(state){ + return ( + <div className='modal'> + <div onClick={()=>setState(false)}></div> + <section> + {children} + </section> + </div> + ) + } +} \ No newline at end of file diff --git a/frontend/src/components/Modal/style.scss b/frontend/src/components/Modal/style.scss new file mode 100644 index 0000000..7a25fdc --- /dev/null +++ b/frontend/src/components/Modal/style.scss @@ -0,0 +1,37 @@ +@use "sass:color"; +@use '@/styles/index' as *; + +div.modal { + display: flex; + justify-content: center; + align-items: center; + z-index: 4; + top: 0; + left: 0; + position: fixed; + width: 100%; + height: 100%; + + > div { + position: absolute; + display: block; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0,0,0,0.2); + z-index: -1; + } + + > section { + background-color: elevation(4); + min-height: 30%; + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + border-radius: 4px; + box-shadow: 0 0 10px rgba(0,0,0,0.4); + } + +} \ No newline at end of file diff --git a/frontend/src/components/Navigator/index.tsx b/frontend/src/components/Navigator/index.tsx index 3ea66f1..f37f65e 100644 --- a/frontend/src/components/Navigator/index.tsx +++ b/frontend/src/components/Navigator/index.tsx @@ -1,13 +1,13 @@ -import { useState } from 'react'; -import { ReactNode } from 'react'; +import type { ReactNode } from 'react'; import './style.scss'; interface NavigatorProps { + activationState: [boolean, (state:boolean)=> void] children?: ReactNode } -export function Navigator({children}:NavigatorProps) { - const [drawerState, setDrawerState] = useState(false); +export function Navigator({activationState, children}:NavigatorProps) { + const [drawerState, setDrawerState] = activationState; function toggleDrawer() {setDrawerState(!drawerState)} @@ -16,8 +16,5 @@ export function Navigator({children}:NavigatorProps) { <div></div> <div>{children}</div> </nav> - <button onClick={toggleDrawer}> - <i>menu</i> - </button> </>) } \ No newline at end of file diff --git a/frontend/src/components/Navigator/style.scss b/frontend/src/components/Navigator/style.scss index e3af671..3b611d5 100644 --- a/frontend/src/components/Navigator/style.scss +++ b/frontend/src/components/Navigator/style.scss @@ -56,19 +56,4 @@ nav { width: 100%; } } - - + button { - position: sticky; - left: 30px; - top: 30px; - z-index: 2; - background: none; - border: none; - color: $text-color; - cursor: pointer; - - &:hover { - color: $base-color; - } - } } \ No newline at end of file diff --git a/frontend/src/components/Table/index.tsx b/frontend/src/components/Table/index.tsx index b0e43a9..0a5d67f 100644 --- a/frontend/src/components/Table/index.tsx +++ b/frontend/src/components/Table/index.tsx @@ -1,5 +1,6 @@ import { useState, useEffect } from 'react'; import { Menu, toast } from '@/helper'; +import { Modal, Badge } from '..'; import type { ReactNode } from 'react'; import type { API, SerieTableEntry } from '@/utils/API'; @@ -14,21 +15,27 @@ export function Table({ api }: TableProps) { const [table, setTable] = useState([] as SerieTableEntry[]); const [toSync, setToSync] = useState(true); + // SYNC DATA useEffect(() => { - if(toSync){ + if (toSync) { api.getTable().then(res => setTable(res)); setToSync(false); } }, [toSync]); return (<> + + <SerieAddModal + api={api} + onUpdate={() => setToSync(true)} + /> - {table.map(entry => - <TableEntry - api={api} - entry={entry} + {table.map(entry => + <TableEntry + api={api} + entry={entry} onUpdate={() => setToSync(true)} key={entry.title} /> @@ -37,29 +44,97 @@ export function Table({ api }: TableProps) { </>); } +interface SerieAddModalProps{ + api: API, + onUpdate: () => void +} + +function SerieAddModal({api, onUpdate}:SerieAddModalProps){ + const [modalActive, setModalActive] = useState(false); + + const [info, setInfo] = useState({ + title: '', + season: NaN, + absolute: false, + link: '' + }) + + async function submit(e:any){ + e.preventDefault(); + let msg = (await api.addSerie(info.title, info.absolute)).message; + toast.success(msg); + msg = (await api.addSeason(info.title, info.season.toString())).message; + toast.success(msg); + msg = (await api.addLinks(info.title, info.season.toString(), [info.link])).message; + toast.success(msg); + + setModalActive(false); + onUpdate(); + } + + return (<> + <Modal + activationState={[modalActive, setModalActive]} + > + <form onSubmit={submit}> + <div> + <input type="text" name="title" id="title" placeholder="Sword Art Online" required onChange={(e) => setInfo({...info, title: e.target.value})}/> + <label htmlFor="title">Nome Anime</label> + </div> + <div> + <input type="number" name="season" id="season" placeholder="1" min="0" step="1" required disabled={info.absolute} onChange={(e) => setInfo({...info, season: e.target.valueAsNumber})}/> + <label htmlFor="season">Stagione</label> + + </div> + <div> + <input type="checkbox" id="absolute" name="absolute" value="false" onChange={(e) => setInfo({...info,absolute: e.target.checked})}/> + <label htmlFor="absolute">Absolute</label> + </div> + <div> + <input type="text" name="link" id="link" placeholder="https://www.animeworld.so/play/sword-art-online.N0onT" pattern="^https:\/\/www\.animeworld\.(tv|so)\/play\/.+" required onChange={(e) => setInfo({...info, link: e.target.value})}/> + <label htmlFor="link">Link</label> + </div> + <div> + <button type="reset" id="clear" onClick={() => setModalActive(false)}>CLEAR</button> + <button type="submit" id="submit">SUBMIT</button> + </div> + </form> + + </Modal> + + <button + id="add-anime" + type='button' + onClick={()=>setModalActive(true)} + > + <i>add</i> + </button> + </>); +} + interface TableEntryProps { api: API, entry: SerieTableEntry onUpdate: () => void } -function TableEntry({api, entry, onUpdate}: TableEntryProps) { +function TableEntry({ api, entry, onUpdate }: TableEntryProps) { const [editTitle, setEditTitle] = useState(false); const [editSeasons, setEditSasons] = useState( Object.fromEntries( Object.keys(entry.seasons) - .map((season) => [season, false]) + .map((season) => [season, false]) ) ); const [editLinks, setEditLinks] = useState( Object.fromEntries( Object.entries(entry.seasons) - .map(([season, links]) => [ - season, - Object.fromEntries( - links.map((link) => [link, false]) - ) - ]) + .map(([season, links]) => [ + season, + Object.fromEntries( + links.map((link) => [link, false]) + ) + ]) ) ); @@ -71,10 +146,10 @@ function TableEntry({api, entry, onUpdate}: TableEntryProps) { 'Edit': () => setEditTitle(true), 'Delete': () => { api.deleteSerie(entry.title) - .then(res => { - toast.success(res.message); - onUpdate(); - }); + .then(res => { + toast.success(res.message); + onUpdate(); + }); } })} > @@ -82,17 +157,19 @@ function TableEntry({api, entry, onUpdate}: TableEntryProps) { <EditableNode type='text' defaultValue={entry.title} - activeState={[editTitle, setEditTitle]} + activationState={[editTitle, setEditTitle]} onSubmit={(content) => { api.editSerie(entry.title, content) - .then(res => { - toast.success(res.message); - onUpdate(); - }); + .then(res => { + toast.success(res.message); + onUpdate(); + }); }} > {entry.title} </EditableNode> + + {entry.absolute && <Badge title='ABSOLUTE'/>} </summary> <Tabs @@ -100,28 +177,28 @@ function TableEntry({api, entry, onUpdate}: TableEntryProps) { <EditableNode type='text' defaultValue={season} - activeState={[ - editSeasons[season], - (state:boolean) => setEditSasons({...editSeasons, [season]:state}) + activationState={[ + editSeasons[season], + (state: boolean) => setEditSasons({ ...editSeasons, [season]: state }) ]} onSubmit={(content) => { api.editSeason(entry.title, season, content) - .then(res => { - toast.success(res.message); - onUpdate(); - }); + .then(res => { + toast.success(res.message); + onUpdate(); + }); }} > <span onContextMenu={e => Menu.show(e as any, { 'Copy': () => navigator.clipboard.writeText(season), - 'Edit': () => setEditSasons({...editSeasons, [season]:true}), + 'Edit': () => setEditSasons({ ...editSeasons, [season]: true }), 'Delete': () => { api.deleteSeason(entry.title, season) - .then(res => { - toast.success(res.message); - onUpdate(); - }) + .then(res => { + toast.success(res.message); + onUpdate(); + }) } })} > @@ -129,35 +206,42 @@ function TableEntry({api, entry, onUpdate}: TableEntryProps) { </span> </EditableNode> )} + onAddLabel={(content) => { + api.addSeason(entry.title, content) + .then(res => { + toast.success(res.message); + onUpdate(); + }) + }} contents={Object.entries(entry.seasons).map(([season, links]) => links.map(link => <EditableNode + key={link} type='text' defaultValue={link} - activeState={[ - editLinks[season][link], - (state:boolean) => setEditLinks({ - ...editLinks, [season]:{ + activationState={[ + editLinks[season][link], + (state: boolean) => setEditLinks({ + ...editLinks, [season]: { ...editLinks[season], [link]: state } }) ]} onSubmit={(content) => { api.editLink(entry.title, season, link, content) - .then(res => { - toast.success(res.message); - onUpdate(); - }); + .then(res => { + toast.success(res.message); + onUpdate(); + }); }} > - <a - href={link} - target="_blank" - key={link} + <a + href={link} + target="_blank" onContextMenu={e => Menu.show(e as any, { 'Copy': () => navigator.clipboard.writeText(link), 'Edit': () => setEditLinks({ - ...editLinks, [season]:{ + ...editLinks, [season]: { ...editLinks[season], [link]: true } }), @@ -175,6 +259,13 @@ function TableEntry({api, entry, onUpdate}: TableEntryProps) { </EditableNode> ) )} + onAddContent={(index, content) => { + api.addLinks(entry.title, Object.keys(entry.seasons)[index], [content]) + .then(res => { + toast.success(res.message); + onUpdate(); + }) + }} /> </details> @@ -184,10 +275,14 @@ function TableEntry({api, entry, onUpdate}: TableEntryProps) { interface TabsProps { labels: ReactNode[], contents: ReactNode[] + onAddLabel?: (content:string) => void, + onAddContent?: (index:number, content:string) => void, } -function Tabs({ labels, contents }: TabsProps) { +function Tabs({ labels, contents, onAddLabel, onAddContent }: TabsProps) { const [tab, setTab] = useState(0); + const [addLabel, setAddLabel] = useState(false); + const [addContent, setAddContent] = useState(false); return (<> <ul> @@ -198,11 +293,33 @@ function Tabs({ labels, contents }: TabsProps) { className={tab == index ? 'active' : ''} >{value}</li> )} - <li><button><i>add</i></button></li> + {onAddLabel && ( + <li> + <EditableNode + type='text' + defaultValue='' + activationState={[addLabel, setAddLabel]} + onSubmit={onAddLabel} + > + <button type='button' onClick={() => setAddLabel(true)}><i>add</i></button> + </EditableNode> + </li> + )} + </ul> <section> {contents[tab]} - <button><i>add</i></button> + {onAddContent && ( + <EditableNode + type='text' + defaultValue='' + activationState={[addContent, setAddContent]} + onSubmit={(content) => onAddContent(tab, content)} + > + <button type='button' onClick={() => setAddContent(true)}><i>add</i></button> + </EditableNode> + )} + </section> </>); @@ -210,16 +327,22 @@ function Tabs({ labels, contents }: TabsProps) { interface EditableNodeProps<T extends string | number> { type: string, - activeState: [boolean, (active: boolean) => void], + activationState: [boolean, (active: boolean) => void], onSubmit: (content: T) => void, defaultValue?: T, placeholder?: string, pattern?: string, children: ReactNode } -function EditableNode<T extends string | number>({ type, defaultValue, activeState, onSubmit, placeholder, pattern, children }: EditableNodeProps<T>) { + +function EditableNode<T extends string | number>({ type, defaultValue, activationState, onSubmit, placeholder, pattern, children }: EditableNodeProps<T>) { const [content, setContent] = useState(defaultValue ?? '' as T); - const [active, setActive] = activeState; + const [active, setActive] = activationState; + + function reset() { + setActive(false); + setContent(defaultValue ?? '' as T); + } if (!active) { return children; @@ -228,8 +351,8 @@ function EditableNode<T extends string | number>({ type, defaultValue, activeSta <form onSubmit={(e) => { e.preventDefault(); - setActive(false); onSubmit(content); + reset(); }} > <input @@ -241,11 +364,11 @@ function EditableNode<T extends string | number>({ type, defaultValue, activeSta onChange={e => setContent(e.target.value as T)} - onBlur={() => setActive(false)} - onKeyDown={e => e.key == 'Escape' && setActive(false)} + onBlur={reset} + onKeyDown={e => e.key == 'Escape' && reset()} /> </form> - + ); } diff --git a/frontend/src/components/Table/style.scss b/frontend/src/components/Table/style.scss index f4279fd..94d97f7 100644 --- a/frontend/src/components/Table/style.scss +++ b/frontend/src/components/Table/style.scss @@ -25,8 +25,7 @@ details { font-weight: 400; padding: 15px; border-bottom: 1px solid $background-color; - transition: box-shadow 0.5s; - transition: color 0.2s; + transition: box-shadow 0.5s, color 0.2s; display: flex; align-items: center; @@ -50,17 +49,17 @@ details { padding: 0; font-size: 16px; display: flex; - justify-content: flex-start; + justify-content: flex-start; border-bottom: 2px solid color.change($color: $text-color, $lightness: 40%); list-style-type: none; - > li { + >li { padding: 10px 50px; cursor: pointer; border-bottom: 2px solid color.change($color: $text-color, $lightness: 40%); margin: 0 0 -2px 0; - transition-property: color, border; - transition-duration: 0.3s; + transition-property: color, border; + transition-duration: 0.3s; text-align: center; &:hover { @@ -72,16 +71,28 @@ details { border-bottom: 2px solid $base-color; } - >button{ + >button { padding: 0; margin: -2px; border: 0; } + + >form { + >input[type=text] { + text-align: center; + border: 0; + width: 108.98px; + padding: 0; + margin: 0 -50px + } + } + + } - + section>* { + +section>* { display: block; - + margin: 20px; width: calc(100% - 40px); } @@ -92,4 +103,40 @@ details { flex-grow: 1; @extend %input; } +} + +div.modal { + form { + display: flex; + flex-direction: column; + width: 100%; + >div { + @extend %input; + margin: 20px; + width: 30vw; + + @media only screen and (max-width: 700px) { + width: 80vw; + } + + font-size: 1em; + + input { + + label { + background-color: elevation(4); + } + } + + &:last-of-type { + display: flex; + flex-direction: row; + justify-content: space-between; + > button { + width: 45%; + padding: 10px; + } + } + + } + } } \ No newline at end of file diff --git a/frontend/src/components/index.tsx b/frontend/src/components/index.tsx index d12f8b4..011f330 100644 --- a/frontend/src/components/index.tsx +++ b/frontend/src/components/index.tsx @@ -1,3 +1,5 @@ -// export {Footer} from './Footer'; -export {Navigator} from './Navigator'; -export {Container} from './Container'; \ No newline at end of file +export { Table } from './Table'; +export { Modal } from './Modal'; +export { Navigator } from './Navigator'; +export { Container } from './Container'; +export { Badge } from './Badge'; \ No newline at end of file diff --git a/frontend/src/pages/home/App.tsx b/frontend/src/pages/home/App.tsx index a9903a0..02e4790 100644 --- a/frontend/src/pages/home/App.tsx +++ b/frontend/src/pages/home/App.tsx @@ -1,6 +1,8 @@ // import * from "react"; import { useState, useEffect } from 'react'; -import { Container, Navigator } from '@/components'; +import { Table, Container, Navigator } from '@/components'; + +import './style.scss'; import '@fontsource/roboto/300.css'; import '@fontsource/roboto/400.css'; @@ -9,12 +11,12 @@ import '@fontsource/roboto/700.css'; import { API } from '@/utils/API'; -import { Table } from '@/components/Table'; import { ToastContainer } from '@/helper'; export default function App() { const [version, setVersion] = useState(''); + const [navActive, setNavActive] = useState(false); const api = new API('http://127.0.0.1:5000'); @@ -23,15 +25,18 @@ export default function App() { }, []); return (<> - <Navigator> + <Navigator + activationState={[navActive, setNavActive]} + > <a>Home</a> <a href="settings.html">Settings</a> <a href="log.html">Log</a> </Navigator> - + <Container title='Tabella Di Conversione' version={version} + navigatorState={[navActive, setNavActive]} > <Table api={api}/> </Container> diff --git a/frontend/src/pages/home/style.scss b/frontend/src/pages/home/style.scss new file mode 100644 index 0000000..6c9b847 --- /dev/null +++ b/frontend/src/pages/home/style.scss @@ -0,0 +1,13 @@ +@use "sass:color"; +@use '@/styles/index' as *; + +button#add-anime { + background-color: elevation(1); + margin: 0 30px; + border-radius: 4px 4px 0 0; + border: 0; + border-bottom: 1px solid $background-color; + width: calc(100% - 60px); + display: block; + padding: 15px; +} \ No newline at end of file diff --git a/frontend/src/styles/_index.scss b/frontend/src/styles/_index.scss index 1673dae..6ba73d9 100644 --- a/frontend/src/styles/_index.scss +++ b/frontend/src/styles/_index.scss @@ -10,6 +10,7 @@ html { color: $text-color; background-color: $background-color; + font-size: 1em; font-family: 'Roboto'; } diff --git a/frontend/src/styles/_inputs.scss b/frontend/src/styles/_inputs.scss index 10d4fbd..2b0350f 100644 --- a/frontend/src/styles/_inputs.scss +++ b/frontend/src/styles/_inputs.scss @@ -8,17 +8,17 @@ > input[type=text], > input[type=password], > input[type=email], > input[type=url], > input[type=number], > input[type=date], > input[type=datetime-local], > select { appearance: textfield; background: transparent; - font-size: 1rem; + font-size: 1em; font-weight: 400; - padding: 5px; + padding: 10px; border: 2px solid darken($text-color, 30%); color: $text-color; border-radius: 4px; - width: calc(100% - 10px); + width: calc(100% - 20px); outline: none; &:disabled{ - border-color: darken($background-color, 3%); + border-color: lighten($background-color, 5%); } &:invalid { @@ -26,7 +26,7 @@ } &::placeholder{ - color: $text-color; + color: darken($text-color, 30%); } &:focus:not(:read-only){ @@ -36,10 +36,9 @@ + label { position: absolute; - top: 0; + top: -10px; left: 20px; padding: 0 10px; - background-color: $background-color; font-size: 14px; } } @@ -57,11 +56,11 @@ + label { user-select: none; cursor: pointer; - font-size: 14px; + font-size: 1em; &::before { font-weight: bold; - margin-right: 5px; + margin-right: 10px; text-align: center; font-size: 15px; color: $base-color; @@ -69,7 +68,7 @@ height: 20px; display: inline-block; content: ' '; - vertical-align: middle; + vertical-align: text-bottom; border-radius: 4px; border: 2px solid darken($text-color, 30%); } @@ -101,6 +100,8 @@ button { background-color: transparent; border: 2px solid color.change($color: $text-color, $lightness: 40%); border-radius: 4px; + transition: color 200ms, border 200ms; + font-size: 1em; &:hover { color: $base-color; From af20154efbc76d9ad0f701617b740aa7da0d96f3 Mon Sep 17 00:00:00 2001 From: MainKronos <lorenzo.chesi@live.it> Date: Thu, 28 Dec 2023 18:48:24 +0100 Subject: [PATCH 24/39] settings --- frontend/settings.html | 2 +- frontend/src/components/Card/index.tsx | 17 +++++ frontend/src/components/Card/style.scss | 20 +++++ frontend/src/components/Settings/index.tsx | 85 +++++++++++++++++++++ frontend/src/components/Settings/style.scss | 55 +++++++++++++ frontend/src/components/index.tsx | 4 +- frontend/src/pages/home/App.tsx | 6 -- frontend/src/pages/settings/App.tsx | 52 ++++++++++--- frontend/src/pages/settings/main.tsx | 2 - frontend/src/pages/settings/style.scss | 2 + frontend/src/styles/_fonts.scss | 4 +- frontend/src/styles/_index.scss | 1 + frontend/src/styles/_inputs.scss | 5 +- 13 files changed, 231 insertions(+), 24 deletions(-) create mode 100644 frontend/src/components/Card/index.tsx create mode 100644 frontend/src/components/Card/style.scss create mode 100644 frontend/src/components/Settings/index.tsx create mode 100644 frontend/src/components/Settings/style.scss create mode 100644 frontend/src/pages/settings/style.scss diff --git a/frontend/settings.html b/frontend/settings.html index f29580c..560b71c 100644 --- a/frontend/settings.html +++ b/frontend/settings.html @@ -2,7 +2,7 @@ <html lang="en"> <head> <meta charset="UTF-8" /> - <link rel="icon" type="image/svg+xml" href="/vite.svg" /> + <link rel="icon" type="image/svg+xml" href="/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Settings diff --git a/frontend/src/components/Card/index.tsx b/frontend/src/components/Card/index.tsx new file mode 100644 index 0000000..0f3492e --- /dev/null +++ b/frontend/src/components/Card/index.tsx @@ -0,0 +1,17 @@ +import type { ReactNode } from 'react'; + +import './style.scss'; + +interface CardProps { + title:string + children?: ReactNode +} + +export function Card({title, children}:CardProps){ + return ( +
+

{title}

+ {children} +
+ ) +} \ No newline at end of file diff --git a/frontend/src/components/Card/style.scss b/frontend/src/components/Card/style.scss new file mode 100644 index 0000000..b134dd4 --- /dev/null +++ b/frontend/src/components/Card/style.scss @@ -0,0 +1,20 @@ +@use "sass:color"; +@use '@/styles/index' as *; + +div.card { + background-color: elevation(2); + border-radius: 4px; + box-shadow: 0 2px 2px 0 rgb(0 0 0 / 14%), 0 3px 1px -2px rgb(0 0 0 / 12%), 0 1px 5px 0 rgb(0 0 0 / 20%); + margin: 50px 30px; + min-height: 100px; + + > h2 { + border-radius: 4px 4px 0 0; + font-weight: 400; + padding: 1.3rem; + border-bottom: 1px solid $base-color; + background-color: elevation(3); + box-shadow: 0px 1px 2px -1px black; + margin: 0; + } +} \ No newline at end of file diff --git a/frontend/src/components/Settings/index.tsx b/frontend/src/components/Settings/index.tsx new file mode 100644 index 0000000..98524cd --- /dev/null +++ b/frontend/src/components/Settings/index.tsx @@ -0,0 +1,85 @@ +import { toast } from '@/helper'; +import { Card } from '..'; + +import type { API } from '@/utils/API'; + +import './style.scss'; +import { useState } from 'react'; + +interface SettingsProps{ + api: API +} + +export function Settings({api}:SettingsProps){ + + const [settings, setSettings] = useState({ + "AutoBind": true, + "LogLevel": "DEBUG", + "MoveEp": true, + "RenameEp": true, + "ScanDelay": 30, + "TagsMode": "BLACKLIST"}) + + + return ( + +
+
+ Livello Log +
+ +
+ + +
+ +
+ Modalità Tag +
+ +
+ + +
+ +
+ Other +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ Intervallo Scan +
+ +
+ +
+ + +
+
+ ) +} \ No newline at end of file diff --git a/frontend/src/components/Settings/style.scss b/frontend/src/components/Settings/style.scss new file mode 100644 index 0000000..90f0316 --- /dev/null +++ b/frontend/src/components/Settings/style.scss @@ -0,0 +1,55 @@ +@use "sass:color"; +@use '@/styles/index' as *; + +section#settings { + padding: 10px; + display: grid; + gap: 10px; + + > *:nth-child(1) {grid-area: log_level;} + > *:nth-child(2) {grid-area: tag_mode;} + > *:nth-child(3) {grid-area: other;} + > *:nth-child(4) {grid-area: scan_interval;} + > *:nth-child(5) {grid-area: restart_btn;} + + grid-template-areas: + "log_level tag_mode other" + "restart_btn scan_interval other"; + + @media screen and (max-width: 1000px){ + grid-template-areas: + "log_level tag_mode" + "scan_interval scan_interval" + "other restart_btn"; + } + + @media screen and (max-width: 550px){ + grid-template-areas: + "log_level" + "tag_mode" + "scan_interval" + "other" + "restart_btn"; + } + + > button { + margin: 9px 2px 0 2px; + min-height: 50px; + } + + > fieldset{ + display: grid; + align-items: center; + gap: 10px; + + > div { + @extend %input; + // margin: 10px 0; + } + + + > legend { + font-size: 1em; + } + } +} \ No newline at end of file diff --git a/frontend/src/components/index.tsx b/frontend/src/components/index.tsx index 011f330..e624ef6 100644 --- a/frontend/src/components/index.tsx +++ b/frontend/src/components/index.tsx @@ -2,4 +2,6 @@ export { Table } from './Table'; export { Modal } from './Modal'; export { Navigator } from './Navigator'; export { Container } from './Container'; -export { Badge } from './Badge'; \ No newline at end of file +export { Badge } from './Badge'; +export { Card } from './Card'; +export { Settings } from './Settings'; \ No newline at end of file diff --git a/frontend/src/pages/home/App.tsx b/frontend/src/pages/home/App.tsx index 02e4790..d9a5837 100644 --- a/frontend/src/pages/home/App.tsx +++ b/frontend/src/pages/home/App.tsx @@ -4,12 +4,6 @@ import { Table, Container, Navigator } from '@/components'; import './style.scss'; -import '@fontsource/roboto/300.css'; -import '@fontsource/roboto/400.css'; -import '@fontsource/roboto/500.css'; -import '@fontsource/roboto/700.css'; - - import { API } from '@/utils/API'; import { ToastContainer } from '@/helper'; diff --git a/frontend/src/pages/settings/App.tsx b/frontend/src/pages/settings/App.tsx index ef3bc19..a6b1609 100644 --- a/frontend/src/pages/settings/App.tsx +++ b/frontend/src/pages/settings/App.tsx @@ -1,11 +1,41 @@ -import { Component } from "react"; - -export default class App extends Component { - render() { - return ( - <> - - - ) - } -} \ No newline at end of file + + +import { useState, useEffect } from 'react'; + +import { Container, Navigator, Settings } from '@/components'; + +import { API } from '@/utils/API'; +import { ToastContainer } from '@/helper'; + +import './style.scss'; + +export default function App() { + + const [version, setVersion] = useState(''); + const [navActive, setNavActive] = useState(false); + + const api = new API('http://127.0.0.1:5000'); + + useEffect(() => { + api.getVersion().then(res => setVersion(res)); + }, []); + + return (<> + + Home + Settings + Log + + + + + + + ); +} diff --git a/frontend/src/pages/settings/main.tsx b/frontend/src/pages/settings/main.tsx index a9a7b44..1c99ae8 100644 --- a/frontend/src/pages/settings/main.tsx +++ b/frontend/src/pages/settings/main.tsx @@ -2,8 +2,6 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; -import '@/styles/base.scss' - const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); root.render( diff --git a/frontend/src/pages/settings/style.scss b/frontend/src/pages/settings/style.scss new file mode 100644 index 0000000..9015f6b --- /dev/null +++ b/frontend/src/pages/settings/style.scss @@ -0,0 +1,2 @@ +@use "sass:color"; +@use '@/styles/index' as *; \ No newline at end of file diff --git a/frontend/src/styles/_fonts.scss b/frontend/src/styles/_fonts.scss index ae751ef..c12fad4 100644 --- a/frontend/src/styles/_fonts.scss +++ b/frontend/src/styles/_fonts.scss @@ -3,4 +3,6 @@ font-style: normal; font-weight: 400; src: url(https://fonts.gstatic.com/s/materialicons/v140/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2'); -} \ No newline at end of file +} + +@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap'); \ No newline at end of file diff --git a/frontend/src/styles/_index.scss b/frontend/src/styles/_index.scss index 6ba73d9..c4c7b76 100644 --- a/frontend/src/styles/_index.scss +++ b/frontend/src/styles/_index.scss @@ -16,6 +16,7 @@ html { body { margin: 0; + color-scheme: dark; } a { diff --git a/frontend/src/styles/_inputs.scss b/frontend/src/styles/_inputs.scss index 2b0350f..5d269ab 100644 --- a/frontend/src/styles/_inputs.scss +++ b/frontend/src/styles/_inputs.scss @@ -4,10 +4,11 @@ %input { position: relative; + > input { + background: transparent; + } > input[type=text], > input[type=password], > input[type=email], > input[type=url], > input[type=number], > input[type=date], > input[type=datetime-local], > select { - appearance: textfield; - background: transparent; font-size: 1em; font-weight: 400; padding: 10px; From 0bbc9f817f0b3c81195c6727e9fd85b5b4bce223 Mon Sep 17 00:00:00 2001 From: MainKronos Date: Fri, 29 Dec 2023 16:06:22 +0100 Subject: [PATCH 25/39] settings --- frontend/src/components/Settings/index.tsx | 193 +++++++++++++-------- frontend/src/components/Table/index.tsx | 2 +- frontend/src/pages/settings/App.tsx | 2 - frontend/src/utils/API.ts | 72 ++++++-- src/components/api/__init__.py | 12 +- 5 files changed, 184 insertions(+), 97 deletions(-) diff --git a/frontend/src/components/Settings/index.tsx b/frontend/src/components/Settings/index.tsx index 98524cd..796164e 100644 --- a/frontend/src/components/Settings/index.tsx +++ b/frontend/src/components/Settings/index.tsx @@ -1,85 +1,128 @@ +import { useState, useEffect } from 'react'; + import { toast } from '@/helper'; import { Card } from '..'; -import type { API } from '@/utils/API'; +import type { API, SettingsOptions } from '@/utils/API'; import './style.scss'; -import { useState } from 'react'; -interface SettingsProps{ +interface SettingsProps { api: API } -export function Settings({api}:SettingsProps){ - - const [settings, setSettings] = useState({ - "AutoBind": true, - "LogLevel": "DEBUG", - "MoveEp": true, - "RenameEp": true, - "ScanDelay": 30, - "TagsMode": "BLACKLIST"}) - - - return ( - -
-
- Livello Log -
- -
- - -
- -
- Modalità Tag -
- -
- - -
- -
- Other -
- - -
- -
- - -
- -
- - -
- -
- -
- Intervallo Scan -
- -
- -
- - -
-
- ) +export function Settings({ api }: SettingsProps) { + + return (<> + + + ) +} + + +interface SettingCardProps { + api: API +} +function SettingCard({ api }: SettingCardProps) { + const [settings, setSettings] = useState(); + + useEffect(() => { + api.getSettings().then(res => setSettings(res)); + }, []); + + if (settings) + return ( + +
+
+ Livello Log +
+ +
+
+ +
+ Modalità Tag +
+ +
+ + +
+ +
+ Other +
+ { + setSettings({...settings, RenameEp:e.target.checked}); + api.editSettings("RenameEp", e.target.checked) + .then(res => toast.success(res.message)); + }}/> + +
+ +
+ { + setSettings({...settings, MoveEp:e.target.checked}); + api.editSettings("MoveEp", e.target.checked) + .then(res => toast.success(res.message)); + }}/> + +
+ +
+ { + setSettings({...settings, AutoBind:e.target.checked}); + api.editSettings("AutoBind", e.target.checked) + .then(res => toast.success(res.message)); + }}/> + +
+ +
+ +
+ Intervallo Scan +
+ { + if(e.target.checkValidity()){ + setSettings({...settings, ScanDelay:e.target.valueAsNumber}); + api.editSettings("ScanDelay", e.target.valueAsNumber) + .then(res => toast.success(res.message)); + } + }}/> +
+ +
+ + +
+
+ ); } \ No newline at end of file diff --git a/frontend/src/components/Table/index.tsx b/frontend/src/components/Table/index.tsx index 0a5d67f..d2d2be0 100644 --- a/frontend/src/components/Table/index.tsx +++ b/frontend/src/components/Table/index.tsx @@ -87,7 +87,7 @@ function SerieAddModal({api, onUpdate}:SerieAddModalProps){
- setInfo({...info,absolute: e.target.checked})}/> + setInfo({...info,absolute: e.target.checked})}/>
diff --git a/frontend/src/pages/settings/App.tsx b/frontend/src/pages/settings/App.tsx index a6b1609..80e6ee2 100644 --- a/frontend/src/pages/settings/App.tsx +++ b/frontend/src/pages/settings/App.tsx @@ -1,5 +1,3 @@ - - import { useState, useEffect } from 'react'; import { Container, Navigator, Settings } from '@/components'; diff --git a/frontend/src/utils/API.ts b/frontend/src/utils/API.ts index 88511ca..886479f 100644 --- a/frontend/src/utils/API.ts +++ b/frontend/src/utils/API.ts @@ -10,12 +10,35 @@ export class API { return data.version; } + async putWekeup(): Promise { + const res = await fetch(encodeURI(this.backend + '/wekeup'), { + method: "PUT" + }); + return await res.json(); + } + + async getSettings(): Promise { + const res = await fetch(this.backend + '/settings'); + return await res.json(); + } + + async editSettings(setting:"AutoBind"|"LogLevel"|"MoveEp"|"RenameEp"|"ScanDelay"|"TagsMode", value:any): Promise{ + const res = await fetch(encodeURI(this.backend + `/settings/${setting}`), { + method: "PATCH", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ value: value }) + }); + return await res.json(); + } + async getTable(): Promise { const res = await fetch(this.backend + '/table/'); - return await res.json() + return await res.json(); } - async addSerie(title: string, absolute: boolean = false) { + async addSerie(title: string, absolute: boolean = false): Promise { const res = await fetch(this.backend + '/table/', { method: "POST", headers: { @@ -23,20 +46,20 @@ export class API { }, body: JSON.stringify({ title: title, absolute: absolute }) }); - return await res.json() + return await res.json(); } - async deleteSerie(title: string) { + async deleteSerie(title: string): Promise { const res = await fetch(encodeURI(this.backend + `/table/${title}`), { method: "DELETE", headers: { "Content-Type": "application/json" } }); - return await res.json() + return await res.json(); } - async editSerie(title: string, newTitle: string) { + async editSerie(title: string, newTitle: string): Promise { const res = await fetch(encodeURI(this.backend + `/table/${title}`), { method: "PATCH", headers: { @@ -44,10 +67,10 @@ export class API { }, body: JSON.stringify({ title: newTitle }) }); - return await res.json() + return await res.json(); } - async addSeason(title: string, season: string) { + async addSeason(title: string, season: string): Promise { const res = await fetch(encodeURI(this.backend + `/table/${title}`), { method: "POST", headers: { @@ -55,20 +78,20 @@ export class API { }, body: JSON.stringify({ season: season }) }); - return await res.json() + return await res.json(); } - async deleteSeason(title: string, season: string) { + async deleteSeason(title: string, season: string): Promise { const res = await fetch(encodeURI(this.backend + `/table/${title}/${season}`), { method: "DELETE", headers: { "Content-Type": "application/json" } }); - return await res.json() + return await res.json(); } - async editSeason(title: string, season: string, newSeason: string) { + async editSeason(title: string, season: string, newSeason: string): Promise { const res = await fetch(encodeURI(this.backend + `/table/${title}/${season}`), { method: "PATCH", headers: { @@ -76,10 +99,10 @@ export class API { }, body: JSON.stringify({ season: newSeason }) }); - return await res.json() + return await res.json(); } - async addLinks(title: string, season: string, links: string[]) { + async addLinks(title: string, season: string, links: string[]): Promise { const res = await fetch(encodeURI(this.backend + `/table/${title}/${season}`), { method: "POST", headers: { @@ -87,10 +110,10 @@ export class API { }, body: JSON.stringify({ links: links }) }); - return await res.json() + return await res.json(); } - async deleteLink(title:string, season:string, link:string){ + async deleteLink(title: string, season: string, link: string): Promise { const res = await fetch(encodeURI(this.backend + `/table/${title}/${season}/${link}`), { method: "DELETE", headers: { @@ -100,7 +123,7 @@ export class API { return await res.json() } - async editLink(title:string, season:string, link:string, newLink: string){ + async editLink(title: string, season: string, link: string, newLink: string): Promise { const res = await fetch(encodeURI(this.backend + `/table/${title}/${season}/${link}`), { method: "PATCH", headers: { @@ -108,7 +131,7 @@ export class API { }, body: JSON.stringify({ link: newLink }) }); - return await res.json() + return await res.json(); } } @@ -120,4 +143,17 @@ export interface SerieTableEntry { export interface SeasonsTableEntry { [season: string]: string[] +} + +export interface ResponseMsg { + message: string +} + +export interface SettingsOptions { + AutoBind: boolean, + LogLevel: "DEBUG" | "INFO" | "WARNING" | "ERROR" | "CRITICAL", + MoveEp: boolean, + RenameEp: boolean, + ScanDelay: number, + TagsMode: "BLACKLIST" | "WHITELIST" } \ No newline at end of file diff --git a/src/components/api/__init__.py b/src/components/api/__init__.py index 7b994f1..c9e1879 100644 --- a/src/components/api/__init__.py +++ b/src/components/api/__init__.py @@ -35,7 +35,17 @@ def get_version(): """ # time.sleep(5) return {"version": core.version} - + + @api.put("/wekeup") + def put_wekeup(): + """ + Forza l'avvio di una nuova scansione. + """ + if core.wakeUp(): + return {"message": "Scansione avviata."} + else: + return {"message": "Scansione in corso."} + api.register_blueprint(Table(core)) api.register_blueprint(Settings(core)) api.register_blueprint(Tags(core)) From 66a5ffc185d014e7bb1a4bb0a70c8c52124280b1 Mon Sep 17 00:00:00 2001 From: MainKronos Date: Fri, 5 Jan 2024 12:23:50 +0100 Subject: [PATCH 26/39] tags --- frontend/src/components/Card/index.tsx | 7 +- frontend/src/components/Card/style.scss | 21 ++-- frontend/src/components/Settings/index.tsx | 84 ++++++++++----- frontend/src/components/Settings/style.scss | 108 +++++++++++++++----- frontend/src/utils/API.ts | 21 ++++ src/components/api/routes/Tags.py | 39 ++++--- 6 files changed, 205 insertions(+), 75 deletions(-) diff --git a/frontend/src/components/Card/index.tsx b/frontend/src/components/Card/index.tsx index 0f3492e..4d761de 100644 --- a/frontend/src/components/Card/index.tsx +++ b/frontend/src/components/Card/index.tsx @@ -3,14 +3,13 @@ import type { ReactNode } from 'react'; import './style.scss'; interface CardProps { - title:string + className?: string children?: ReactNode } -export function Card({title, children}:CardProps){ +export function Card({children, className}:CardProps){ return ( -
-

{title}

+
{children}
) diff --git a/frontend/src/components/Card/style.scss b/frontend/src/components/Card/style.scss index b134dd4..d83a460 100644 --- a/frontend/src/components/Card/style.scss +++ b/frontend/src/components/Card/style.scss @@ -1,20 +1,23 @@ @use "sass:color"; @use '@/styles/index' as *; -div.card { +.card { background-color: elevation(2); border-radius: 4px; box-shadow: 0 2px 2px 0 rgb(0 0 0 / 14%), 0 3px 1px -2px rgb(0 0 0 / 12%), 0 1px 5px 0 rgb(0 0 0 / 20%); margin: 50px 30px; min-height: 100px; - > h2 { - border-radius: 4px 4px 0 0; - font-weight: 400; - padding: 1.3rem; - border-bottom: 1px solid $base-color; - background-color: elevation(3); - box-shadow: 0px 1px 2px -1px black; - margin: 0; + > h2,h3,h4,h5,h6 { + &:first-of-type { + border-radius: 4px 4px 0 0; + font-weight: 400; + padding: 1.3rem; + border-bottom: 1px solid $base-color; + background-color: elevation(1); + box-shadow: 0px 1px 2px -1px black; + margin: 0; + } + } } \ No newline at end of file diff --git a/frontend/src/components/Settings/index.tsx b/frontend/src/components/Settings/index.tsx index 796164e..14b3497 100644 --- a/frontend/src/components/Settings/index.tsx +++ b/frontend/src/components/Settings/index.tsx @@ -3,7 +3,7 @@ import { useState, useEffect } from 'react'; import { toast } from '@/helper'; import { Card } from '..'; -import type { API, SettingsOptions } from '@/utils/API'; +import type { API, SettingsOptions, TagValue } from '@/utils/API'; import './style.scss'; @@ -16,14 +16,12 @@ export function Settings({ api }: SettingsProps) { return (<> + ) } -interface SettingCardProps { - api: API -} -function SettingCard({ api }: SettingCardProps) { +function SettingCard({ api }: SettingsProps) { const [settings, setSettings] = useState(); useEffect(() => { @@ -32,17 +30,18 @@ function SettingCard({ api }: SettingCardProps) { if (settings) return ( - + +

Settings

Livello Log
- { - setSettings({...settings, TagsMode:e.target.value as any}); + setSettings({ ...settings, TagsMode: e.target.value as any }); api.editSettings("TagsMode", e.target.value) - .then(res => toast.success(res.message)); + .then(res => toast.success(res.message)); }} > @@ -77,28 +76,28 @@ function SettingCard({ api }: SettingCardProps) { Other
{ - setSettings({...settings, RenameEp:e.target.checked}); + setSettings({ ...settings, RenameEp: e.target.checked }); api.editSettings("RenameEp", e.target.checked) - .then(res => toast.success(res.message)); - }}/> + .then(res => toast.success(res.message)); + }} />
{ - setSettings({...settings, MoveEp:e.target.checked}); + setSettings({ ...settings, MoveEp: e.target.checked }); api.editSettings("MoveEp", e.target.checked) - .then(res => toast.success(res.message)); - }}/> + .then(res => toast.success(res.message)); + }} />
{ - setSettings({...settings, AutoBind:e.target.checked}); + setSettings({ ...settings, AutoBind: e.target.checked }); api.editSettings("AutoBind", e.target.checked) - .then(res => toast.success(res.message)); - }}/> + .then(res => toast.success(res.message)); + }} />
@@ -108,21 +107,54 @@ function SettingCard({ api }: SettingCardProps) { Intervallo Scan
{ - if(e.target.checkValidity()){ - setSettings({...settings, ScanDelay:e.target.valueAsNumber}); + if (e.target.checkValidity()) { + setSettings({ ...settings, ScanDelay: e.target.valueAsNumber }); api.editSettings("ScanDelay", e.target.valueAsNumber) - .then(res => toast.success(res.message)); + .then(res => toast.success(res.message)); } - }}/> + }} />
); +} + + + +function TagsCard({ api }: SettingsProps) { + const [tags, setTags] = useState(); + + useEffect(() => { + api.getTags().then(res => setTags(res)); + }, []); + + if(tags){ + return ( + +

Tags

+
+ {tags.map(tag => +

{tag.name}

+ + + + +
)} +
+
+ ); + } } \ No newline at end of file diff --git a/frontend/src/components/Settings/style.scss b/frontend/src/components/Settings/style.scss index 90f0316..7eb3d33 100644 --- a/frontend/src/components/Settings/style.scss +++ b/frontend/src/components/Settings/style.scss @@ -4,52 +4,114 @@ section#settings { padding: 10px; display: grid; - gap: 10px; + gap: 10px; - > *:nth-child(1) {grid-area: log_level;} - > *:nth-child(2) {grid-area: tag_mode;} - > *:nth-child(3) {grid-area: other;} - > *:nth-child(4) {grid-area: scan_interval;} - > *:nth-child(5) {grid-area: restart_btn;} + >*:nth-child(1) { + grid-area: log_level; + } + + >*:nth-child(2) { + grid-area: tag_mode; + } + + >*:nth-child(3) { + grid-area: other; + } + + >*:nth-child(4) { + grid-area: scan_interval; + } - grid-template-areas: - "log_level tag_mode other" - "restart_btn scan_interval other"; + >*:nth-child(5) { + grid-area: restart_btn; + } + + grid-template-areas: "log_level tag_mode other" + "restart_btn scan_interval other"; - @media screen and (max-width: 1000px){ - grid-template-areas: - "log_level tag_mode" + @media screen and (max-width: 1000px) { + grid-template-areas: + "log_level tag_mode" "scan_interval scan_interval" - "other restart_btn"; + "other restart_btn"; } - @media screen and (max-width: 550px){ - grid-template-areas: - "log_level" + @media screen and (max-width: 550px) { + grid-template-areas: + "log_level" "tag_mode" "scan_interval" - "other" + "other" "restart_btn"; } - > button { + >button { margin: 9px 2px 0 2px; min-height: 50px; } - > fieldset{ + >fieldset { display: grid; align-items: center; - gap: 10px; + gap: 10px; - > div { + >div { @extend %input; // margin: 10px 0; } - - > legend { + + >legend { font-size: 1em; } } +} + +section#tags { + + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 15px; + padding: 15px; + + >*.card { + margin: 0; + background-color: elevation(3); + height: 200px; + // width: 200px; + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: 70% auto; + gap: 10px; + + + &.active { + >button:nth-of-type(2) { + border-color: $base-color; + color: $base-color; + } + } + + >h3 { + text-align: center; + vertical-align: middle; + grid-column: 1 / 3; + display: flex; + justify-content: center; + align-content: center; + flex-direction: column; + text-overflow: ellipsis; + overflow: hidden; + word-break: break-word; + } + + >button:nth-of-type(1) { + margin: 0 0 10px 10px; + } + + >button:nth-of-type(2) { + margin: 0 10px 10px 0; + } + + } } \ No newline at end of file diff --git a/frontend/src/utils/API.ts b/frontend/src/utils/API.ts index 886479f..7d1b4bb 100644 --- a/frontend/src/utils/API.ts +++ b/frontend/src/utils/API.ts @@ -33,6 +33,21 @@ export class API { return await res.json(); } + async getTags(): Promise { + const res = await fetch(this.backend + '/tags'); + return await res.json(); + } + + async editToggleTag(tag:string|number): Promise{ + const res = await fetch(encodeURI(this.backend + `/tags/${tag}/toggle`), { + method: "PATCH", + headers: { + "Content-Type": "application/json" + } + }); + return await res.json(); + } + async getTable(): Promise { const res = await fetch(this.backend + '/table/'); return await res.json(); @@ -156,4 +171,10 @@ export interface SettingsOptions { RenameEp: boolean, ScanDelay: number, TagsMode: "BLACKLIST" | "WHITELIST" +} + +export interface TagValue { + "id": number, + "name": string, + "active": boolean } \ No newline at end of file diff --git a/src/components/api/routes/Tags.py b/src/components/api/routes/Tags.py index b30982c..f0c0ce5 100644 --- a/src/components/api/routes/Tags.py +++ b/src/components/api/routes/Tags.py @@ -46,44 +46,57 @@ def get_tag(tag:str|int): @route.delete('/') def del_tag(tag:str|int): - """Aggiunge un tag.""" - - try: tag = int(tag) - except ValueError: pass + """Rimuove un tag.""" if tag not in core.tags: abort(400, f"Il tag '{tag}' non esiste.") + tagname = core.tags[tag]["name"] + del core.tags[tag] - return {'message': f"Tag '{tag}' eliminato."} + return {'message': f"Tag '{tagname}' eliminato."} @route.patch('//enable') def enable_tag(tag:str|int): """Attiva un tag.""" - try: tag = int(tag) - except ValueError: pass - if tag not in core.tags: abort(400, f"Il tag '{tag}' non esiste.") + tagname = core.tags[tag]["name"] + core.tags.enable(tag) - return {'message': f"Tag '{tag}' attivato."} + return {'message': f"Tag '{tagname}' attivato."} @route.patch('//disable') def disable_tag(tag:str|int): """Disattiva un tag.""" - try: tag = int(tag) - except ValueError: pass - if tag not in core.tags: abort(400, f"Il tag '{tag}' non esiste.") + tagname = core.tags[tag]["name"] + core.tags.disable(tag) - return {'message': f"Tag '{tag}' disattivato."} + return {'message': f"Tag '{tagname}' disattivato."} + + @route.patch('//toggle') + def toggle_tag(tag:str|int): + """Attiva/Disattiva un tag.""" + + if tag not in core.tags: + abort(400, f"Il tag '{tag}' non esiste.") + + tagname = core.tags[tag]["name"] + + if core.tags.isActive(tag): + core.tags.disable(tag) + return {'message': f"Tag '{tagname}' disattivato."} + else: + core.tags.enable(tag) + return {'message': f"Tag '{tagname}' attivato."} return route \ No newline at end of file From 4c32c029613f8a376fb985ee0d9e4481c7e5eb72 Mon Sep 17 00:00:00 2001 From: MainKronos Date: Fri, 5 Jan 2024 15:00:58 +0100 Subject: [PATCH 27/39] tags --- frontend/src/components/Settings/index.tsx | 95 +++++++++++++++++---- frontend/src/components/Settings/style.scss | 39 ++++++--- frontend/src/styles/_inputs.scss | 2 +- frontend/src/utils/API.ts | 21 +++++ src/components/api/routes/Tags.py | 36 ++++++-- src/components/backend/connection/Sonarr.py | 2 +- src/dev_api.py | 2 +- 7 files changed, 159 insertions(+), 38 deletions(-) diff --git a/frontend/src/components/Settings/index.tsx b/frontend/src/components/Settings/index.tsx index 14b3497..833017e 100644 --- a/frontend/src/components/Settings/index.tsx +++ b/frontend/src/components/Settings/index.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; -import { toast } from '@/helper'; +import { Menu, toast } from '@/helper'; import { Card } from '..'; import type { API, SettingsOptions, TagValue } from '@/utils/API'; @@ -130,31 +130,94 @@ function SettingCard({ api }: SettingsProps) { function TagsCard({ api }: SettingsProps) { const [tags, setTags] = useState(); + const [toSync, setToSync] = useState(true); useEffect(() => { - api.getTags().then(res => setTags(res)); - }, []); + if(toSync) { + api.getTags().then(res => setTags(res)); + setToSync(false); + } + }, [toSync]); + + async function toggleTag(tag:TagValue) { + let res = await api.editToggleTag(tag.id); + toast.success(res.message); + setToSync(true); + } + + async function deleteTag(tag:TagValue) { + let res = await api.deleteTag(tag.id); + toast.success(res.message); + setToSync(true); + } + + async function addTag(name:string) { + let res = await api.addTag(name, false); + toast.info(res.message); + setToSync(true); + } if(tags){ return (

Tags

- {tags.map(tag => -

{tag.name}

- - - - -
)} + {tags.map(tag => + +

Menu.show(e as any, { + 'Copy': () => navigator.clipboard.writeText(tag.name), + 'Delete': () => deleteTag(tag) + })} + >{tag.name}

+ + + +
+ )} + +
); } +} + +interface AddCardProps { + onSubmit: (content:string) => void +} +function AddCard({onSubmit}:AddCardProps) { + const [active, setActive] = useState(false); + const [content, setContent] = useState(''); + + function reset() { + setContent(''); + setActive(false); + } + + if(!active){ + return ( + + + + ) + }else { + return ( +
{ + e.preventDefault(); + if(content) onSubmit(content); + reset(); + }} + className='card' + onKeyDown={e => e.key == 'Escape' && reset()} + > + + + + + ) + } } \ No newline at end of file diff --git a/frontend/src/components/Settings/style.scss b/frontend/src/components/Settings/style.scss index 7eb3d33..2cae3cd 100644 --- a/frontend/src/components/Settings/style.scss +++ b/frontend/src/components/Settings/style.scss @@ -78,15 +78,11 @@ section#tags { margin: 0; background-color: elevation(3); height: 200px; - // width: 200px; display: grid; - grid-template-columns: 1fr 1fr; - grid-template-rows: 70% auto; - gap: 10px; - + grid-template-rows: 70% auto; &.active { - >button:nth-of-type(2) { + >button { border-color: $base-color; color: $base-color; } @@ -95,7 +91,6 @@ section#tags { >h3 { text-align: center; vertical-align: middle; - grid-column: 1 / 3; display: flex; justify-content: center; align-content: center; @@ -105,13 +100,33 @@ section#tags { word-break: break-word; } - >button:nth-of-type(1) { - margin: 0 0 10px 10px; + >button { + margin: 10px; } - >button:nth-of-type(2) { - margin: 0 10px 10px 0; - } + &:last-child { + textarea { + padding: 1.3rem; + resize: none; + outline: none; + font-family: 'Roboto'; + font-weight: 400; + font-size: 1.17em; + color: $text-color; + text-align: center; + border: 0; + border-bottom: 1px solid $base-color; + background-color: elevation(1); + + & + button { + grid-row: 2; + } + } + > button { + grid-row: 1 / span 2; + } + } } + } \ No newline at end of file diff --git a/frontend/src/styles/_inputs.scss b/frontend/src/styles/_inputs.scss index 5d269ab..1b8e8c3 100644 --- a/frontend/src/styles/_inputs.scss +++ b/frontend/src/styles/_inputs.scss @@ -8,7 +8,7 @@ background: transparent; } - > input[type=text], > input[type=password], > input[type=email], > input[type=url], > input[type=number], > input[type=date], > input[type=datetime-local], > select { + > input[type=text], > input[type=password], > input[type=email], > input[type=url], > input[type=number], > input[type=date], > input[type=datetime-local], > select, textarea { font-size: 1em; font-weight: 400; padding: 10px; diff --git a/frontend/src/utils/API.ts b/frontend/src/utils/API.ts index 7d1b4bb..6ddccb4 100644 --- a/frontend/src/utils/API.ts +++ b/frontend/src/utils/API.ts @@ -38,6 +38,17 @@ export class API { return await res.json(); } + async addTag(name:string, active:boolean) { + const res = await fetch(this.backend + '/tags/', { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ name: name, active: active }) + }); + return await res.json(); + } + async editToggleTag(tag:string|number): Promise{ const res = await fetch(encodeURI(this.backend + `/tags/${tag}/toggle`), { method: "PATCH", @@ -48,6 +59,16 @@ export class API { return await res.json(); } + async deleteTag(tag:string|number): Promise { + const res = await fetch(encodeURI(this.backend + `/tags/${tag}`), { + method: "DELETE", + headers: { + "Content-Type": "application/json" + } + }); + return await res.json(); + } + async getTable(): Promise { const res = await fetch(this.backend + '/table/'); return await res.json(); diff --git a/src/components/api/routes/Tags.py b/src/components/api/routes/Tags.py index f0c0ce5..dcf4f38 100644 --- a/src/components/api/routes/Tags.py +++ b/src/components/api/routes/Tags.py @@ -20,17 +20,27 @@ def get_tags(): return core.tags.getData() @route.post('/') - @route.input({'name':fields.String(),'id': fields.Integer(), 'active': fields.Boolean()}) + @route.input({'name':fields.String(), 'active': fields.Boolean()}) def add_tag(json_data:dict): """Aggiunge un tag.""" - try: - core.tags.append(json_data['id'], json_data['name'], json_data['active']) - except ValueError as e: - abort(400, str(e)) + name = json_data["name"] + active = json_data["active"] - return {'message': f"Tag '{json_data['name']}' aggiunto."} - + res = core.sonarr.tags() + res.raise_for_status() + + sonarr_tag = next(filter(lambda x:x["label"] == name, res.json()), None) + + if sonarr_tag: + tag_id = sonarr_tag["id"] + try: + core.tags.append(tag_id, name, active) + return {'message': f"Tag '{json_data['name']}' aggiunto."} + except ValueError as e: + abort(400, str(e)) + else: + return {'message': f"Il Tag {name} non esiste su Sonarr."} @route.get('/') def get_tag(tag:str|int): @@ -47,6 +57,9 @@ def get_tag(tag:str|int): @route.delete('/') def del_tag(tag:str|int): """Rimuove un tag.""" + + try: tag = int(tag) + except ValueError: pass if tag not in core.tags: abort(400, f"Il tag '{tag}' non esiste.") @@ -61,6 +74,9 @@ def del_tag(tag:str|int): def enable_tag(tag:str|int): """Attiva un tag.""" + try: tag = int(tag) + except ValueError: pass + if tag not in core.tags: abort(400, f"Il tag '{tag}' non esiste.") @@ -74,6 +90,9 @@ def enable_tag(tag:str|int): def disable_tag(tag:str|int): """Disattiva un tag.""" + try: tag = int(tag) + except ValueError: pass + if tag not in core.tags: abort(400, f"Il tag '{tag}' non esiste.") @@ -87,6 +106,9 @@ def disable_tag(tag:str|int): def toggle_tag(tag:str|int): """Attiva/Disattiva un tag.""" + try: tag = int(tag) + except ValueError: pass + if tag not in core.tags: abort(400, f"Il tag '{tag}' non esiste.") diff --git a/src/components/backend/connection/Sonarr.py b/src/components/backend/connection/Sonarr.py index 14a7f40..9603b4d 100644 --- a/src/components/backend/connection/Sonarr.py +++ b/src/components/backend/connection/Sonarr.py @@ -92,7 +92,7 @@ def tags(self) -> httpx.Response: Returns: La risposta HTTP """ - return self.client.get("/tag") + return self.client.get("/tag", params='') ### COMMAND diff --git a/src/dev_api.py b/src/dev_api.py index 8fc6044..2ff8945 100644 --- a/src/dev_api.py +++ b/src/dev_api.py @@ -6,7 +6,7 @@ ctx.DOWNLOAD_FOLDER = pathlib.Path('./tests/downloads').absolute() ctx.DATABASE_FOLDER = pathlib.Path("./tests/database").absolute() ctx.SCRIPT_FOLDER = pathlib.Path("./tests/script").absolute() -ctx.SONARR_URL = "http://netvault:8989/" +ctx.SONARR_URL = "http://netvault:8989" ctx.API_KEY = "f10994e9f3494368a31a3088aba6b9fc" ctx.VERSION = "dev" From a3f4784ff72ebc44e784562faf4d5fe570ec7273 Mon Sep 17 00:00:00 2001 From: Francesco Paolo De Valerio Date: Sat, 6 Jan 2024 13:22:47 +0100 Subject: [PATCH 28/39] [Enhancement] Pulsante "Ricerca Ora" #121 --- src/components/backend/core/Core.py | 26 +++++++++++++++++-- src/components/frontend/api.py | 13 ++++++++++ src/components/frontend/static/css/input.css | 7 +++++ src/components/frontend/static/js/base/nav.js | 9 +++++++ src/components/frontend/templates/base.html | 1 + 5 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/components/backend/core/Core.py b/src/components/backend/core/Core.py index 0796da5..66e457e 100644 --- a/src/components/backend/core/Core.py +++ b/src/components/backend/core/Core.py @@ -39,6 +39,7 @@ def __init__(self, *, ### Setup Thread ### super().__init__(name=self.__class__.__name__, daemon=True) + self.semaphore = threading.Condition() self.version = ctx.VERSION ### Setup logger ### @@ -132,6 +133,9 @@ def run(self): self.log.info("]────────────────────────────────────────────────────────────────────────────────────────────[") self.log.info("") + # Acquire lock + self.semaphore.acquire() + try: while True: start = time.time() @@ -143,13 +147,31 @@ def run(self): wait = next_run - time.time() self.log.info(f"╰───────────────────────────────────「{time.strftime('%d %b %Y %H:%M:%S', time.localtime(next_run))}」───────────────────────────────────╯") self.log.info("") - if wait > 0: time.sleep(wait) + + # release lock and wait for next execution + self.semaphore.wait(timeout=wait) except Exception as e: # Errore interno non recuperabile self.log.critical("]─────────────────────────────────────────[CRITICAL]─────────────────────────────────────────[") self.log.exception(e) self.error = e - + + def wake(self) -> bool: + """ + Fa partire immediatamente il processo di ricerca e download. + """ + try: + # acquire lock + self.semaphore.acquire() + # resume thread + self.semaphore.notify() + # release lock + self.semaphore.release() + except RuntimeError as e: + return False + else: + return True + def job(self): """ Processo principale di ricerca e download. diff --git a/src/components/frontend/api.py b/src/components/frontend/api.py index 14b4044..9067a1b 100644 --- a/src/components/frontend/api.py +++ b/src/components/frontend/api.py @@ -8,6 +8,19 @@ def loadAPI(app:Flask): core:Core = app.config['CORE'] + @app.route('/api/rescan', methods=['GET']) + def rescan(): + result = core.wake() + return Response( + mimetype='application/json', + status=200, + response=json.dumps({ + "error": result, + "data": "Rescan schedulato." if result else "Errore in fase di rescan" + }), + headers={"Access-Control-Allow-Origin": "*"} + ) + @app.route('/api/table', methods=['GET']) def getTable(): return Response( diff --git a/src/components/frontend/static/css/input.css b/src/components/frontend/static/css/input.css index 2697a02..0aa686d 100644 --- a/src/components/frontend/static/css/input.css +++ b/src/components/frontend/static/css/input.css @@ -302,6 +302,13 @@ summary input[type=text]{ text-shadow: 0 0 10px var(--primary-color); } +#rescan.btn{ + position: absolute; + right: 2.67em; + top: 0.67em; + text-shadow: 0 0 10px var(--primary-color); +} + #add-anime.btn{ width: calc(100% - 60px); margin: 0 30px; diff --git a/src/components/frontend/static/js/base/nav.js b/src/components/frontend/static/js/base/nav.js index 8248778..cc7544f 100644 --- a/src/components/frontend/static/js/base/nav.js +++ b/src/components/frontend/static/js/base/nav.js @@ -8,6 +8,15 @@ document.querySelector(".nav-overlay").addEventListener('click', function(){ document.querySelector("nav").classList.remove('active'); }); +document.querySelector('#rescan').addEventListener('click', function (e) { + e.preventDefault(); + fetch("/api/rescan").then(res => res.json()).then(res => { + showToast(res.data); + }, error => { + showToast(error) + }); +}); + // TOAST ////////////////////////////////////////////////////// diff --git a/src/components/frontend/templates/base.html b/src/components/frontend/templates/base.html index dc1dc0a..50dce2f 100644 --- a/src/components/frontend/templates/base.html +++ b/src/components/frontend/templates/base.html @@ -35,6 +35,7 @@ {% block header %}

{{ self.title() }}

+ {% endblock %}
From b173840a74b88d5b4f83817f3487fdf6c3a65e41 Mon Sep 17 00:00:00 2001 From: MainKronos Date: Sat, 6 Jan 2024 15:08:40 +0100 Subject: [PATCH 29/39] Connections --- frontend/src/components/Settings/index.tsx | 94 +++++++++++++++---- frontend/src/components/Settings/style.scss | 11 ++- frontend/src/utils/API.ts | 42 +++++++++ src/components/api/__init__.py | 2 + src/components/api/routes/Connections.py | 92 ++++++++++++++++++ .../backend/database/ConnectionsDB.py | 88 ++++++++++++++--- 6 files changed, 298 insertions(+), 31 deletions(-) create mode 100644 src/components/api/routes/Connections.py diff --git a/frontend/src/components/Settings/index.tsx b/frontend/src/components/Settings/index.tsx index 833017e..8bdeaa4 100644 --- a/frontend/src/components/Settings/index.tsx +++ b/frontend/src/components/Settings/index.tsx @@ -3,7 +3,7 @@ import { useState, useEffect } from 'react'; import { Menu, toast } from '@/helper'; import { Card } from '..'; -import type { API, SettingsOptions, TagValue } from '@/utils/API'; +import type { API, SettingsOptions, TagValue, ConnectionValue } from '@/utils/API'; import './style.scss'; @@ -17,6 +17,8 @@ export function Settings({ api }: SettingsProps) { + + ) } @@ -126,45 +128,43 @@ function SettingCard({ api }: SettingsProps) { ); } - - function TagsCard({ api }: SettingsProps) { const [tags, setTags] = useState(); const [toSync, setToSync] = useState(true); useEffect(() => { - if(toSync) { + if (toSync) { api.getTags().then(res => setTags(res)); setToSync(false); } }, [toSync]); - async function toggleTag(tag:TagValue) { + async function toggleTag(tag: TagValue) { let res = await api.editToggleTag(tag.id); toast.success(res.message); setToSync(true); } - async function deleteTag(tag:TagValue) { + async function deleteTag(tag: TagValue) { let res = await api.deleteTag(tag.id); toast.success(res.message); setToSync(true); } - async function addTag(name:string) { + async function addTag(name: string) { let res = await api.addTag(name, false); toast.info(res.message); setToSync(true); } - if(tags){ + if (tags) { return (

Tags

- {tags.map(tag => + {tags.map(tag => -

Menu.show(e as any, { 'Copy': () => navigator.clipboard.writeText(tag.name), @@ -179,7 +179,64 @@ function TagsCard({ api }: SettingsProps) { )} - + +

+
+ ); + } +} + +function ConnectionCard({ api }: SettingsProps) { + const [connections, setConnections] = useState(); + const [toSync, setToSync] = useState(true); + + useEffect(() => { + if (toSync) { + api.getConnections().then(res => setConnections(res)); + setToSync(false); + } + }, [toSync]); + + async function toggleConnection(connection: ConnectionValue) { + let res = await api.editToggleConnection(connection.script); + toast.success(res.message); + setToSync(true); + } + + async function deleteConnection(connection: ConnectionValue) { + let res = await api.deleteConnection(connection.script); + toast.success(res.message); + setToSync(true); + } + + async function addConnection(script: string) { + let res = await api.addConnection(script, false); + toast.info(res.message); + setToSync(true); + } + + if (connections) { + return ( + +

Connections

+
+ {connections.map(connection => + +

Menu.show(e as any, { + 'Copy': () => navigator.clipboard.writeText(connection.script), + 'Delete': () => deleteConnection(connection) + })} + >{connection.script}

+ + + +
+ )} + +
); @@ -187,9 +244,9 @@ function TagsCard({ api }: SettingsProps) { } interface AddCardProps { - onSubmit: (content:string) => void + onSubmit: (content: string) => void } -function AddCard({onSubmit}:AddCardProps) { +function AddCard({ onSubmit }: AddCardProps) { const [active, setActive] = useState(false); const [content, setContent] = useState(''); @@ -198,26 +255,27 @@ function AddCard({onSubmit}:AddCardProps) { setActive(false); } - if(!active){ + if (!active) { return ( ) - }else { + } else { return (
{ e.preventDefault(); - if(content) onSubmit(content); + if (content) onSubmit(content); reset(); - }} + }} className='card' onKeyDown={e => e.key == 'Escape' && reset()} > + ) } -} \ No newline at end of file +} diff --git a/frontend/src/components/Settings/style.scss b/frontend/src/components/Settings/style.scss index 2cae3cd..b84c2d2 100644 --- a/frontend/src/components/Settings/style.scss +++ b/frontend/src/components/Settings/style.scss @@ -67,7 +67,7 @@ section#settings { } } -section#tags { +section#tags, section#connections { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); @@ -117,9 +117,16 @@ section#tags { border: 0; border-bottom: 1px solid $base-color; background-color: elevation(1); + grid-column: span 2; - & + button { + & ~ button { grid-row: 2; + &:nth-of-type(1){ + grid-column: 1; + } + &:nth-of-type(1){ + grid-column: 2; + } } } diff --git a/frontend/src/utils/API.ts b/frontend/src/utils/API.ts index 6ddccb4..fe08f09 100644 --- a/frontend/src/utils/API.ts +++ b/frontend/src/utils/API.ts @@ -169,6 +169,42 @@ export class API { }); return await res.json(); } + + async getConnections(): Promise{ + const res = await fetch(this.backend + '/connections'); + return await res.json(); + } + + async editToggleConnection(script:string): Promise{ + const res = await fetch(encodeURI(this.backend + `/connections/${script}/toggle`), { + method: "PATCH", + headers: { + "Content-Type": "application/json" + } + }); + return await res.json(); + } + + async deleteConnection(script:string): Promise { + const res = await fetch(encodeURI(this.backend + `/connections/${script}`), { + method: "DELETE", + headers: { + "Content-Type": "application/json" + } + }); + return await res.json(); + } + + async addConnection(script:string, active:boolean) { + const res = await fetch(this.backend + '/connections/', { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ script: script, active: active }) + }); + return await res.json(); + } } export interface SerieTableEntry { @@ -198,4 +234,10 @@ export interface TagValue { "id": number, "name": string, "active": boolean +} + +export interface ConnectionValue { + "name": string, + "script": string, + "active": boolean } \ No newline at end of file diff --git a/src/components/api/__init__.py b/src/components/api/__init__.py index c9e1879..1a50e74 100644 --- a/src/components/api/__init__.py +++ b/src/components/api/__init__.py @@ -6,6 +6,7 @@ from .routes.Table import Table from .routes.Settings import Settings from .routes.Tags import Tags +from .routes.Connections import Connections import time @@ -49,5 +50,6 @@ def put_wekeup(): api.register_blueprint(Table(core)) api.register_blueprint(Settings(core)) api.register_blueprint(Tags(core)) + api.register_blueprint(Connections(core)) app.register_blueprint(api) return app \ No newline at end of file diff --git a/src/components/api/routes/Connections.py b/src/components/api/routes/Connections.py new file mode 100644 index 0000000..f0ac90e --- /dev/null +++ b/src/components/api/routes/Connections.py @@ -0,0 +1,92 @@ +from ...backend import Core + +from apiflask import APIBlueprint, abort, fields + +def Connections(core:Core) -> APIBlueprint: + + route = APIBlueprint('connections', __name__, url_prefix='/connections', tag='Connections') + + @route.after_request + def cors(res): + res.headers['Access-Control-Allow-Origin'] = '*' + res.headers['Access-Control-Allow-Headers'] = '*' + res.headers['Access-Control-Allow-Methods'] = '*' + return res + + @route.get('/') + def get_connections(): + """Restituisce le connections.""" + + return core.connections_db.getData() + + @route.get('/