mirror of
https://github.com/morpheus65535/bazarr.git
synced 2025-04-18 20:05:12 -04:00
Merge branch 'refs/heads/development' into non-hi-only
# Conflicts: # bazarr/app/database.py # bazarr/subtitles/indexer/movies.py # bazarr/subtitles/indexer/series.py
This commit is contained in:
commit
6be6e2c1ad
65 changed files with 957 additions and 775 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -36,7 +36,7 @@ jobs:
|
|||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
node-version-file: "${{ env.UI_DIRECTORY }}/.nvmrc"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
|
2
.github/workflows/release_beta_to_dev.yaml
vendored
2
.github/workflows/release_beta_to_dev.yaml
vendored
|
@ -38,7 +38,7 @@ jobs:
|
|||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
node-version-file: "${{ env.UI_DIRECTORY }}/.nvmrc"
|
||||
|
||||
- name: Install Global Tools
|
||||
run: npm install -g release-it auto-changelog
|
||||
|
|
2
.github/workflows/release_dev_to_master.yaml
vendored
2
.github/workflows/release_dev_to_master.yaml
vendored
|
@ -40,7 +40,7 @@ jobs:
|
|||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
node-version-file: "${{ env.UI_DIRECTORY }}/.nvmrc"
|
||||
|
||||
- name: Install Global Tools
|
||||
run: npm install -g release-it auto-changelog
|
||||
|
|
2
.github/workflows/test_bazarr_execution.yml
vendored
2
.github/workflows/test_bazarr_execution.yml
vendored
|
@ -24,7 +24,7 @@ jobs:
|
|||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
node-version-file: "${{ env.UI_DIRECTORY }}/.nvmrc"
|
||||
|
||||
- name: Install UI Dependencies
|
||||
run: npm install
|
||||
|
|
1
frontend/.nvmrc
Normal file
1
frontend/.nvmrc
Normal file
|
@ -0,0 +1 @@
|
|||
20.13
|
|
@ -2,9 +2,12 @@
|
|||
|
||||
## Dependencies
|
||||
|
||||
- [Node.js](https://nodejs.org/)
|
||||
- Either [Node.js](https://nodejs.org/) installed manually or using [Node Version Manager](https://github.com/nvm-sh/nvm)
|
||||
- npm (included in Node.js)
|
||||
|
||||
> The recommended Node version to use and maintained is managed on the `.nvmrc` file. You can either install manually
|
||||
> or use `nvm install` followed by `nvm use`.
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Clone or download this repository
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
// eslint-disable-next-line no-restricted-imports
|
||||
import { dependencies } from "../package.json";
|
||||
|
||||
const vendors = [
|
||||
"react",
|
||||
"react-router-dom",
|
||||
"react-dom",
|
||||
"react-query",
|
||||
"@tanstack/react-query",
|
||||
"axios",
|
||||
"socket.io-client",
|
||||
];
|
||||
|
|
205
frontend/package-lock.json
generated
205
frontend/package-lock.json
generated
|
@ -15,10 +15,10 @@
|
|||
"@mantine/hooks": "^7.10.1",
|
||||
"@mantine/modals": "^7.10.1",
|
||||
"@mantine/notifications": "^7.10.1",
|
||||
"@tanstack/react-query": "^5.40.1",
|
||||
"axios": "^1.6.8",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-query": "^3.39.3",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"socket.io-client": "^4.7.5"
|
||||
},
|
||||
|
@ -28,7 +28,8 @@
|
|||
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.5.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.5.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||
"@tanstack/react-query-devtools": "^5.40.1",
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@testing-library/react": "^15.0.5",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
|
@ -2792,9 +2793,9 @@
|
|||
}
|
||||
},
|
||||
"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==",
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz",
|
||||
"integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"prop-types": "^15.8.1"
|
||||
|
@ -3331,6 +3332,61 @@
|
|||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
|
||||
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
|
||||
},
|
||||
"node_modules/@tanstack/query-core": {
|
||||
"version": "5.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.40.0.tgz",
|
||||
"integrity": "sha512-eD8K8jsOIq0Z5u/QbvOmfvKKE/XC39jA7yv4hgpl/1SRiU+J8QCIwgM/mEHuunQsL87dcvnHqSVLmf9pD4CiaA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-devtools": {
|
||||
"version": "5.37.1",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.37.1.tgz",
|
||||
"integrity": "sha512-XcG4IIHIv0YQKrexTqo2zogQWR1Sz672tX2KsfE9kzB+9zhx44vRKH5si4WDILE1PIWQpStFs/NnrDQrBAUQpg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-query": {
|
||||
"version": "5.40.1",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.40.1.tgz",
|
||||
"integrity": "sha512-gOcmu+gpFd2taHrrgMM9RemLYYEDYfsCqszxCC0xtx+csDa4R8t7Hr7SfWXQP13S2sF+mOxySo/+FNXJFYBqcA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "5.40.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-query-devtools": {
|
||||
"version": "5.40.1",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.40.1.tgz",
|
||||
"integrity": "sha512-/AN2UsbuL+28/KSlBkVHq/4chHTEp4l2UWTKWixXbn4pprLQrZGmQTAKN4tYxZDuNwNZY5+Zp67pDfXj+F/UBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/query-devtools": "5.37.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tanstack/react-query": "^5.40.1",
|
||||
"react": "^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.0.0.tgz",
|
||||
|
@ -4726,15 +4782,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=="
|
||||
},
|
||||
"node_modules/big-integer": {
|
||||
"version": "1.6.52",
|
||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
|
||||
"integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.3.0",
|
||||
|
@ -4752,38 +4801,25 @@
|
|||
"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==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.0.1"
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/broadcast-channel": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz",
|
||||
"integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.7.2",
|
||||
"detect-node": "^2.1.0",
|
||||
"js-sha3": "0.8.0",
|
||||
"microseconds": "0.2.0",
|
||||
"nano-time": "1.0.0",
|
||||
"oblivious-set": "1.0.0",
|
||||
"rimraf": "3.0.2",
|
||||
"unload": "2.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.23.0",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
|
||||
|
@ -5029,7 +5065,8 @@
|
|||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/confusing-browser-globals": {
|
||||
"version": "1.0.11",
|
||||
|
@ -5412,11 +5449,6 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-node": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
|
||||
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="
|
||||
},
|
||||
"node_modules/detect-node-es": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
||||
|
@ -6378,10 +6410,11 @@
|
|||
}
|
||||
},
|
||||
"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==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
|
@ -6492,7 +6525,8 @@
|
|||
"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=="
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
|
@ -6622,6 +6656,7 @@
|
|||
"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",
|
||||
|
@ -6931,6 +6966,7 @@
|
|||
"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"
|
||||
|
@ -6939,7 +6975,8 @@
|
|||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/internal-slot": {
|
||||
"version": "1.0.7",
|
||||
|
@ -7180,6 +7217,7 @@
|
|||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
|
@ -7601,11 +7639,6 @@
|
|||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/js-sha3": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
|
||||
"integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
|
@ -7965,15 +7998,6 @@
|
|||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/match-sorter": {
|
||||
"version": "6.3.4",
|
||||
"resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.4.tgz",
|
||||
"integrity": "sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.8",
|
||||
"remove-accents": "0.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
|
@ -8002,11 +8026,6 @@
|
|||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/microseconds": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz",
|
||||
"integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA=="
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
|
@ -8048,6 +8067,7 @@
|
|||
"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"
|
||||
},
|
||||
|
@ -8099,14 +8119,6 @@
|
|||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/nano-time": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz",
|
||||
"integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==",
|
||||
"dependencies": {
|
||||
"big-integer": "^1.6.16"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
|
@ -8294,15 +8306,11 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/oblivious-set": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz",
|
||||
"integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw=="
|
||||
},
|
||||
"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"
|
||||
}
|
||||
|
@ -8424,6 +8432,7 @@
|
|||
"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"
|
||||
}
|
||||
|
@ -8857,31 +8866,6 @@
|
|||
"react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-query": {
|
||||
"version": "3.39.3",
|
||||
"resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz",
|
||||
"integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"broadcast-channel": "^3.4.1",
|
||||
"match-sorter": "^6.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
|
||||
|
@ -9213,11 +9197,6 @@
|
|||
"jsesc": "bin/jsesc"
|
||||
}
|
||||
},
|
||||
"node_modules/remove-accents": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz",
|
||||
"integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A=="
|
||||
},
|
||||
"node_modules/requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
|
@ -9264,6 +9243,7 @@
|
|||
"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"
|
||||
},
|
||||
|
@ -9866,6 +9846,7 @@
|
|||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
|
@ -10154,15 +10135,6 @@
|
|||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unload": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz",
|
||||
"integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.6.2",
|
||||
"detect-node": "^2.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.0.13",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
|
||||
|
@ -10974,7 +10946,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=="
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.16.0",
|
||||
|
|
|
@ -19,10 +19,10 @@
|
|||
"@mantine/hooks": "^7.10.1",
|
||||
"@mantine/modals": "^7.10.1",
|
||||
"@mantine/notifications": "^7.10.1",
|
||||
"@tanstack/react-query": "^5.40.1",
|
||||
"axios": "^1.6.8",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-query": "^3.39.3",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"socket.io-client": "^4.7.5"
|
||||
},
|
||||
|
@ -32,7 +32,8 @@
|
|||
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.5.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.5.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||
"@tanstack/react-query-devtools": "^5.40.1",
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@testing-library/react": "^15.0.5",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
|
|
|
@ -64,10 +64,9 @@ const AppHeader: FunctionComponent = () => {
|
|||
label="System"
|
||||
tooltip={{ position: "left", openDelay: 2000 }}
|
||||
loading={offline}
|
||||
color={offline ? "yellow" : undefined}
|
||||
c={offline ? "yellow" : undefined}
|
||||
icon={faGear}
|
||||
size="lg"
|
||||
variant="light"
|
||||
></Action>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
|
|
|
@ -130,8 +130,7 @@ const AppNavbar: FunctionComponent = () => {
|
|||
<Group gap="xs">
|
||||
<Action
|
||||
label="Change Theme"
|
||||
color={dark ? "yellow" : "indigo"}
|
||||
variant="subtle"
|
||||
c={dark ? "yellow" : "indigo"}
|
||||
onClick={() => toggleColorScheme()}
|
||||
icon={dark ? faSun : faMoon}
|
||||
></Action>
|
||||
|
@ -139,12 +138,7 @@ const AppNavbar: FunctionComponent = () => {
|
|||
href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=XHHRWXT9YB7WE&source=url"
|
||||
target="_blank"
|
||||
>
|
||||
<Action
|
||||
label="Donate"
|
||||
icon={faHeart}
|
||||
variant="subtle"
|
||||
color="red"
|
||||
></Action>
|
||||
<Action label="Donate" icon={faHeart} c="red"></Action>
|
||||
</Anchor>
|
||||
</Group>
|
||||
</AppShell.Section>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { FunctionComponent, PropsWithChildren } from "react";
|
||||
import {
|
||||
ActionIcon,
|
||||
AppShell,
|
||||
Badge,
|
||||
Button,
|
||||
createTheme,
|
||||
|
@ -13,7 +12,6 @@ import "@mantine/core/styles.layer.css";
|
|||
import "@mantine/notifications/styles.layer.css";
|
||||
import styleVars from "@/assets/_variables.module.scss";
|
||||
import actionIconClasses from "@/assets/action_icon.module.scss";
|
||||
import appShellClasses from "@/assets/app_shell.module.scss";
|
||||
import badgeClasses from "@/assets/badge.module.scss";
|
||||
import buttonClasses from "@/assets/button.module.scss";
|
||||
import paginationClasses from "@/assets/pagination.module.scss";
|
||||
|
@ -39,9 +37,6 @@ const themeProvider = createTheme({
|
|||
ActionIcon: ActionIcon.extend({
|
||||
classNames: actionIconClasses,
|
||||
}),
|
||||
AppShell: AppShell.extend({
|
||||
classNames: appShellClasses,
|
||||
}),
|
||||
Badge: Badge.extend({
|
||||
classNames: badgeClasses,
|
||||
}),
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { useEffect } from "react";
|
||||
import {
|
||||
QueryClient,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from "react-query";
|
||||
} from "@tanstack/react-query";
|
||||
import { usePaginationQuery } from "@/apis/queries/hooks";
|
||||
import { QueryKeys } from "@/apis/queries/keys";
|
||||
import api from "@/apis/raw";
|
||||
|
@ -26,28 +27,36 @@ const cacheEpisodes = (client: QueryClient, episodes: Item.Episode[]) => {
|
|||
|
||||
export function useEpisodesByIds(ids: number[]) {
|
||||
const client = useQueryClient();
|
||||
return useQuery(
|
||||
[QueryKeys.Series, QueryKeys.Episodes, ids],
|
||||
() => api.episodes.byEpisodeId(ids),
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
cacheEpisodes(client, data);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [QueryKeys.Series, QueryKeys.Episodes, ids],
|
||||
queryFn: () => api.episodes.byEpisodeId(ids),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (query.isSuccess && query.data) {
|
||||
cacheEpisodes(client, query.data);
|
||||
}
|
||||
}, [query.isSuccess, query.data, client]);
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
export function useEpisodesBySeriesId(id: number) {
|
||||
const client = useQueryClient();
|
||||
return useQuery(
|
||||
[QueryKeys.Series, id, QueryKeys.Episodes, QueryKeys.All],
|
||||
() => api.episodes.bySeriesId([id]),
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
cacheEpisodes(client, data);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [QueryKeys.Series, id, QueryKeys.Episodes, QueryKeys.All],
|
||||
queryFn: () => api.episodes.bySeriesId([id]),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (query.isSuccess && query.data) {
|
||||
cacheEpisodes(client, query.data);
|
||||
}
|
||||
}, [query.isSuccess, query.data, client]);
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
export function useEpisodeWantedPagination() {
|
||||
|
@ -57,17 +66,18 @@ export function useEpisodeWantedPagination() {
|
|||
}
|
||||
|
||||
export function useEpisodeBlacklist() {
|
||||
return useQuery(
|
||||
[QueryKeys.Series, QueryKeys.Episodes, QueryKeys.Blacklist],
|
||||
() => api.episodes.blacklist(),
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.Series, QueryKeys.Episodes, QueryKeys.Blacklist],
|
||||
queryFn: () => api.episodes.blacklist(),
|
||||
});
|
||||
}
|
||||
|
||||
export function useEpisodeAddBlacklist() {
|
||||
const client = useQueryClient();
|
||||
return useMutation(
|
||||
[QueryKeys.Series, QueryKeys.Episodes, QueryKeys.Blacklist],
|
||||
(param: {
|
||||
return useMutation({
|
||||
mutationKey: [QueryKeys.Series, QueryKeys.Episodes, QueryKeys.Blacklist],
|
||||
|
||||
mutationFn: (param: {
|
||||
seriesId: number;
|
||||
episodeId: number;
|
||||
form: FormType.AddBlacklist;
|
||||
|
@ -75,35 +85,32 @@ export function useEpisodeAddBlacklist() {
|
|||
const { seriesId, episodeId, form } = param;
|
||||
return api.episodes.addBlacklist(seriesId, episodeId, form);
|
||||
},
|
||||
{
|
||||
onSuccess: (_, { seriesId, episodeId }) => {
|
||||
client.invalidateQueries([
|
||||
QueryKeys.Series,
|
||||
QueryKeys.Episodes,
|
||||
QueryKeys.Blacklist,
|
||||
]);
|
||||
client.invalidateQueries([QueryKeys.Series, seriesId]);
|
||||
},
|
||||
|
||||
onSuccess: (_, { seriesId }) => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series, QueryKeys.Episodes, QueryKeys.Blacklist],
|
||||
});
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series, seriesId],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function useEpisodeDeleteBlacklist() {
|
||||
const client = useQueryClient();
|
||||
return useMutation(
|
||||
[QueryKeys.Series, QueryKeys.Episodes, QueryKeys.Blacklist],
|
||||
(param: { all?: boolean; form?: FormType.DeleteBlacklist }) =>
|
||||
return useMutation({
|
||||
mutationKey: [QueryKeys.Series, QueryKeys.Episodes, QueryKeys.Blacklist],
|
||||
|
||||
mutationFn: (param: { all?: boolean; form?: FormType.DeleteBlacklist }) =>
|
||||
api.episodes.deleteBlacklist(param.all, param.form),
|
||||
{
|
||||
onSuccess: (_, param) => {
|
||||
client.invalidateQueries([
|
||||
QueryKeys.Series,
|
||||
QueryKeys.Episodes,
|
||||
QueryKeys.Blacklist,
|
||||
]);
|
||||
},
|
||||
|
||||
onSuccess: (_) => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series, QueryKeys.Episodes, QueryKeys.Blacklist],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function useEpisodeHistoryPagination() {
|
||||
|
@ -115,12 +122,20 @@ export function useEpisodeHistoryPagination() {
|
|||
}
|
||||
|
||||
export function useEpisodeHistory(episodeId?: number) {
|
||||
return useQuery(
|
||||
[QueryKeys.Series, QueryKeys.Episodes, QueryKeys.History, episodeId],
|
||||
() => {
|
||||
return useQuery({
|
||||
queryKey: [
|
||||
QueryKeys.Series,
|
||||
QueryKeys.Episodes,
|
||||
QueryKeys.History,
|
||||
episodeId,
|
||||
],
|
||||
|
||||
queryFn: () => {
|
||||
if (episodeId) {
|
||||
return api.episodes.historyBy(episodeId);
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useQuery } from "react-query";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { QueryKeys } from "@/apis/queries/keys";
|
||||
import api from "@/apis/raw";
|
||||
|
||||
|
@ -8,14 +8,19 @@ export function useHistoryStats(
|
|||
provider: System.Provider | null,
|
||||
language: Language.Info | null,
|
||||
) {
|
||||
return useQuery(
|
||||
[QueryKeys.System, QueryKeys.History, { time, action, provider, language }],
|
||||
() =>
|
||||
return useQuery({
|
||||
queryKey: [
|
||||
QueryKeys.System,
|
||||
QueryKeys.History,
|
||||
{ time, action, provider, language },
|
||||
],
|
||||
|
||||
queryFn: () =>
|
||||
api.history.stats(
|
||||
time,
|
||||
action ?? undefined,
|
||||
provider?.name,
|
||||
language?.code2,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,23 +1,19 @@
|
|||
import { useQuery } from "react-query";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { QueryKeys } from "@/apis/queries/keys";
|
||||
import api from "@/apis/raw";
|
||||
|
||||
export function useLanguages(history?: boolean) {
|
||||
return useQuery(
|
||||
[QueryKeys.System, QueryKeys.Languages, history ?? false],
|
||||
() => api.system.languages(history),
|
||||
{
|
||||
staleTime: Infinity,
|
||||
},
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Languages, history ?? false],
|
||||
queryFn: () => api.system.languages(history),
|
||||
staleTime: Infinity,
|
||||
});
|
||||
}
|
||||
|
||||
export function useLanguageProfiles() {
|
||||
return useQuery(
|
||||
[QueryKeys.System, QueryKeys.LanguagesProfiles],
|
||||
() => api.system.languagesProfileList(),
|
||||
{
|
||||
staleTime: Infinity,
|
||||
},
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.System, QueryKeys.LanguagesProfiles],
|
||||
queryFn: () => api.system.languagesProfileList(),
|
||||
staleTime: Infinity,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { useEffect } from "react";
|
||||
import {
|
||||
QueryClient,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from "react-query";
|
||||
} from "@tanstack/react-query";
|
||||
import { usePaginationQuery } from "@/apis/queries/hooks";
|
||||
import { QueryKeys } from "@/apis/queries/keys";
|
||||
import api from "@/apis/raw";
|
||||
|
@ -16,31 +17,47 @@ const cacheMovies = (client: QueryClient, movies: Item.Movie[]) => {
|
|||
|
||||
export function useMoviesByIds(ids: number[]) {
|
||||
const client = useQueryClient();
|
||||
return useQuery([QueryKeys.Movies, ...ids], () => api.movies.movies(ids), {
|
||||
onSuccess: (data) => {
|
||||
cacheMovies(client, data);
|
||||
},
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [QueryKeys.Movies, ...ids],
|
||||
queryFn: () => api.movies.movies(ids),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (query.isSuccess && query.data) {
|
||||
cacheMovies(client, query.data);
|
||||
}
|
||||
}, [query.isSuccess, query.data, client]);
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
export function useMovieById(id: number) {
|
||||
return useQuery([QueryKeys.Movies, id], async () => {
|
||||
const response = await api.movies.movies([id]);
|
||||
return response.length > 0 ? response[0] : undefined;
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.Movies, id],
|
||||
|
||||
queryFn: async () => {
|
||||
const response = await api.movies.movies([id]);
|
||||
return response.length > 0 ? response[0] : undefined;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useMovies() {
|
||||
const client = useQueryClient();
|
||||
return useQuery(
|
||||
[QueryKeys.Movies, QueryKeys.All],
|
||||
() => api.movies.movies(),
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
cacheMovies(client, data);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [QueryKeys.Movies, QueryKeys.All],
|
||||
queryFn: () => api.movies.movies(),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (query.isSuccess && query.data) {
|
||||
cacheMovies(client, query.data);
|
||||
}
|
||||
}, [query.isSuccess, query.data, client]);
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
export function useMoviesPagination() {
|
||||
|
@ -51,32 +68,36 @@ export function useMoviesPagination() {
|
|||
|
||||
export function useMovieModification() {
|
||||
const client = useQueryClient();
|
||||
return useMutation(
|
||||
[QueryKeys.Movies],
|
||||
(form: FormType.ModifyItem) => api.movies.modify(form),
|
||||
{
|
||||
onSuccess: (_, form) => {
|
||||
form.id.forEach((v) => {
|
||||
client.invalidateQueries([QueryKeys.Movies, v]);
|
||||
return useMutation({
|
||||
mutationKey: [QueryKeys.Movies],
|
||||
mutationFn: (form: FormType.ModifyItem) => api.movies.modify(form),
|
||||
|
||||
onSuccess: (_, form) => {
|
||||
form.id.forEach((v) => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, v],
|
||||
});
|
||||
// TODO: query less
|
||||
client.invalidateQueries([QueryKeys.Movies]);
|
||||
},
|
||||
});
|
||||
// TODO: query less
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function useMovieAction() {
|
||||
const client = useQueryClient();
|
||||
return useMutation(
|
||||
[QueryKeys.Actions, QueryKeys.Movies],
|
||||
(form: FormType.MoviesAction) => api.movies.action(form),
|
||||
{
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries([QueryKeys.Movies]);
|
||||
},
|
||||
return useMutation({
|
||||
mutationKey: [QueryKeys.Actions, QueryKeys.Movies],
|
||||
mutationFn: (form: FormType.MoviesAction) => api.movies.action(form),
|
||||
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function useMovieWantedPagination() {
|
||||
|
@ -86,40 +107,48 @@ export function useMovieWantedPagination() {
|
|||
}
|
||||
|
||||
export function useMovieBlacklist() {
|
||||
return useQuery([QueryKeys.Movies, QueryKeys.Blacklist], () =>
|
||||
api.movies.blacklist(),
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.Movies, QueryKeys.Blacklist],
|
||||
|
||||
queryFn: () => api.movies.blacklist(),
|
||||
});
|
||||
}
|
||||
|
||||
export function useMovieAddBlacklist() {
|
||||
const client = useQueryClient();
|
||||
return useMutation(
|
||||
[QueryKeys.Movies, QueryKeys.Blacklist],
|
||||
(param: { id: number; form: FormType.AddBlacklist }) => {
|
||||
return useMutation({
|
||||
mutationKey: [QueryKeys.Movies, QueryKeys.Blacklist],
|
||||
|
||||
mutationFn: (param: { id: number; form: FormType.AddBlacklist }) => {
|
||||
const { id, form } = param;
|
||||
return api.movies.addBlacklist(id, form);
|
||||
},
|
||||
{
|
||||
onSuccess: (_, { id }) => {
|
||||
client.invalidateQueries([QueryKeys.Movies, QueryKeys.Blacklist]);
|
||||
client.invalidateQueries([QueryKeys.Movies, id]);
|
||||
},
|
||||
|
||||
onSuccess: (_, { id }) => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, QueryKeys.Blacklist],
|
||||
});
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, id],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function useMovieDeleteBlacklist() {
|
||||
const client = useQueryClient();
|
||||
return useMutation(
|
||||
[QueryKeys.Movies, QueryKeys.Blacklist],
|
||||
(param: { all?: boolean; form?: FormType.DeleteBlacklist }) =>
|
||||
return useMutation({
|
||||
mutationKey: [QueryKeys.Movies, QueryKeys.Blacklist],
|
||||
|
||||
mutationFn: (param: { all?: boolean; form?: FormType.DeleteBlacklist }) =>
|
||||
api.movies.deleteBlacklist(param.all, param.form),
|
||||
{
|
||||
onSuccess: (_, param) => {
|
||||
client.invalidateQueries([QueryKeys.Movies, QueryKeys.Blacklist]);
|
||||
},
|
||||
|
||||
onSuccess: (_, param) => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, QueryKeys.Blacklist],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function useMovieHistoryPagination() {
|
||||
|
@ -131,9 +160,15 @@ export function useMovieHistoryPagination() {
|
|||
}
|
||||
|
||||
export function useMovieHistory(radarrId?: number) {
|
||||
return useQuery([QueryKeys.Movies, QueryKeys.History, radarrId], () => {
|
||||
if (radarrId) {
|
||||
return api.movies.historyBy(radarrId);
|
||||
}
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.Movies, QueryKeys.History, radarrId],
|
||||
|
||||
queryFn: () => {
|
||||
if (radarrId) {
|
||||
return api.movies.historyBy(radarrId);
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,66 +1,82 @@
|
|||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { QueryKeys } from "@/apis/queries/keys";
|
||||
import api from "@/apis/raw";
|
||||
|
||||
export function useSystemProviders(history?: boolean) {
|
||||
return useQuery(
|
||||
[QueryKeys.System, QueryKeys.Providers, history ?? false],
|
||||
() => api.providers.providers(history),
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Providers, history ?? false],
|
||||
queryFn: () => api.providers.providers(history),
|
||||
});
|
||||
}
|
||||
|
||||
export function useMoviesProvider(radarrId?: number) {
|
||||
return useQuery(
|
||||
[QueryKeys.System, QueryKeys.Providers, QueryKeys.Movies, radarrId],
|
||||
() => {
|
||||
return useQuery({
|
||||
queryKey: [
|
||||
QueryKeys.System,
|
||||
QueryKeys.Providers,
|
||||
QueryKeys.Movies,
|
||||
radarrId,
|
||||
],
|
||||
|
||||
queryFn: () => {
|
||||
if (radarrId) {
|
||||
return api.providers.movies(radarrId);
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
{
|
||||
staleTime: 0,
|
||||
},
|
||||
);
|
||||
|
||||
staleTime: 0,
|
||||
});
|
||||
}
|
||||
|
||||
export function useEpisodesProvider(episodeId?: number) {
|
||||
return useQuery(
|
||||
[QueryKeys.System, QueryKeys.Providers, QueryKeys.Episodes, episodeId],
|
||||
() => {
|
||||
return useQuery({
|
||||
queryKey: [
|
||||
QueryKeys.System,
|
||||
QueryKeys.Providers,
|
||||
QueryKeys.Episodes,
|
||||
episodeId,
|
||||
],
|
||||
|
||||
queryFn: () => {
|
||||
if (episodeId) {
|
||||
return api.providers.episodes(episodeId);
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
{
|
||||
staleTime: 0,
|
||||
},
|
||||
);
|
||||
|
||||
staleTime: 0,
|
||||
});
|
||||
}
|
||||
|
||||
export function useResetProvider() {
|
||||
const client = useQueryClient();
|
||||
return useMutation(
|
||||
[QueryKeys.System, QueryKeys.Providers],
|
||||
() => api.providers.reset(),
|
||||
{
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries([QueryKeys.System, QueryKeys.Providers]);
|
||||
},
|
||||
return useMutation({
|
||||
mutationKey: [QueryKeys.System, QueryKeys.Providers],
|
||||
mutationFn: () => api.providers.reset(),
|
||||
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Providers],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function useDownloadEpisodeSubtitles() {
|
||||
const client = useQueryClient();
|
||||
|
||||
return useMutation(
|
||||
[
|
||||
return useMutation({
|
||||
mutationKey: [
|
||||
QueryKeys.System,
|
||||
QueryKeys.Providers,
|
||||
QueryKeys.Subtitles,
|
||||
QueryKeys.Episodes,
|
||||
],
|
||||
(param: {
|
||||
|
||||
mutationFn: (param: {
|
||||
seriesId: number;
|
||||
episodeId: number;
|
||||
form: FormType.ManualDownload;
|
||||
|
@ -70,30 +86,33 @@ export function useDownloadEpisodeSubtitles() {
|
|||
param.episodeId,
|
||||
param.form,
|
||||
),
|
||||
{
|
||||
onSuccess: (_, param) => {
|
||||
client.invalidateQueries([QueryKeys.Series, param.seriesId]);
|
||||
},
|
||||
|
||||
onSuccess: (_, param) => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series, param.seriesId],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function useDownloadMovieSubtitles() {
|
||||
const client = useQueryClient();
|
||||
|
||||
return useMutation(
|
||||
[
|
||||
return useMutation({
|
||||
mutationKey: [
|
||||
QueryKeys.System,
|
||||
QueryKeys.Providers,
|
||||
QueryKeys.Subtitles,
|
||||
QueryKeys.Movies,
|
||||
],
|
||||
(param: { radarrId: number; form: FormType.ManualDownload }) =>
|
||||
|
||||
mutationFn: (param: { radarrId: number; form: FormType.ManualDownload }) =>
|
||||
api.providers.downloadMovieSubtitle(param.radarrId, param.form),
|
||||
{
|
||||
onSuccess: (_, param) => {
|
||||
client.invalidateQueries([QueryKeys.Movies, param.radarrId]);
|
||||
},
|
||||
|
||||
onSuccess: (_, param) => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, param.radarrId],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { useEffect } from "react";
|
||||
import {
|
||||
QueryClient,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from "react-query";
|
||||
} from "@tanstack/react-query";
|
||||
import { usePaginationQuery } from "@/apis/queries/hooks";
|
||||
import { QueryKeys } from "@/apis/queries/keys";
|
||||
import api from "@/apis/raw";
|
||||
|
@ -16,31 +17,47 @@ function cacheSeries(client: QueryClient, series: Item.Series[]) {
|
|||
|
||||
export function useSeriesByIds(ids: number[]) {
|
||||
const client = useQueryClient();
|
||||
return useQuery([QueryKeys.Series, ...ids], () => api.series.series(ids), {
|
||||
onSuccess: (data) => {
|
||||
cacheSeries(client, data);
|
||||
},
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [QueryKeys.Series, ...ids],
|
||||
queryFn: () => api.series.series(ids),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (query.isSuccess && query.data) {
|
||||
cacheSeries(client, query.data);
|
||||
}
|
||||
}, [query.isSuccess, query.data, client]);
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
export function useSeriesById(id: number) {
|
||||
return useQuery([QueryKeys.Series, id], async () => {
|
||||
const response = await api.series.series([id]);
|
||||
return response.length > 0 ? response[0] : undefined;
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.Series, id],
|
||||
|
||||
queryFn: async () => {
|
||||
const response = await api.series.series([id]);
|
||||
return response.length > 0 ? response[0] : undefined;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useSeries() {
|
||||
const client = useQueryClient();
|
||||
return useQuery(
|
||||
[QueryKeys.Series, QueryKeys.All],
|
||||
() => api.series.series(),
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
cacheSeries(client, data);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [QueryKeys.Series, QueryKeys.All],
|
||||
queryFn: () => api.series.series(),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (query.isSuccess && query.data) {
|
||||
cacheSeries(client, query.data);
|
||||
}
|
||||
}, [query.isSuccess, query.data, client]);
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
export function useSeriesPagination() {
|
||||
|
@ -51,29 +68,33 @@ export function useSeriesPagination() {
|
|||
|
||||
export function useSeriesModification() {
|
||||
const client = useQueryClient();
|
||||
return useMutation(
|
||||
[QueryKeys.Series],
|
||||
(form: FormType.ModifyItem) => api.series.modify(form),
|
||||
{
|
||||
onSuccess: (_, form) => {
|
||||
form.id.forEach((v) => {
|
||||
client.invalidateQueries([QueryKeys.Series, v]);
|
||||
return useMutation({
|
||||
mutationKey: [QueryKeys.Series],
|
||||
mutationFn: (form: FormType.ModifyItem) => api.series.modify(form),
|
||||
|
||||
onSuccess: (_, form) => {
|
||||
form.id.forEach((v) => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series, v],
|
||||
});
|
||||
client.invalidateQueries([QueryKeys.Series]);
|
||||
},
|
||||
});
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function useSeriesAction() {
|
||||
const client = useQueryClient();
|
||||
return useMutation(
|
||||
[QueryKeys.Actions, QueryKeys.Series],
|
||||
(form: FormType.SeriesAction) => api.series.action(form),
|
||||
{
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries([QueryKeys.Series]);
|
||||
},
|
||||
return useMutation({
|
||||
mutationKey: [QueryKeys.Actions, QueryKeys.Series],
|
||||
mutationFn: (form: FormType.SeriesAction) => api.series.action(form),
|
||||
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,16 +1,28 @@
|
|||
import { useIsMutating } from "react-query";
|
||||
import { useIsMutating } from "@tanstack/react-query";
|
||||
import { QueryKeys } from "@/apis/queries/keys";
|
||||
|
||||
export function useIsAnyActionRunning() {
|
||||
return useIsMutating([QueryKeys.Actions]) > 0;
|
||||
return (
|
||||
useIsMutating({
|
||||
mutationKey: [QueryKeys.Actions],
|
||||
}) > 0
|
||||
);
|
||||
}
|
||||
|
||||
export function useIsMovieActionRunning() {
|
||||
return useIsMutating([QueryKeys.Actions, QueryKeys.Movies]) > 0;
|
||||
return (
|
||||
useIsMutating({
|
||||
mutationKey: [QueryKeys.Actions, QueryKeys.Movies],
|
||||
}) > 0
|
||||
);
|
||||
}
|
||||
|
||||
export function useIsSeriesActionRunning() {
|
||||
return useIsMutating([QueryKeys.Actions, QueryKeys.Series]) > 0;
|
||||
return (
|
||||
useIsMutating({
|
||||
mutationKey: [QueryKeys.Actions, QueryKeys.Series],
|
||||
}) > 0
|
||||
);
|
||||
}
|
||||
|
||||
export function useIsAnyMutationRunning() {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { QueryKeys } from "@/apis/queries/keys";
|
||||
import api from "@/apis/raw";
|
||||
|
||||
|
@ -8,23 +8,29 @@ export function useSubtitleAction() {
|
|||
action: string;
|
||||
form: FormType.ModifySubtitle;
|
||||
}
|
||||
return useMutation(
|
||||
[QueryKeys.Subtitles],
|
||||
(param: Param) => api.subtitles.modify(param.action, param.form),
|
||||
{
|
||||
onSuccess: (_, param) => {
|
||||
client.invalidateQueries([QueryKeys.History]);
|
||||
return useMutation({
|
||||
mutationKey: [QueryKeys.Subtitles],
|
||||
mutationFn: (param: Param) =>
|
||||
api.subtitles.modify(param.action, param.form),
|
||||
|
||||
// TODO: Query less
|
||||
const { type, id } = param.form;
|
||||
if (type === "episode") {
|
||||
client.invalidateQueries([QueryKeys.Series, id]);
|
||||
} else {
|
||||
client.invalidateQueries([QueryKeys.Movies, id]);
|
||||
}
|
||||
},
|
||||
onSuccess: (_, param) => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.History],
|
||||
});
|
||||
|
||||
// TODO: Query less
|
||||
const { type, id } = param.form;
|
||||
if (type === "episode") {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series, id],
|
||||
});
|
||||
} else {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, id],
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function useEpisodeSubtitleModification() {
|
||||
|
@ -36,42 +42,48 @@ export function useEpisodeSubtitleModification() {
|
|||
form: T;
|
||||
}
|
||||
|
||||
const download = useMutation(
|
||||
[QueryKeys.Subtitles, QueryKeys.Episodes],
|
||||
(param: Param<FormType.Subtitle>) =>
|
||||
const download = useMutation({
|
||||
mutationKey: [QueryKeys.Subtitles, QueryKeys.Episodes],
|
||||
|
||||
mutationFn: (param: Param<FormType.Subtitle>) =>
|
||||
api.episodes.downloadSubtitles(
|
||||
param.seriesId,
|
||||
param.episodeId,
|
||||
param.form,
|
||||
),
|
||||
{
|
||||
onSuccess: (_, param) => {
|
||||
client.invalidateQueries([QueryKeys.Series, param.seriesId]);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const remove = useMutation(
|
||||
[QueryKeys.Subtitles, QueryKeys.Episodes],
|
||||
(param: Param<FormType.DeleteSubtitle>) =>
|
||||
onSuccess: (_, param) => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series, param.seriesId],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const remove = useMutation({
|
||||
mutationKey: [QueryKeys.Subtitles, QueryKeys.Episodes],
|
||||
|
||||
mutationFn: (param: Param<FormType.DeleteSubtitle>) =>
|
||||
api.episodes.deleteSubtitles(param.seriesId, param.episodeId, param.form),
|
||||
{
|
||||
onSuccess: (_, param) => {
|
||||
client.invalidateQueries([QueryKeys.Series, param.seriesId]);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const upload = useMutation(
|
||||
[QueryKeys.Subtitles, QueryKeys.Episodes],
|
||||
(param: Param<FormType.UploadSubtitle>) =>
|
||||
api.episodes.uploadSubtitles(param.seriesId, param.episodeId, param.form),
|
||||
{
|
||||
onSuccess: (_, { seriesId }) => {
|
||||
client.invalidateQueries([QueryKeys.Series, seriesId]);
|
||||
},
|
||||
onSuccess: (_, param) => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series, param.seriesId],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const upload = useMutation({
|
||||
mutationKey: [QueryKeys.Subtitles, QueryKeys.Episodes],
|
||||
|
||||
mutationFn: (param: Param<FormType.UploadSubtitle>) =>
|
||||
api.episodes.uploadSubtitles(param.seriesId, param.episodeId, param.form),
|
||||
|
||||
onSuccess: (_, { seriesId }) => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series, seriesId],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return { download, remove, upload };
|
||||
}
|
||||
|
@ -84,46 +96,54 @@ export function useMovieSubtitleModification() {
|
|||
form: T;
|
||||
}
|
||||
|
||||
const download = useMutation(
|
||||
[QueryKeys.Subtitles, QueryKeys.Movies],
|
||||
(param: Param<FormType.Subtitle>) =>
|
||||
const download = useMutation({
|
||||
mutationKey: [QueryKeys.Subtitles, QueryKeys.Movies],
|
||||
|
||||
mutationFn: (param: Param<FormType.Subtitle>) =>
|
||||
api.movies.downloadSubtitles(param.radarrId, param.form),
|
||||
{
|
||||
onSuccess: (_, param) => {
|
||||
client.invalidateQueries([QueryKeys.Movies, param.radarrId]);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const remove = useMutation(
|
||||
[QueryKeys.Subtitles, QueryKeys.Movies],
|
||||
(param: Param<FormType.DeleteSubtitle>) =>
|
||||
onSuccess: (_, param) => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, param.radarrId],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const remove = useMutation({
|
||||
mutationKey: [QueryKeys.Subtitles, QueryKeys.Movies],
|
||||
|
||||
mutationFn: (param: Param<FormType.DeleteSubtitle>) =>
|
||||
api.movies.deleteSubtitles(param.radarrId, param.form),
|
||||
{
|
||||
onSuccess: (_, param) => {
|
||||
client.invalidateQueries([QueryKeys.Movies, param.radarrId]);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const upload = useMutation(
|
||||
[QueryKeys.Subtitles, QueryKeys.Movies],
|
||||
(param: Param<FormType.UploadSubtitle>) =>
|
||||
api.movies.uploadSubtitles(param.radarrId, param.form),
|
||||
{
|
||||
onSuccess: (_, { radarrId }) => {
|
||||
client.invalidateQueries([QueryKeys.Movies, radarrId]);
|
||||
},
|
||||
onSuccess: (_, param) => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, param.radarrId],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const upload = useMutation({
|
||||
mutationKey: [QueryKeys.Subtitles, QueryKeys.Movies],
|
||||
|
||||
mutationFn: (param: Param<FormType.UploadSubtitle>) =>
|
||||
api.movies.uploadSubtitles(param.radarrId, param.form),
|
||||
|
||||
onSuccess: (_, { radarrId }) => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, radarrId],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return { download, remove, upload };
|
||||
}
|
||||
|
||||
export function useSubtitleInfos(names: string[]) {
|
||||
return useQuery([QueryKeys.Subtitles, QueryKeys.Infos, names], () =>
|
||||
api.subtitles.info(names),
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.Subtitles, QueryKeys.Infos, names],
|
||||
|
||||
queryFn: () => api.subtitles.info(names),
|
||||
});
|
||||
}
|
||||
|
||||
export function useRefTracksByEpisodeId(
|
||||
|
@ -131,11 +151,17 @@ export function useRefTracksByEpisodeId(
|
|||
sonarrEpisodeId: number,
|
||||
isEpisode: boolean,
|
||||
) {
|
||||
return useQuery(
|
||||
[QueryKeys.Episodes, sonarrEpisodeId, QueryKeys.Subtitles, subtitlesPath],
|
||||
() => api.subtitles.getRefTracksByEpisodeId(subtitlesPath, sonarrEpisodeId),
|
||||
{ enabled: isEpisode },
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: [
|
||||
QueryKeys.Episodes,
|
||||
sonarrEpisodeId,
|
||||
QueryKeys.Subtitles,
|
||||
subtitlesPath,
|
||||
],
|
||||
queryFn: () =>
|
||||
api.subtitles.getRefTracksByEpisodeId(subtitlesPath, sonarrEpisodeId),
|
||||
enabled: isEpisode,
|
||||
});
|
||||
}
|
||||
|
||||
export function useRefTracksByMovieId(
|
||||
|
@ -143,9 +169,15 @@ export function useRefTracksByMovieId(
|
|||
radarrMovieId: number,
|
||||
isMovie: boolean,
|
||||
) {
|
||||
return useQuery(
|
||||
[QueryKeys.Movies, radarrMovieId, QueryKeys.Subtitles, subtitlesPath],
|
||||
() => api.subtitles.getRefTracksByMovieId(subtitlesPath, radarrMovieId),
|
||||
{ enabled: isMovie },
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: [
|
||||
QueryKeys.Movies,
|
||||
radarrMovieId,
|
||||
QueryKeys.Subtitles,
|
||||
subtitlesPath,
|
||||
],
|
||||
queryFn: () =>
|
||||
api.subtitles.getRefTracksByMovieId(subtitlesPath, radarrMovieId),
|
||||
enabled: isMovie,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
import { useMemo } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { QueryKeys } from "@/apis/queries/keys";
|
||||
import api from "@/apis/raw";
|
||||
import { Environment } from "@/utilities";
|
||||
import { setAuthenticated } from "@/utilities/event";
|
||||
|
||||
export function useBadges() {
|
||||
return useQuery(
|
||||
[QueryKeys.System, QueryKeys.Badges],
|
||||
() => api.badges.all(),
|
||||
{
|
||||
refetchOnWindowFocus: "always",
|
||||
refetchInterval: 1000 * 60,
|
||||
staleTime: 1000 * 10,
|
||||
},
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Badges],
|
||||
queryFn: () => api.badges.all(),
|
||||
refetchOnWindowFocus: "always",
|
||||
refetchInterval: 1000 * 60,
|
||||
staleTime: 1000 * 10,
|
||||
});
|
||||
}
|
||||
|
||||
export function useFileSystem(
|
||||
|
@ -22,9 +20,10 @@ export function useFileSystem(
|
|||
path: string,
|
||||
enabled: boolean,
|
||||
) {
|
||||
return useQuery(
|
||||
[QueryKeys.FileSystem, type, path],
|
||||
() => {
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.FileSystem, type, path],
|
||||
|
||||
queryFn: () => {
|
||||
if (type === "bazarr") {
|
||||
return api.files.bazarr(path);
|
||||
} else if (type === "radarr") {
|
||||
|
@ -32,53 +31,63 @@ export function useFileSystem(
|
|||
} else if (type === "sonarr") {
|
||||
return api.files.sonarr(path);
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
{
|
||||
enabled,
|
||||
},
|
||||
);
|
||||
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
|
||||
export function useSystemSettings() {
|
||||
return useQuery(
|
||||
[QueryKeys.System, QueryKeys.Settings],
|
||||
() => api.system.settings(),
|
||||
{
|
||||
staleTime: Infinity,
|
||||
},
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Settings],
|
||||
queryFn: () => api.system.settings(),
|
||||
staleTime: Infinity,
|
||||
});
|
||||
}
|
||||
|
||||
export function useSettingsMutation() {
|
||||
const client = useQueryClient();
|
||||
return useMutation(
|
||||
[QueryKeys.System, QueryKeys.Settings],
|
||||
(data: LooseObject) => api.system.updateSettings(data),
|
||||
{
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries([QueryKeys.System]);
|
||||
client.invalidateQueries([QueryKeys.Series]);
|
||||
client.invalidateQueries([QueryKeys.Episodes]);
|
||||
client.invalidateQueries([QueryKeys.Movies]);
|
||||
client.invalidateQueries([QueryKeys.Wanted]);
|
||||
client.invalidateQueries([QueryKeys.Badges]);
|
||||
},
|
||||
return useMutation({
|
||||
mutationKey: [QueryKeys.System, QueryKeys.Settings],
|
||||
mutationFn: (data: LooseObject) => api.system.updateSettings(data),
|
||||
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.System],
|
||||
});
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series],
|
||||
});
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Episodes],
|
||||
});
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies],
|
||||
});
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Wanted],
|
||||
});
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Badges],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function useServerSearch(query: string, enabled: boolean) {
|
||||
return useQuery(
|
||||
[QueryKeys.System, QueryKeys.Search, query],
|
||||
() => api.system.search(query),
|
||||
{
|
||||
enabled,
|
||||
},
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Search, query],
|
||||
queryFn: () => api.system.search(query),
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
|
||||
export function useSystemLogs() {
|
||||
return useQuery([QueryKeys.System, QueryKeys.Logs], () => api.system.logs(), {
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Logs],
|
||||
queryFn: () => api.system.logs(),
|
||||
refetchOnWindowFocus: "always",
|
||||
refetchInterval: 1000 * 60,
|
||||
staleTime: 1000 * 10,
|
||||
|
@ -87,171 +96,187 @@ export function useSystemLogs() {
|
|||
|
||||
export function useDeleteLogs() {
|
||||
const client = useQueryClient();
|
||||
return useMutation(
|
||||
[QueryKeys.System, QueryKeys.Logs],
|
||||
() => api.system.deleteLogs(),
|
||||
{
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries([QueryKeys.System, QueryKeys.Logs]);
|
||||
},
|
||||
return useMutation({
|
||||
mutationKey: [QueryKeys.System, QueryKeys.Logs],
|
||||
mutationFn: () => api.system.deleteLogs(),
|
||||
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Logs],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function useSystemAnnouncements() {
|
||||
return useQuery(
|
||||
[QueryKeys.System, QueryKeys.Announcements],
|
||||
() => api.system.announcements(),
|
||||
{
|
||||
refetchOnWindowFocus: "always",
|
||||
refetchInterval: 1000 * 60,
|
||||
staleTime: 1000 * 10,
|
||||
},
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Announcements],
|
||||
queryFn: () => api.system.announcements(),
|
||||
refetchOnWindowFocus: "always",
|
||||
refetchInterval: 1000 * 60,
|
||||
staleTime: 1000 * 10,
|
||||
});
|
||||
}
|
||||
|
||||
export function useSystemAnnouncementsAddDismiss() {
|
||||
const client = useQueryClient();
|
||||
return useMutation(
|
||||
[QueryKeys.System, QueryKeys.Announcements],
|
||||
(param: { hash: string }) => {
|
||||
return useMutation({
|
||||
mutationKey: [QueryKeys.System, QueryKeys.Announcements],
|
||||
|
||||
mutationFn: (param: { hash: string }) => {
|
||||
const { hash } = param;
|
||||
return api.system.addAnnouncementsDismiss(hash);
|
||||
},
|
||||
{
|
||||
onSuccess: (_, { hash }) => {
|
||||
client.invalidateQueries([QueryKeys.System, QueryKeys.Announcements]);
|
||||
client.invalidateQueries([QueryKeys.System, QueryKeys.Badges]);
|
||||
},
|
||||
|
||||
onSuccess: (_, { hash }) => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Announcements],
|
||||
});
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Badges],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function useSystemTasks() {
|
||||
return useQuery(
|
||||
[QueryKeys.System, QueryKeys.Tasks],
|
||||
() => api.system.tasks(),
|
||||
{
|
||||
refetchOnWindowFocus: "always",
|
||||
refetchInterval: 1000 * 60,
|
||||
staleTime: 1000 * 10,
|
||||
},
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Tasks],
|
||||
queryFn: () => api.system.tasks(),
|
||||
refetchOnWindowFocus: "always",
|
||||
refetchInterval: 1000 * 60,
|
||||
staleTime: 1000 * 10,
|
||||
});
|
||||
}
|
||||
|
||||
export function useRunTask() {
|
||||
const client = useQueryClient();
|
||||
return useMutation(
|
||||
[QueryKeys.System, QueryKeys.Tasks],
|
||||
(id: string) => api.system.runTask(id),
|
||||
{
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries([QueryKeys.System, QueryKeys.Tasks]);
|
||||
client.invalidateQueries([QueryKeys.System, QueryKeys.Backups]);
|
||||
},
|
||||
return useMutation({
|
||||
mutationKey: [QueryKeys.System, QueryKeys.Tasks],
|
||||
mutationFn: (id: string) => api.system.runTask(id),
|
||||
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Tasks],
|
||||
});
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Backups],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function useSystemBackups() {
|
||||
return useQuery([QueryKeys.System, "backups"], () => api.system.backups());
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.System, "backups"],
|
||||
queryFn: () => api.system.backups(),
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateBackups() {
|
||||
const client = useQueryClient();
|
||||
return useMutation(
|
||||
[QueryKeys.System, QueryKeys.Backups],
|
||||
() => api.system.createBackups(),
|
||||
{
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries([QueryKeys.System, QueryKeys.Backups]);
|
||||
},
|
||||
return useMutation({
|
||||
mutationKey: [QueryKeys.System, QueryKeys.Backups],
|
||||
mutationFn: () => api.system.createBackups(),
|
||||
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Backups],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function useRestoreBackups() {
|
||||
const client = useQueryClient();
|
||||
return useMutation(
|
||||
[QueryKeys.System, QueryKeys.Backups],
|
||||
(filename: string) => api.system.restoreBackups(filename),
|
||||
{
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries([QueryKeys.System, QueryKeys.Backups]);
|
||||
},
|
||||
return useMutation({
|
||||
mutationKey: [QueryKeys.System, QueryKeys.Backups],
|
||||
mutationFn: (filename: string) => api.system.restoreBackups(filename),
|
||||
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Backups],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteBackups() {
|
||||
const client = useQueryClient();
|
||||
return useMutation(
|
||||
[QueryKeys.System, QueryKeys.Backups],
|
||||
(filename: string) => api.system.deleteBackups(filename),
|
||||
{
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries([QueryKeys.System, QueryKeys.Backups]);
|
||||
},
|
||||
return useMutation({
|
||||
mutationKey: [QueryKeys.System, QueryKeys.Backups],
|
||||
mutationFn: (filename: string) => api.system.deleteBackups(filename),
|
||||
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Backups],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function useSystemStatus() {
|
||||
return useQuery([QueryKeys.System, "status"], () => api.system.status());
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.System, "status"],
|
||||
queryFn: () => api.system.status(),
|
||||
});
|
||||
}
|
||||
|
||||
export function useSystemHealth() {
|
||||
return useQuery([QueryKeys.System, "health"], () => api.system.health());
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.System, "health"],
|
||||
queryFn: () => api.system.health(),
|
||||
});
|
||||
}
|
||||
|
||||
export function useSystemReleases() {
|
||||
return useQuery([QueryKeys.System, "releases"], () => api.system.releases());
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.System, "releases"],
|
||||
queryFn: () => api.system.releases(),
|
||||
});
|
||||
}
|
||||
|
||||
export function useSystem() {
|
||||
const client = useQueryClient();
|
||||
const { mutate: logout, isLoading: isLoggingOut } = useMutation(
|
||||
[QueryKeys.System, QueryKeys.Actions],
|
||||
() => api.system.logout(),
|
||||
{
|
||||
onSuccess: () => {
|
||||
setAuthenticated(false);
|
||||
client.clear();
|
||||
},
|
||||
},
|
||||
);
|
||||
const { mutate: logout, isPending: isLoggingOut } = useMutation({
|
||||
mutationKey: [QueryKeys.System, QueryKeys.Actions],
|
||||
mutationFn: () => api.system.logout(),
|
||||
|
||||
const { mutate: login, isLoading: isLoggingIn } = useMutation(
|
||||
[QueryKeys.System, QueryKeys.Actions],
|
||||
(param: { username: string; password: string }) =>
|
||||
onSuccess: () => {
|
||||
setAuthenticated(false);
|
||||
client.clear();
|
||||
},
|
||||
});
|
||||
|
||||
const { mutate: login, isPending: isLoggingIn } = useMutation({
|
||||
mutationKey: [QueryKeys.System, QueryKeys.Actions],
|
||||
|
||||
mutationFn: (param: { username: string; password: string }) =>
|
||||
api.system.login(param.username, param.password),
|
||||
{
|
||||
onSuccess: () => {
|
||||
// TODO: Hard-coded value
|
||||
window.location.replace(Environment.baseUrl);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: shutdown, isLoading: isShuttingDown } = useMutation(
|
||||
[QueryKeys.System, QueryKeys.Actions],
|
||||
() => api.system.shutdown(),
|
||||
{
|
||||
onSuccess: () => {
|
||||
client.clear();
|
||||
},
|
||||
onSuccess: () => {
|
||||
// TODO: Hard-coded value
|
||||
window.location.replace(Environment.baseUrl);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const { mutate: restart, isLoading: isRestarting } = useMutation(
|
||||
[QueryKeys.System, QueryKeys.Actions],
|
||||
() => api.system.restart(),
|
||||
{
|
||||
onSuccess: () => {
|
||||
client.clear();
|
||||
},
|
||||
const { mutate: shutdown, isPending: isShuttingDown } = useMutation({
|
||||
mutationKey: [QueryKeys.System, QueryKeys.Actions],
|
||||
mutationFn: () => api.system.shutdown(),
|
||||
|
||||
onSuccess: () => {
|
||||
client.clear();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const { mutate: restart, isPending: isRestarting } = useMutation({
|
||||
mutationKey: [QueryKeys.System, QueryKeys.Actions],
|
||||
mutationFn: () => api.system.restart(),
|
||||
|
||||
onSuccess: () => {
|
||||
client.clear();
|
||||
},
|
||||
});
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
useQuery,
|
||||
useQueryClient,
|
||||
UseQueryResult,
|
||||
} from "react-query";
|
||||
} from "@tanstack/react-query";
|
||||
import { GetItemId, useOnValueChange } from "@/utilities";
|
||||
import { usePageSize } from "@/utilities/storage";
|
||||
import { QueryKeys } from "./keys";
|
||||
|
@ -39,31 +39,31 @@ export function usePaginationQuery<
|
|||
|
||||
const start = page * pageSize;
|
||||
|
||||
const results = useQuery(
|
||||
[...queryKey, QueryKeys.Range, { start, size: pageSize }],
|
||||
() => {
|
||||
const results = useQuery({
|
||||
queryKey: [...queryKey, QueryKeys.Range, { start, size: pageSize }],
|
||||
|
||||
queryFn: () => {
|
||||
const param: Parameter.Range = {
|
||||
start,
|
||||
length: pageSize,
|
||||
};
|
||||
return queryFn(param);
|
||||
},
|
||||
{
|
||||
onSuccess: ({ data }) => {
|
||||
if (cacheIndividual) {
|
||||
data.forEach((item) => {
|
||||
const id = GetItemId(item);
|
||||
if (id) {
|
||||
client.setQueryData([...queryKey, id], item);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const { data } = results;
|
||||
|
||||
useEffect(() => {
|
||||
if (results.isSuccess && results.data && cacheIndividual) {
|
||||
results.data.data.forEach((item) => {
|
||||
const id = GetItemId(item);
|
||||
if (id) {
|
||||
client.setQueryData([...queryKey, id], item);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [results.isSuccess, results.data, client, cacheIndividual, queryKey]);
|
||||
|
||||
const totalCount = data?.total ?? 0;
|
||||
const pageCount = Math.ceil(totalCount / pageSize);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { QueryClient } from "react-query";
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
|
@ -6,7 +6,11 @@ const queryClient = new QueryClient({
|
|||
refetchOnWindowFocus: false,
|
||||
retry: false,
|
||||
staleTime: 1000 * 60,
|
||||
keepPreviousData: true,
|
||||
networkMode: "offlineFirst",
|
||||
placeholderData: (previousData: object) => previousData,
|
||||
},
|
||||
mutations: {
|
||||
networkMode: "offlineFirst",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -74,3 +74,9 @@ $header-height: 64px;
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
@include dark {
|
||||
--mantine-color-body: var(--mantine-color-dark-8);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,16 @@
|
|||
@layer mantine {
|
||||
.root {
|
||||
background-color: transparent;
|
||||
|
||||
&[data-variant="light"] {
|
||||
color: var(--mantine-color-dark-0);
|
||||
}
|
||||
|
||||
&[data-variant="dark"] {
|
||||
--ai-bg: transparent;
|
||||
--ai-hover: darken(var(--mantine-color-grape-light), 0.2);
|
||||
}
|
||||
--ai-bg: transparent;
|
||||
|
||||
@include light {
|
||||
&[data-variant="light"] {
|
||||
background-color: var(--mantine-color-gray-1);
|
||||
color: var(--mantine-color-dark-2);
|
||||
}
|
||||
color: var(--mantine-color-dark-2);
|
||||
--ai-hover: var(--mantine-color-gray-1);
|
||||
--ai-hover-color: var(--mantine-color-gray-1);
|
||||
}
|
||||
|
||||
&[data-variant="dark"] {
|
||||
--ai-color: var(--mantine-color-dark-filled);
|
||||
}
|
||||
@include dark {
|
||||
color: var(--mantine-color-dark-0);
|
||||
--ai-hover: var(--mantine-color-gray-8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
@layer mantine {
|
||||
.main {
|
||||
@include dark {
|
||||
background-color: var(--mantine-color-dark-8);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
background-color: transparentize($color-brand-6, 0.8);
|
||||
|
||||
&[data-variant="warning"] {
|
||||
color: lighten($color-warning-2, 1);
|
||||
color: lighten($color-warning-2, 0.8);
|
||||
background-color: transparentize($color-warning-6, 0.8);
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,11 @@
|
|||
background-color: transparentize($color-disabled-7, 0.8);
|
||||
}
|
||||
|
||||
&[data-variant="light"] {
|
||||
color: var(--mantine-color-dark-0);
|
||||
background-color: transparentize($color-disabled-9, 0.8);
|
||||
}
|
||||
|
||||
@include light {
|
||||
color: $color-brand-6;
|
||||
background-color: transparentize($color-brand-3, 0.8);
|
||||
|
@ -35,6 +40,11 @@
|
|||
color: darken($color-highlight-6, 1);
|
||||
background-color: transparentize($color-highlight-5, 0.8);
|
||||
}
|
||||
|
||||
&[data-variant="light"] {
|
||||
color: var(--mantine-color-black);
|
||||
background-color: var(--mantine-color-gray-5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
.result {
|
||||
@include light {
|
||||
color: var(--mantine-color-dark-8);
|
||||
}
|
||||
|
||||
@include dark {
|
||||
color: var(--mantine-color-gray-1);
|
||||
}
|
||||
}
|
|
@ -1,16 +1,10 @@
|
|||
import { FunctionComponent, useMemo, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import {
|
||||
Anchor,
|
||||
Autocomplete,
|
||||
ComboboxItem,
|
||||
OptionsFilter,
|
||||
} from "@mantine/core";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Autocomplete, ComboboxItem, OptionsFilter, Text } from "@mantine/core";
|
||||
import { faSearch } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useServerSearch } from "@/apis/hooks";
|
||||
import { useDebouncedValue } from "@/utilities";
|
||||
import styles from "./Search.module.scss";
|
||||
|
||||
type SearchResultItem = {
|
||||
value: string;
|
||||
|
@ -57,21 +51,8 @@ const optionsFilter: OptionsFilter = ({ options, search }) => {
|
|||
});
|
||||
};
|
||||
|
||||
const ResultComponent = ({ name, link }: { name: string; link: string }) => {
|
||||
return (
|
||||
<Anchor
|
||||
component={Link}
|
||||
to={link}
|
||||
underline="never"
|
||||
className={styles.result}
|
||||
p="sm"
|
||||
>
|
||||
{name}
|
||||
</Anchor>
|
||||
);
|
||||
};
|
||||
|
||||
const Search: FunctionComponent = () => {
|
||||
const navigate = useNavigate();
|
||||
const [query, setQuery] = useState("");
|
||||
|
||||
const results = useSearch(query);
|
||||
|
@ -79,14 +60,7 @@ const Search: FunctionComponent = () => {
|
|||
return (
|
||||
<Autocomplete
|
||||
leftSection={<FontAwesomeIcon icon={faSearch} />}
|
||||
renderOption={(input) => (
|
||||
<ResultComponent
|
||||
name={input.option.value}
|
||||
link={
|
||||
results.find((a) => a.value === input.option.value)?.link || "/"
|
||||
}
|
||||
/>
|
||||
)}
|
||||
renderOption={(input) => <Text p="xs">{input.option.value}</Text>}
|
||||
placeholder="Search"
|
||||
size="sm"
|
||||
data={results}
|
||||
|
@ -96,6 +70,9 @@ const Search: FunctionComponent = () => {
|
|||
onChange={setQuery}
|
||||
onBlur={() => setQuery("")}
|
||||
filter={optionsFilter}
|
||||
onOptionSubmit={(option) =>
|
||||
navigate(results.find((a) => a.value === option)?.link || "/")
|
||||
}
|
||||
></Autocomplete>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useCallback, useState } from "react";
|
||||
import { UseMutationResult } from "react-query";
|
||||
import { UseMutationResult } from "@tanstack/react-query";
|
||||
import { Action } from "@/components/inputs";
|
||||
import { ActionProps } from "@/components/inputs/Action";
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useCallback, useState } from "react";
|
||||
import { UseMutationResult } from "react-query";
|
||||
import { Button, ButtonProps } from "@mantine/core";
|
||||
import { UseMutationResult } from "@tanstack/react-query";
|
||||
|
||||
type MutateButtonProps<DATA, VAR> = Omit<
|
||||
ButtonProps,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { FunctionComponent, ReactNode } from "react";
|
||||
import { UseQueryResult } from "react-query";
|
||||
import { LoadingOverlay } from "@mantine/core";
|
||||
import { UseQueryResult } from "@tanstack/react-query";
|
||||
import { LoadingProvider } from "@/contexts";
|
||||
|
||||
interface QueryOverlayProps {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { FunctionComponent, useMemo } from "react";
|
||||
import { UseMutationResult } from "react-query";
|
||||
import { Button, Divider, Group, LoadingOverlay, Stack } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { UseMutationResult } from "@tanstack/react-query";
|
||||
import { useLanguageProfiles } from "@/apis/hooks";
|
||||
import { MultiSelector, Selector } from "@/components/inputs";
|
||||
import { useModals, withModal } from "@/modules/modals";
|
||||
|
@ -21,7 +21,7 @@ const ItemEditForm: FunctionComponent<Props> = ({
|
|||
onCancel,
|
||||
}) => {
|
||||
const { data, isFetching } = useLanguageProfiles();
|
||||
const { isLoading, mutate } = mutation;
|
||||
const { isPending, mutate } = mutation;
|
||||
const modals = useModals();
|
||||
|
||||
const profileOptions = useSelectorOptions(
|
||||
|
@ -47,7 +47,7 @@ const ItemEditForm: FunctionComponent<Props> = ({
|
|||
(v) => v.code2,
|
||||
);
|
||||
|
||||
const isOverlayVisible = isLoading || isFetching || item === null;
|
||||
const isOverlayVisible = isPending || isFetching || item === null;
|
||||
|
||||
return (
|
||||
<form
|
||||
|
|
|
@ -243,7 +243,7 @@ const MovieUploadForm: FunctionComponent<Props> = ({
|
|||
<Action
|
||||
label="Remove"
|
||||
icon={faTrash}
|
||||
color="red"
|
||||
c="red"
|
||||
onClick={() => action.remove(index)}
|
||||
></Action>
|
||||
);
|
||||
|
|
|
@ -241,7 +241,7 @@ const ProfileEditForm: FunctionComponent<Props> = ({
|
|||
<Action
|
||||
label="Remove"
|
||||
icon={faTrash}
|
||||
color="red"
|
||||
c="red"
|
||||
onClick={() => action.remove(row.index)}
|
||||
></Action>
|
||||
);
|
||||
|
|
|
@ -309,7 +309,7 @@ const SeriesUploadForm: FunctionComponent<Props> = ({
|
|||
<Action
|
||||
label="Remove"
|
||||
icon={faTrash}
|
||||
color="red"
|
||||
c="red"
|
||||
onClick={() => action.remove(index)}
|
||||
></Action>
|
||||
);
|
||||
|
|
|
@ -9,7 +9,11 @@ import {
|
|||
useRefTracksByMovieId,
|
||||
useSubtitleAction,
|
||||
} from "@/apis/hooks";
|
||||
import { GroupedSelector, Selector } from "@/components/inputs";
|
||||
import {
|
||||
GroupedSelector,
|
||||
GroupedSelectorOptions,
|
||||
Selector,
|
||||
} from "@/components/inputs";
|
||||
import { useModals, withModal } from "@/modules/modals";
|
||||
import { task } from "@/modules/task";
|
||||
import { syncMaxOffsetSecondsOptions } from "@/pages/Settings/Subtitles/options";
|
||||
|
@ -17,11 +21,6 @@ import { toPython } from "@/utilities";
|
|||
|
||||
const TaskName = "Syncing Subtitle";
|
||||
|
||||
interface SelectOptions {
|
||||
group: string;
|
||||
items: { value: string; label: string }[];
|
||||
}
|
||||
|
||||
function useReferencedSubtitles(
|
||||
mediaType: "episode" | "movie",
|
||||
mediaId: number,
|
||||
|
@ -41,13 +40,13 @@ function useReferencedSubtitles(
|
|||
|
||||
const mediaData = mediaType === "episode" ? episodeData : movieData;
|
||||
|
||||
const subtitles: SelectOptions[] = [];
|
||||
const subtitles: GroupedSelectorOptions<string>[] = [];
|
||||
|
||||
if (!mediaData.data) {
|
||||
return [];
|
||||
} else {
|
||||
if (mediaData.data.audio_tracks.length > 0) {
|
||||
const embeddedAudioGroup: SelectOptions = {
|
||||
const embeddedAudioGroup: GroupedSelectorOptions<string> = {
|
||||
group: "Embedded audio tracks",
|
||||
items: [],
|
||||
};
|
||||
|
@ -63,7 +62,7 @@ function useReferencedSubtitles(
|
|||
}
|
||||
|
||||
if (mediaData.data.embedded_subtitles_tracks.length > 0) {
|
||||
const embeddedSubtitlesTrackGroup: SelectOptions = {
|
||||
const embeddedSubtitlesTrackGroup: GroupedSelectorOptions<string> = {
|
||||
group: "Embedded subtitles tracks",
|
||||
items: [],
|
||||
};
|
||||
|
@ -79,7 +78,7 @@ function useReferencedSubtitles(
|
|||
}
|
||||
|
||||
if (mediaData.data.external_subtitles_tracks.length > 0) {
|
||||
const externalSubtitlesFilesGroup: SelectOptions = {
|
||||
const externalSubtitlesFilesGroup: GroupedSelectorOptions<string> = {
|
||||
group: "External Subtitles files",
|
||||
items: [],
|
||||
};
|
||||
|
@ -127,11 +126,7 @@ const SyncSubtitleForm: FunctionComponent<Props> = ({
|
|||
const mediaId = selections[0].id;
|
||||
const subtitlesPath = selections[0].path;
|
||||
|
||||
const subtitles: SelectOptions[] = useReferencedSubtitles(
|
||||
mediaType,
|
||||
mediaId,
|
||||
subtitlesPath,
|
||||
);
|
||||
const subtitles = useReferencedSubtitles(mediaType, mediaId, subtitlesPath);
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
initialValues: {
|
||||
|
|
|
@ -74,6 +74,7 @@ const TimeOffsetForm: FunctionComponent<Props> = ({ selections, onSubmit }) => {
|
|||
<Button
|
||||
color="gray"
|
||||
variant="filled"
|
||||
style={{ overflow: "visible" }}
|
||||
onClick={() =>
|
||||
form.setValues((f) => ({ ...f, positive: !f.positive }))
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { useCallback, useMemo, useRef } from "react";
|
||||
import {
|
||||
ComboboxItem,
|
||||
ComboboxParsedItemGroup,
|
||||
ComboboxItemGroup,
|
||||
MultiSelect,
|
||||
MultiSelectProps,
|
||||
Select,
|
||||
SelectProps,
|
||||
} from "@mantine/core";
|
||||
import { isNull, isUndefined } from "lodash";
|
||||
import { isNull, isUndefined, noop } from "lodash";
|
||||
import { LOG } from "@/utilities/console";
|
||||
|
||||
export type SelectorOption<T> = Override<
|
||||
|
@ -35,9 +35,14 @@ function DefaultKeyBuilder<T>(value: T) {
|
|||
}
|
||||
}
|
||||
|
||||
export interface GroupedSelectorOptions<T> {
|
||||
group: string;
|
||||
items: SelectorOption<T>[];
|
||||
}
|
||||
|
||||
export type GroupedSelectorProps<T> = Override<
|
||||
{
|
||||
options: ComboboxParsedItemGroup[];
|
||||
options: ComboboxItemGroup[];
|
||||
getkey?: (value: T) => string;
|
||||
},
|
||||
Omit<SelectProps, "data">
|
||||
|
@ -47,6 +52,7 @@ export function GroupedSelector<T>({
|
|||
value,
|
||||
options,
|
||||
getkey = DefaultKeyBuilder,
|
||||
onOptionSubmit = noop,
|
||||
...select
|
||||
}: GroupedSelectorProps<T>) {
|
||||
return (
|
||||
|
|
|
@ -269,6 +269,7 @@ const EpisodeHistoryView: FunctionComponent<EpisodeHistoryViewProps> = ({
|
|||
return (
|
||||
<QueryOverlay result={history}>
|
||||
<PageTable
|
||||
autoScroll={false}
|
||||
tableStyles={{ emptyText: "No history found", placeholder: 5 }}
|
||||
columns={columns}
|
||||
data={data ?? []}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { useCallback, useMemo, useState } from "react";
|
||||
import { UseQueryResult } from "react-query";
|
||||
import { Column } from "react-table";
|
||||
import {
|
||||
Alert,
|
||||
|
@ -18,6 +17,7 @@ import {
|
|||
faInfoCircle,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { UseQueryResult } from "@tanstack/react-query";
|
||||
import { isString } from "lodash";
|
||||
import { Action, PageTable } from "@/components";
|
||||
import Language from "@/components/bazarr/Language";
|
||||
|
@ -162,8 +162,7 @@ function ManualSearchView<T extends SupportType>(props: Props<T>) {
|
|||
<Action
|
||||
label="Download"
|
||||
icon={faDownload}
|
||||
color="brand"
|
||||
variant="light"
|
||||
c="brand"
|
||||
disabled={item === null}
|
||||
onClick={() => {
|
||||
if (!item) return;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { onlineManager } from "react-query";
|
||||
import { onlineManager } from "@tanstack/react-query";
|
||||
import { debounce, forIn, remove, uniq } from "lodash";
|
||||
import { io, Socket } from "socket.io-client";
|
||||
import { Environment, isDevEnv, isTestEnv } from "@/utilities";
|
||||
|
|
|
@ -40,13 +40,13 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
|||
update: (ids) => {
|
||||
LOG("info", "Invalidating series", ids);
|
||||
ids.forEach((id) => {
|
||||
queryClient.invalidateQueries([QueryKeys.Series, id]);
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.Series, id] });
|
||||
});
|
||||
},
|
||||
delete: (ids) => {
|
||||
LOG("info", "Invalidating series", ids);
|
||||
ids.forEach((id) => {
|
||||
queryClient.invalidateQueries([QueryKeys.Series, id]);
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.Series, id] });
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -55,13 +55,13 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
|||
update: (ids) => {
|
||||
LOG("info", "Invalidating movies", ids);
|
||||
ids.forEach((id) => {
|
||||
queryClient.invalidateQueries([QueryKeys.Movies, id]);
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.Movies, id] });
|
||||
});
|
||||
},
|
||||
delete: (ids) => {
|
||||
LOG("info", "Invalidating movies", ids);
|
||||
ids.forEach((id) => {
|
||||
queryClient.invalidateQueries([QueryKeys.Movies, id]);
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.Movies, id] });
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -78,10 +78,9 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
|||
id,
|
||||
]);
|
||||
if (episode !== undefined) {
|
||||
queryClient.invalidateQueries([
|
||||
QueryKeys.Series,
|
||||
episode.sonarrSeriesId,
|
||||
]);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series, episode.sonarrSeriesId],
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -93,10 +92,9 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
|||
id,
|
||||
]);
|
||||
if (episode !== undefined) {
|
||||
queryClient.invalidateQueries([
|
||||
QueryKeys.Series,
|
||||
episode.sonarrSeriesId,
|
||||
]);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series, episode.sonarrSeriesId],
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -105,83 +103,106 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
|||
key: "episode-wanted",
|
||||
update: (ids) => {
|
||||
// Find a better way to update wanted
|
||||
queryClient.invalidateQueries([QueryKeys.Episodes, QueryKeys.Wanted]);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Episodes, QueryKeys.Wanted],
|
||||
});
|
||||
},
|
||||
delete: () => {
|
||||
queryClient.invalidateQueries([QueryKeys.Episodes, QueryKeys.Wanted]);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Episodes, QueryKeys.Wanted],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "movie-wanted",
|
||||
update: (ids) => {
|
||||
// Find a better way to update wanted
|
||||
queryClient.invalidateQueries([QueryKeys.Movies, QueryKeys.Wanted]);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, QueryKeys.Wanted],
|
||||
});
|
||||
},
|
||||
delete: () => {
|
||||
queryClient.invalidateQueries([QueryKeys.Movies, QueryKeys.Wanted]);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, QueryKeys.Wanted],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "settings",
|
||||
any: () => {
|
||||
queryClient.invalidateQueries([QueryKeys.System]);
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.System] });
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "languages",
|
||||
any: () => {
|
||||
queryClient.invalidateQueries([QueryKeys.System, QueryKeys.Languages]);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Languages],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "badges",
|
||||
any: () => {
|
||||
queryClient.invalidateQueries([QueryKeys.System, QueryKeys.Badges]);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Badges],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "movie-history",
|
||||
any: () => {
|
||||
queryClient.invalidateQueries([QueryKeys.Movies, QueryKeys.History]);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, QueryKeys.History],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "movie-blacklist",
|
||||
any: () => {
|
||||
queryClient.invalidateQueries([QueryKeys.Movies, QueryKeys.Blacklist]);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, QueryKeys.Blacklist],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "episode-history",
|
||||
any: () => {
|
||||
queryClient.invalidateQueries([QueryKeys.Episodes, QueryKeys.History]);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Episodes, QueryKeys.History],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "episode-blacklist",
|
||||
any: () => {
|
||||
queryClient.invalidateQueries([
|
||||
QueryKeys.Episodes,
|
||||
QueryKeys.Blacklist,
|
||||
]);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Episodes, QueryKeys.Blacklist],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "reset-episode-wanted",
|
||||
any: () => {
|
||||
queryClient.invalidateQueries([QueryKeys.Episodes, QueryKeys.Wanted]);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Episodes, QueryKeys.Wanted],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "reset-movie-wanted",
|
||||
any: () => {
|
||||
queryClient.invalidateQueries([QueryKeys.Movies, QueryKeys.Wanted]);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, QueryKeys.Wanted],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "task",
|
||||
any: () => {
|
||||
queryClient.invalidateQueries([QueryKeys.System, QueryKeys.Tasks]);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Tasks],
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -169,7 +169,6 @@ const Table: FunctionComponent<Props> = ({
|
|||
<Action
|
||||
label="Manual Search"
|
||||
disabled={disabled}
|
||||
variant="dark"
|
||||
onClick={() => {
|
||||
modals.openContextModal(EpisodeSearchModal, {
|
||||
item: row.original,
|
||||
|
@ -182,7 +181,6 @@ const Table: FunctionComponent<Props> = ({
|
|||
<Action
|
||||
label="History"
|
||||
disabled={disabled}
|
||||
variant="dark"
|
||||
onClick={() => {
|
||||
modals.openContextModal(
|
||||
EpisodeHistoryModal,
|
||||
|
|
|
@ -161,7 +161,6 @@ const Table: FunctionComponent<Props> = ({ movie, profile, disabled }) => {
|
|||
<Action
|
||||
label="Subtitle Actions"
|
||||
disabled={isSubtitleTrack(path)}
|
||||
variant="dark"
|
||||
icon={faEllipsis}
|
||||
></Action>
|
||||
</SubtitleToolsMenu>
|
||||
|
|
|
@ -87,7 +87,6 @@ const MovieView: FunctionComponent = () => {
|
|||
<Action
|
||||
label="Edit Movie"
|
||||
tooltip={{ position: "left" }}
|
||||
variant="light"
|
||||
onClick={() =>
|
||||
modals.openContextModal(
|
||||
ItemEditModal,
|
||||
|
|
|
@ -87,7 +87,6 @@ const SeriesView: FunctionComponent = () => {
|
|||
<Action
|
||||
label="Edit Series"
|
||||
tooltip={{ position: "left" }}
|
||||
variant="light"
|
||||
onClick={() =>
|
||||
modals.openContextModal(
|
||||
ItemEditModal,
|
||||
|
|
|
@ -93,9 +93,8 @@ const SettingsGeneralView: FunctionComponent = () => {
|
|||
window.isSecureContext && (
|
||||
<Action
|
||||
label="Copy API Key"
|
||||
variant="light"
|
||||
settingKey={settingApiKey}
|
||||
color={copied ? "green" : undefined}
|
||||
c={copied ? "green" : undefined}
|
||||
icon={copied ? faCheck : faClipboard}
|
||||
onClick={(update, value) => {
|
||||
if (value) {
|
||||
|
@ -108,9 +107,8 @@ const SettingsGeneralView: FunctionComponent = () => {
|
|||
}
|
||||
<Action
|
||||
label="Regenerate"
|
||||
variant="light"
|
||||
settingKey={settingApiKey}
|
||||
color="red"
|
||||
c="red"
|
||||
icon={faSync}
|
||||
onClick={(update) => {
|
||||
update(generateApiKey());
|
||||
|
|
|
@ -342,7 +342,7 @@ const EqualsTable: FunctionComponent<EqualsTableProps> = () => {
|
|||
<Action
|
||||
label="Remove"
|
||||
icon={faTrash}
|
||||
color="red"
|
||||
c="red"
|
||||
onClick={() => remove(row.index)}
|
||||
></Action>
|
||||
);
|
||||
|
|
|
@ -132,6 +132,7 @@ const Table: FunctionComponent = () => {
|
|||
<Action
|
||||
label="Edit Profile"
|
||||
icon={faWrench}
|
||||
c="gray"
|
||||
onClick={() => {
|
||||
modals.openContextModal(ProfileEditModal, {
|
||||
languages,
|
||||
|
@ -143,7 +144,7 @@ const Table: FunctionComponent = () => {
|
|||
<Action
|
||||
label="Remove"
|
||||
icon={faTrash}
|
||||
color="red"
|
||||
c="red"
|
||||
onClick={() => action.remove(row.index)}
|
||||
></Action>
|
||||
</Group>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { FunctionComponent, useCallback, useMemo } from "react";
|
||||
import { useMutation } from "react-query";
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
|
@ -9,6 +8,7 @@ import {
|
|||
Textarea,
|
||||
} from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { isObject } from "lodash";
|
||||
import api from "@/apis/raw";
|
||||
import { Selector } from "@/components";
|
||||
|
@ -63,7 +63,9 @@ const NotificationForm: FunctionComponent<Props> = ({
|
|||
},
|
||||
});
|
||||
|
||||
const test = useMutation((url: string) => api.system.testNotification(url));
|
||||
const test = useMutation({
|
||||
mutationFn: (url: string) => api.system.testNotification(url),
|
||||
});
|
||||
|
||||
return (
|
||||
<form
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
// eslint-disable-next-line simple-import-sort/imports
|
||||
import { FunctionComponent } from "react";
|
||||
import { Anchor, Blockquote, Text } from "@mantine/core";
|
||||
import { Check, Layout, Message, Section } from "@/pages/Settings/components";
|
||||
import { NotificationView } from "./components";
|
||||
import { faQuoteLeftAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
const SettingsNotificationsView: FunctionComponent = () => {
|
||||
return (
|
||||
<Layout name="Notifications">
|
||||
<Blockquote>
|
||||
<Blockquote
|
||||
bg="transparent"
|
||||
mt="xl"
|
||||
icon={<FontAwesomeIcon icon={faQuoteLeftAlt}></FontAwesomeIcon>}
|
||||
>
|
||||
<Text>
|
||||
Thanks to caronc for his work on{" "}
|
||||
<Anchor
|
||||
|
|
|
@ -25,7 +25,7 @@ const Layout: FunctionComponent<Props> = (props) => {
|
|||
const { children, name } = props;
|
||||
|
||||
const { data: settings, isLoading, isRefetching } = useSystemSettings();
|
||||
const { mutate, isLoading: isMutating } = useSettingsMutation();
|
||||
const { mutate, isPending: isMutating } = useSettingsMutation();
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
initialValues: {
|
||||
|
|
|
@ -28,7 +28,7 @@ const LayoutModal: FunctionComponent<Props> = (props) => {
|
|||
const { children, callbackModal } = props;
|
||||
|
||||
const { data: settings, isLoading, isRefetching } = useSystemSettings();
|
||||
const { mutate, isLoading: isMutating } = useSettingsMutation();
|
||||
const { mutate, isPending: isMutating } = useSettingsMutation();
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
initialValues: {
|
||||
|
|
|
@ -10,7 +10,7 @@ import Table from "./table";
|
|||
const SystemBackupsView: FunctionComponent = () => {
|
||||
const backups = useSystemBackups();
|
||||
|
||||
const { mutate: backup, isLoading: isResetting } = useCreateBackups();
|
||||
const { mutate: backup, isPending: isResetting } = useCreateBackups();
|
||||
|
||||
useDocumentTitle("Backups - Bazarr (System)");
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@ const Table: FunctionComponent<Props> = ({ backups }) => {
|
|||
return (
|
||||
<Action
|
||||
label="Delete"
|
||||
color="red"
|
||||
c="red"
|
||||
onClick={() =>
|
||||
modals.openConfirmModal({
|
||||
title: "Delete Backup",
|
||||
|
|
|
@ -19,7 +19,7 @@ const SystemLogsView: FunctionComponent = () => {
|
|||
const logs = useSystemLogs();
|
||||
const { isFetching, data, refetch } = logs;
|
||||
|
||||
const { mutate, isLoading } = useDeleteLogs();
|
||||
const { mutate, isPending } = useDeleteLogs();
|
||||
|
||||
const download = useCallback(() => {
|
||||
window.open(`${Environment.baseUrl}/bazarr.log`);
|
||||
|
@ -98,14 +98,14 @@ const SystemLogsView: FunctionComponent = () => {
|
|||
Download
|
||||
</Toolbox.Button>
|
||||
<Toolbox.Button
|
||||
loading={isLoading}
|
||||
loading={isPending}
|
||||
icon={faTrash}
|
||||
onClick={() => mutate()}
|
||||
>
|
||||
Empty
|
||||
</Toolbox.Button>
|
||||
<Toolbox.Button
|
||||
loading={isLoading}
|
||||
loading={isPending}
|
||||
icon={faFilter}
|
||||
onClick={openFilterModal}
|
||||
rightSection={
|
||||
|
|
|
@ -12,7 +12,7 @@ const SystemProvidersView: FunctionComponent = () => {
|
|||
|
||||
const { isFetching, data, refetch } = providers;
|
||||
|
||||
const { mutate: reset, isLoading: isResetting } = useResetProvider();
|
||||
const { mutate: reset, isPending: isResetting } = useResetProvider();
|
||||
|
||||
useDocumentTitle("Providers - Bazarr (System)");
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ const SystemReleasesView: FunctionComponent = () => {
|
|||
useDocumentTitle("Releases - Bazarr (System)");
|
||||
|
||||
return (
|
||||
<Container size={600} py={12}>
|
||||
<Container size="md" py={12}>
|
||||
<QueryOverlay result={releases}>
|
||||
<Stack gap="lg">
|
||||
{data?.map((v, idx) => (
|
||||
|
|
|
@ -42,7 +42,7 @@ const WantedMoviesView: FunctionComponent = () => {
|
|||
<Group gap="sm">
|
||||
{value.map((item, idx) => (
|
||||
<Badge
|
||||
color={download.isLoading ? "gray" : undefined}
|
||||
color={download.isPending ? "gray" : undefined}
|
||||
leftSection={<FontAwesomeIcon icon={faSearch} />}
|
||||
key={BuildKey(idx, item.code2)}
|
||||
style={{ cursor: "pointer" }}
|
||||
|
|
|
@ -50,7 +50,7 @@ const WantedSeriesView: FunctionComponent = () => {
|
|||
<Group gap="sm">
|
||||
{value.map((item, idx) => (
|
||||
<Badge
|
||||
color={download.isLoading ? "gray" : undefined}
|
||||
color={download.isPending ? "gray" : undefined}
|
||||
leftSection={<FontAwesomeIcon icon={faSearch} />}
|
||||
key={BuildKey(idx, item.code2)}
|
||||
style={{ cursor: "pointer" }}
|
||||
|
|
|
@ -200,8 +200,8 @@ const ItemBadge: FunctionComponent<ItemBadgeProps> = ({
|
|||
}) => (
|
||||
<Badge
|
||||
leftSection={<FontAwesomeIcon icon={icon}></FontAwesomeIcon>}
|
||||
variant="light"
|
||||
radius="sm"
|
||||
color="dark"
|
||||
size="sm"
|
||||
style={{ textTransform: "none" }}
|
||||
aria-label={title}
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
import { useCallback, useMemo, useState } from "react";
|
||||
import { UseMutationResult } from "react-query";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Column, useRowSelect } from "react-table";
|
||||
import { Box, Container } from "@mantine/core";
|
||||
import { Box, Container, useCombobox } from "@mantine/core";
|
||||
import { faCheck, faUndo } from "@fortawesome/free-solid-svg-icons";
|
||||
import { UseMutationResult } from "@tanstack/react-query";
|
||||
import { uniqBy } from "lodash";
|
||||
import { useIsAnyMutationRunning, useLanguageProfiles } from "@/apis/hooks";
|
||||
import { SimpleTable, Toolbox } from "@/components";
|
||||
import { Selector, SelectorOption } from "@/components/inputs";
|
||||
import {
|
||||
GroupedSelector,
|
||||
GroupedSelectorOptions,
|
||||
SimpleTable,
|
||||
Toolbox,
|
||||
} from "@/components";
|
||||
import { useCustomSelection } from "@/components/tables/plugins";
|
||||
import { GetItemId, useSelectorOptions } from "@/utilities";
|
||||
|
||||
|
@ -36,10 +40,26 @@ function MassEditor<T extends Item.Base>(props: MassEditorProps<T>) {
|
|||
|
||||
const profileOptions = useSelectorOptions(profiles ?? [], (v) => v.name);
|
||||
|
||||
const profileOptionsWithAction = useMemo<SelectorOption<Language.Profile>[]>(
|
||||
() => [...profileOptions.options],
|
||||
[profileOptions.options],
|
||||
);
|
||||
const profileOptionsWithAction = useMemo<
|
||||
GroupedSelectorOptions<string>[]
|
||||
>(() => {
|
||||
return [
|
||||
{
|
||||
group: "Actions",
|
||||
items: [{ label: "Clear", value: "", profileId: null }],
|
||||
},
|
||||
{
|
||||
group: "Profiles",
|
||||
items: profileOptions.options.map((a) => {
|
||||
return {
|
||||
value: a.value.profileId.toString(),
|
||||
label: a.label,
|
||||
profileId: a.value.profileId,
|
||||
};
|
||||
}),
|
||||
},
|
||||
];
|
||||
}, [profileOptions.options]);
|
||||
|
||||
const getKey = useCallback((value: Language.Profile | null) => {
|
||||
if (value) {
|
||||
|
@ -51,11 +71,20 @@ function MassEditor<T extends Item.Base>(props: MassEditorProps<T>) {
|
|||
|
||||
const { mutateAsync } = mutation;
|
||||
|
||||
/**
|
||||
* Submit the form that contains the series id and the respective profile id set in chunks to prevent payloads too
|
||||
* large when we have a high amount of series or movies being applied the profile. The chunks are executed in order
|
||||
* since there are no much benefit on executing in parallel, also parallelism could result in high load on the server
|
||||
* side if not throttled properly.
|
||||
*/
|
||||
const save = useCallback(() => {
|
||||
const chunkSize = 1000;
|
||||
|
||||
const form: FormType.ModifyItem = {
|
||||
id: [],
|
||||
profileid: [],
|
||||
};
|
||||
|
||||
dirties.forEach((v) => {
|
||||
const id = GetItemId(v);
|
||||
if (id) {
|
||||
|
@ -63,12 +92,29 @@ function MassEditor<T extends Item.Base>(props: MassEditorProps<T>) {
|
|||
form.profileid.push(v.profileId);
|
||||
}
|
||||
});
|
||||
return mutateAsync(form);
|
||||
|
||||
const mutateInChunks = async (
|
||||
ids: number[],
|
||||
profileIds: (number | null)[],
|
||||
) => {
|
||||
if (ids.length === 0) return;
|
||||
|
||||
const chunkIds = ids.slice(0, chunkSize);
|
||||
const chunkProfileIds = profileIds.slice(0, chunkSize);
|
||||
|
||||
await mutateAsync({
|
||||
id: chunkIds,
|
||||
profileid: chunkProfileIds,
|
||||
});
|
||||
|
||||
await mutateInChunks(ids.slice(chunkSize), profileIds.slice(chunkSize));
|
||||
};
|
||||
|
||||
return mutateInChunks(form.id, form.profileid);
|
||||
}, [dirties, mutateAsync]);
|
||||
|
||||
const setProfiles = useCallback(
|
||||
(profile: Language.Profile | null) => {
|
||||
const id = profile?.profileId ?? null;
|
||||
(id: number | null) => {
|
||||
const newItems = selections.map((v) => ({ ...v, profileId: id }));
|
||||
|
||||
setDirties((dirty) => {
|
||||
|
@ -78,18 +124,29 @@ function MassEditor<T extends Item.Base>(props: MassEditorProps<T>) {
|
|||
[selections],
|
||||
);
|
||||
|
||||
const combobox = useCombobox();
|
||||
|
||||
return (
|
||||
<Container fluid px={0}>
|
||||
<Toolbox>
|
||||
<Box>
|
||||
<Selector
|
||||
allowDeselect
|
||||
<GroupedSelector
|
||||
onClick={() => combobox.openDropdown()}
|
||||
onDropdownClose={() => {
|
||||
combobox.resetSelectedOption();
|
||||
}}
|
||||
placeholder="Change Profile"
|
||||
withCheckIcon={false}
|
||||
options={profileOptionsWithAction}
|
||||
getkey={getKey}
|
||||
disabled={selections.length === 0}
|
||||
onChange={setProfiles}
|
||||
></Selector>
|
||||
comboboxProps={{
|
||||
store: combobox,
|
||||
onOptionSubmit: (value) => {
|
||||
setProfiles(value ? +value : null);
|
||||
},
|
||||
}}
|
||||
></GroupedSelector>
|
||||
</Box>
|
||||
<Box>
|
||||
<Toolbox.Button icon={faUndo} onClick={onEnded}>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { FunctionComponent, PropsWithChildren } from "react";
|
||||
import { QueryClientProvider } from "react-query";
|
||||
import { ReactQueryDevtools } from "react-query/devtools";
|
||||
import { Notifications } from "@mantine/notifications";
|
||||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||
import queryClient from "@/apis/queries";
|
||||
import ThemeProvider from "@/App/ThemeProvider";
|
||||
import { ModalsProvider } from "@/modules/modals";
|
||||
|
|
Loading…
Add table
Reference in a new issue