perf: add translations cron (#1367)

This commit is contained in:
Daan Wijns 2023-11-29 08:25:43 +01:00 committed by GitHub
parent 8f8caad25e
commit 02d97f5283
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
89 changed files with 6858 additions and 1285 deletions

View file

@ -13,5 +13,5 @@ updates:
interval: 'weekly'
groups:
all:
patterns:
- "*"
patterns:
- '*'

32
.github/workflows/translations.yml vendored Normal file
View file

@ -0,0 +1,32 @@
name: Update Translations
on:
workflow_dispatch:
schedule:
# At 00:00 on Sunday.
- cron: '0 0 * * 0'
permissions:
contents: write
pull-requests: write
jobs:
update-translations:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Update locales
run: |
curl -H 'X-API-KEY: ${{ secrets.TOLGEE_TOKEN }}' 'https://app.tolgee.io/v2/projects/export' -o locales.zip
unzip -o -d ./src/locales locales.zip
- name: Create Pull Request
uses: peter-evans/create-pull-request@v5
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore: update translations'
branch: 'update-tolgee-translations'
title: 'chore: update translations'
delete-branch: true

View file

@ -2,118 +2,142 @@
## [2.2.0](https://github.com/WDaan/VueTorrent/compare/v2.1.1...v2.2.0) (2023-11-20)
### Features
* **AddTorrentDialog:** Rework dialog to add missing parameters ([#1323](https://github.com/WDaan/VueTorrent/issues/1323)) ([f69851c](https://github.com/WDaan/VueTorrent/commit/f69851cc39bd7ca47f426a5eb5d900c5c428b758))
* **ConnectionStatusDialog:** Add DHT node and active peer connections ([#1312](https://github.com/WDaan/VueTorrent/issues/1312)) ([6f01c00](https://github.com/WDaan/VueTorrent/commit/6f01c008e88ad3c4a45fab7f8624416975756033))
* **Content:** Add menu to set file priority ([#1333](https://github.com/WDaan/VueTorrent/issues/1333)) ([f83fe06](https://github.com/WDaan/VueTorrent/commit/f83fe0691a939a99ed4490d22d8aba9c1994d67d))
* **Overview:** Add keyboard shortcuts ([#1335](https://github.com/WDaan/VueTorrent/issues/1335)) ([15a20a3](https://github.com/WDaan/VueTorrent/commit/15a20a3a4e68d86ddf54c0ee13463ed2288ddfc9))
* **preferences:** Update settings page to include missing preferences ([#1296](https://github.com/WDaan/VueTorrent/issues/1296)) ([e034071](https://github.com/WDaan/VueTorrent/commit/e0340710cc7dce842d260edaf0848e241a36208b))
* **RSS:** Add conf to use ID instead of link ([#1334](https://github.com/WDaan/VueTorrent/issues/1334)) ([6af7537](https://github.com/WDaan/VueTorrent/commit/6af75375571fadd650d1342c132e34d3ec6c3f49))
- **AddTorrentDialog:** Rework dialog to add missing parameters ([#1323](https://github.com/WDaan/VueTorrent/issues/1323))
([f69851c](https://github.com/WDaan/VueTorrent/commit/f69851cc39bd7ca47f426a5eb5d900c5c428b758))
- **ConnectionStatusDialog:** Add DHT node and active peer connections ([#1312](https://github.com/WDaan/VueTorrent/issues/1312))
([6f01c00](https://github.com/WDaan/VueTorrent/commit/6f01c008e88ad3c4a45fab7f8624416975756033))
- **Content:** Add menu to set file priority ([#1333](https://github.com/WDaan/VueTorrent/issues/1333))
([f83fe06](https://github.com/WDaan/VueTorrent/commit/f83fe0691a939a99ed4490d22d8aba9c1994d67d))
- **Overview:** Add keyboard shortcuts ([#1335](https://github.com/WDaan/VueTorrent/issues/1335))
([15a20a3](https://github.com/WDaan/VueTorrent/commit/15a20a3a4e68d86ddf54c0ee13463ed2288ddfc9))
- **preferences:** Update settings page to include missing preferences ([#1296](https://github.com/WDaan/VueTorrent/issues/1296))
([e034071](https://github.com/WDaan/VueTorrent/commit/e0340710cc7dce842d260edaf0848e241a36208b))
- **RSS:** Add conf to use ID instead of link ([#1334](https://github.com/WDaan/VueTorrent/issues/1334))
([6af7537](https://github.com/WDaan/VueTorrent/commit/6af75375571fadd650d1342c132e34d3ec6c3f49))
### Bug Fixes
* **AddTorrentDialog:** Add autocomplete attribute ([#1331](https://github.com/WDaan/VueTorrent/issues/1331)) ([aaa4cc1](https://github.com/WDaan/VueTorrent/commit/aaa4cc16e9e7cac207a8286ce950e924c7718327))
* **AddTorrentDialog:** Prevent undefined values from being sent through API ([#1330](https://github.com/WDaan/VueTorrent/issues/1330)) ([6575ade](https://github.com/WDaan/VueTorrent/commit/6575ade19e0bb6443bc8e29187cf952ff4f67262))
* authentication race condition ([#1319](https://github.com/WDaan/VueTorrent/issues/1319)) ([153268f](https://github.com/WDaan/VueTorrent/commit/153268f30d3f2f8c618083caebd51894a42bc32a))
* **dashboard:** Fix span selection not using filters ([#1311](https://github.com/WDaan/VueTorrent/issues/1311)) ([06d1815](https://github.com/WDaan/VueTorrent/commit/06d1815f002b01843518a6bbf54b976c35be1054))
* reduce title size [#1288](https://github.com/WDaan/VueTorrent/issues/1288) ([aab5cc1](https://github.com/WDaan/VueTorrent/commit/aab5cc16edd7247196daf298a366e0d369cfb932))
* reduce title size [#1288](https://github.com/WDaan/VueTorrent/issues/1288) ([0c7701a](https://github.com/WDaan/VueTorrent/commit/0c7701a4d2cd46b7851760e46dc7cf85ef0b6f5a))
* **Settings/Speed:** Fix speed values conversion ([#1332](https://github.com/WDaan/VueTorrent/issues/1332)) ([02c0496](https://github.com/WDaan/VueTorrent/commit/02c049692bb6fc88f1ca76faa1b271118eb7eb4a))
* **settings:** Update disabled state in Bittorrent tab ([#1301](https://github.com/WDaan/VueTorrent/issues/1301)) ([f1cefe4](https://github.com/WDaan/VueTorrent/commit/f1cefe4e3ced36b81feab03e7e5fdf852b7b131e))
* **TRC:** Use all selected torrents for transfer limits ([#1329](https://github.com/WDaan/VueTorrent/issues/1329)) ([3614081](https://github.com/WDaan/VueTorrent/commit/3614081f198e1bedd35a2fbf54aa18a366594cf6))
- **AddTorrentDialog:** Add autocomplete attribute ([#1331](https://github.com/WDaan/VueTorrent/issues/1331))
([aaa4cc1](https://github.com/WDaan/VueTorrent/commit/aaa4cc16e9e7cac207a8286ce950e924c7718327))
- **AddTorrentDialog:** Prevent undefined values from being sent through API ([#1330](https://github.com/WDaan/VueTorrent/issues/1330))
([6575ade](https://github.com/WDaan/VueTorrent/commit/6575ade19e0bb6443bc8e29187cf952ff4f67262))
- authentication race condition ([#1319](https://github.com/WDaan/VueTorrent/issues/1319))
([153268f](https://github.com/WDaan/VueTorrent/commit/153268f30d3f2f8c618083caebd51894a42bc32a))
- **dashboard:** Fix span selection not using filters ([#1311](https://github.com/WDaan/VueTorrent/issues/1311))
([06d1815](https://github.com/WDaan/VueTorrent/commit/06d1815f002b01843518a6bbf54b976c35be1054))
- reduce title size [#1288](https://github.com/WDaan/VueTorrent/issues/1288) ([aab5cc1](https://github.com/WDaan/VueTorrent/commit/aab5cc16edd7247196daf298a366e0d369cfb932))
- reduce title size [#1288](https://github.com/WDaan/VueTorrent/issues/1288) ([0c7701a](https://github.com/WDaan/VueTorrent/commit/0c7701a4d2cd46b7851760e46dc7cf85ef0b6f5a))
- **Settings/Speed:** Fix speed values conversion ([#1332](https://github.com/WDaan/VueTorrent/issues/1332))
([02c0496](https://github.com/WDaan/VueTorrent/commit/02c049692bb6fc88f1ca76faa1b271118eb7eb4a))
- **settings:** Update disabled state in Bittorrent tab ([#1301](https://github.com/WDaan/VueTorrent/issues/1301))
([f1cefe4](https://github.com/WDaan/VueTorrent/commit/f1cefe4e3ced36b81feab03e7e5fdf852b7b131e))
- **TRC:** Use all selected torrents for transfer limits ([#1329](https://github.com/WDaan/VueTorrent/issues/1329))
([3614081](https://github.com/WDaan/VueTorrent/commit/3614081f198e1bedd35a2fbf54aa18a366594cf6))
### Improvements
* **stores:** Rework store structure to prevent circular imports ([#1325](https://github.com/WDaan/VueTorrent/issues/1325)) ([179af5a](https://github.com/WDaan/VueTorrent/commit/179af5a1d6886b9543b9170ae697f1011af501b9))
- **stores:** Rework store structure to prevent circular imports ([#1325](https://github.com/WDaan/VueTorrent/issues/1325))
([179af5a](https://github.com/WDaan/VueTorrent/commit/179af5a1d6886b9543b9170ae697f1011af501b9))
## [2.1.1](https://github.com/WDaan/VueTorrent/compare/v2.1.0...v2.1.1) (2023-11-10)
### Bug Fixes
* **Navbar:** Add missing bind on right drawer setting ([#1294](https://github.com/WDaan/VueTorrent/issues/1294)) ([672ab08](https://github.com/WDaan/VueTorrent/commit/672ab0863f3ee41a5fb2fd17bdba81306fb91be3))
* **RightClickMenu:** Fix target not being selected on long press ([#1295](https://github.com/WDaan/VueTorrent/issues/1295)) ([d80cc35](https://github.com/WDaan/VueTorrent/commit/d80cc35e94f3caa94520abf5ed83ba29b7d3bc2c))
* **settings:** Update drag handling to completed properties ([#1286](https://github.com/WDaan/VueTorrent/issues/1286)) ([dd53e6f](https://github.com/WDaan/VueTorrent/commit/dd53e6fc9d299b506b8c3f28c19ee131aa811430))
- **Navbar:** Add missing bind on right drawer setting ([#1294](https://github.com/WDaan/VueTorrent/issues/1294))
([672ab08](https://github.com/WDaan/VueTorrent/commit/672ab0863f3ee41a5fb2fd17bdba81306fb91be3))
- **RightClickMenu:** Fix target not being selected on long press ([#1295](https://github.com/WDaan/VueTorrent/issues/1295))
([d80cc35](https://github.com/WDaan/VueTorrent/commit/d80cc35e94f3caa94520abf5ed83ba29b7d3bc2c))
- **settings:** Update drag handling to completed properties ([#1286](https://github.com/WDaan/VueTorrent/issues/1286))
([dd53e6f](https://github.com/WDaan/VueTorrent/commit/dd53e6fc9d299b506b8c3f28c19ee131aa811430))
### Improvements
* **MagnetHandler:** Open add dialog on magnet link ([#1293](https://github.com/WDaan/VueTorrent/issues/1293)) ([0c546f1](https://github.com/WDaan/VueTorrent/commit/0c546f1b373b34162ea437c16a7a0574321a8a7f))
- **MagnetHandler:** Open add dialog on magnet link ([#1293](https://github.com/WDaan/VueTorrent/issues/1293))
([0c546f1](https://github.com/WDaan/VueTorrent/commit/0c546f1b373b34162ea437c16a7a0574321a8a7f))
## [2.1.0](https://github.com/WDaan/VueTorrent/compare/v2.0.1...v2.1.0) (2023-11-08)
### Features
* **filters:** Allow to disable filters temporarily ([6f9ee5d](https://github.com/WDaan/VueTorrent/commit/6f9ee5d0e36bd0a9e1707f50edc84d8979ae7a51))
* **localization:** Bringing back Russian locale ([#1281](https://github.com/WDaan/VueTorrent/issues/1281)) ([20dc684](https://github.com/WDaan/VueTorrent/commit/20dc6843aa587dd571362efa34cd6cc986af4591))
* **ShareLimit:** Add torrent share limit dialog ([ed0991e](https://github.com/WDaan/VueTorrent/commit/ed0991e0a5b3985dbea40d343dc6a3c8224e1f32))
* **title:** Ability to set custom browser tab title ([1f58005](https://github.com/WDaan/VueTorrent/commit/1f58005109ab5b8a1cf6d9575246e24a105cc3a8))
- **filters:** Allow to disable filters temporarily ([6f9ee5d](https://github.com/WDaan/VueTorrent/commit/6f9ee5d0e36bd0a9e1707f50edc84d8979ae7a51))
- **localization:** Bringing back Russian locale ([#1281](https://github.com/WDaan/VueTorrent/issues/1281))
([20dc684](https://github.com/WDaan/VueTorrent/commit/20dc6843aa587dd571362efa34cd6cc986af4591))
- **ShareLimit:** Add torrent share limit dialog ([ed0991e](https://github.com/WDaan/VueTorrent/commit/ed0991e0a5b3985dbea40d343dc6a3c8224e1f32))
- **title:** Ability to set custom browser tab title ([1f58005](https://github.com/WDaan/VueTorrent/commit/1f58005109ab5b8a1cf6d9575246e24a105cc3a8))
### Bug Fixes
* **TorrentCard:** Fix properties not being persisted ([#1252](https://github.com/WDaan/VueTorrent/issues/1252)) ([c61b957](https://github.com/WDaan/VueTorrent/commit/c61b957683c5ee6591b27de755fdad062e93e16f))
- **TorrentCard:** Fix properties not being persisted ([#1252](https://github.com/WDaan/VueTorrent/issues/1252))
([c61b957](https://github.com/WDaan/VueTorrent/commit/c61b957683c5ee6591b27de755fdad062e93e16f))
### Improvements
* **faker:** Improve torrent mocking ([#1187](https://github.com/WDaan/VueTorrent/issues/1187)) ([8b1f641](https://github.com/WDaan/VueTorrent/commit/8b1f641fca79ad580bcb6d94f41ad0f52c495e80))
* **localization:** Integrate Tolgee inside CI ([#1276](https://github.com/WDaan/VueTorrent/issues/1276)) ([86e12da](https://github.com/WDaan/VueTorrent/commit/86e12da8d0f3ef5e2f7b335277590f8769daf104))
* **TorrentCard:** Only process drag on handle ([#1253](https://github.com/WDaan/VueTorrent/issues/1253)) ([681e59f](https://github.com/WDaan/VueTorrent/commit/681e59f7c76eb5b984aa968782a81d08fd271cc3))
* **Torrent:** Wrap title ([55c2ef8](https://github.com/WDaan/VueTorrent/commit/55c2ef811543ebcc84124d3304689f2901064e77))
- **faker:** Improve torrent mocking ([#1187](https://github.com/WDaan/VueTorrent/issues/1187))
([8b1f641](https://github.com/WDaan/VueTorrent/commit/8b1f641fca79ad580bcb6d94f41ad0f52c495e80))
- **localization:** Integrate Tolgee inside CI ([#1276](https://github.com/WDaan/VueTorrent/issues/1276))
([86e12da](https://github.com/WDaan/VueTorrent/commit/86e12da8d0f3ef5e2f7b335277590f8769daf104))
- **TorrentCard:** Only process drag on handle ([#1253](https://github.com/WDaan/VueTorrent/issues/1253))
([681e59f](https://github.com/WDaan/VueTorrent/commit/681e59f7c76eb5b984aa968782a81d08fd271cc3))
- **Torrent:** Wrap title ([55c2ef8](https://github.com/WDaan/VueTorrent/commit/55c2ef811543ebcc84124d3304689f2901064e77))
## [2.0.1](https://github.com/WDaan/VueTorrent/compare/v2.0.0...v2.0.1) (2023-11-04)
### Bug Fixes
* **dashboard:** Take filters into account when selecting all torrents ([9f4aa4a](https://github.com/WDaan/VueTorrent/commit/9f4aa4a119046d72eb5c2ad0047205c32c426c7a))
* **settings:** Add DnD exception on settings page ([d91070b](https://github.com/WDaan/VueTorrent/commit/d91070b4a28508d1ea7c4ad7e6e595f358194657))
- **dashboard:** Take filters into account when selecting all torrents ([9f4aa4a](https://github.com/WDaan/VueTorrent/commit/9f4aa4a119046d72eb5c2ad0047205c32c426c7a))
- **settings:** Add DnD exception on settings page ([d91070b](https://github.com/WDaan/VueTorrent/commit/d91070b4a28508d1ea7c4ad7e6e595f358194657))
### Improvements
* **settings reset:** Clear sessionStorage in addition to localStorage ([4eaaa33](https://github.com/WDaan/VueTorrent/commit/4eaaa33711f00fcb32515f58fedd6450d83f7cea))
- **settings reset:** Clear sessionStorage in addition to localStorage ([4eaaa33](https://github.com/WDaan/VueTorrent/commit/4eaaa33711f00fcb32515f58fedd6450d83f7cea))
## [2.0.0](https://github.com/WDaan/VueTorrent/compare/v1.8.0...v2.0.0) (2023-11-03)
### ⚠ BREAKING CHANGES
* Vue3 Rewrite ([#757](https://github.com/WDaan/VueTorrent/issues/757))
- Vue3 Rewrite ([#757](https://github.com/WDaan/VueTorrent/issues/757))
### Features
* **dashboard:** Add DL / UL Speed average to DashboardItems ([#1203](https://github.com/WDaan/VueTorrent/issues/1203)) ([7af47f9](https://github.com/WDaan/VueTorrent/commit/7af47f9e3d375be39fe133f10c69eeee5fa3ff43))
* **filters:** Allow multiple selection ([#1202](https://github.com/WDaan/VueTorrent/issues/1202)) ([41318c3](https://github.com/WDaan/VueTorrent/commit/41318c3d85da44833b7ce1e2afba9a613b07c624))
* **transfer limit:** Add callbacks for download / upload torrent limit ([#1217](https://github.com/WDaan/VueTorrent/issues/1217)) ([1792df9](https://github.com/WDaan/VueTorrent/commit/1792df965ba78c83264b315b6f1ecbaca4b08e04))
* Vue3 Rewrite ([#757](https://github.com/WDaan/VueTorrent/issues/757)) ([575b071](https://github.com/WDaan/VueTorrent/commit/575b071f6db9edc01412fab45109d3f4d203552d))
- **dashboard:** Add DL / UL Speed average to DashboardItems ([#1203](https://github.com/WDaan/VueTorrent/issues/1203))
([7af47f9](https://github.com/WDaan/VueTorrent/commit/7af47f9e3d375be39fe133f10c69eeee5fa3ff43))
- **filters:** Allow multiple selection ([#1202](https://github.com/WDaan/VueTorrent/issues/1202))
([41318c3](https://github.com/WDaan/VueTorrent/commit/41318c3d85da44833b7ce1e2afba9a613b07c624))
- **transfer limit:** Add callbacks for download / upload torrent limit ([#1217](https://github.com/WDaan/VueTorrent/issues/1217))
([1792df9](https://github.com/WDaan/VueTorrent/commit/1792df965ba78c83264b315b6f1ecbaca4b08e04))
- Vue3 Rewrite ([#757](https://github.com/WDaan/VueTorrent/issues/757)) ([575b071](https://github.com/WDaan/VueTorrent/commit/575b071f6db9edc01412fab45109d3f4d203552d))
### Bug Fixes
* **AddTorrentDialog:** Dialog was never initialized with default values ([#1214](https://github.com/WDaan/VueTorrent/issues/1214)) ([44fd8d3](https://github.com/WDaan/VueTorrent/commit/44fd8d38619b7a468f8f0a7e04c71cf9cd94683b))
* **AddTorrentDialog:** Fix persistence not working ([#1213](https://github.com/WDaan/VueTorrent/issues/1213)) ([9ee6533](https://github.com/WDaan/VueTorrent/commit/9ee65335e129139a4cff4ba307d94b8075f66558))
* **AddTorrent:** Error while accessing preferences ([#1216](https://github.com/WDaan/VueTorrent/issues/1216)) ([c760072](https://github.com/WDaan/VueTorrent/commit/c76007241dbd604f6e31bc506b88a065dc57d920))
* Chinese translations([#1189](https://github.com/WDaan/VueTorrent/issues/1189)) ([3f34cfc](https://github.com/WDaan/VueTorrent/commit/3f34cfcfdb9b9ff0a0c1030e2cb82fd671c562a0))
* **dashboard:** Sort not working anymore ([#1211](https://github.com/WDaan/VueTorrent/issues/1211)) ([b2bbcb9](https://github.com/WDaan/VueTorrent/commit/b2bbcb987e7569bfb58bf01930f4d6cbcb9e95c3))
* **rss:** Update overflow rules to handle long name without spaces ([#1228](https://github.com/WDaan/VueTorrent/issues/1228)) ([e2ce2e8](https://github.com/WDaan/VueTorrent/commit/e2ce2e86f5ff59499834a91b8de6c3bc8aa1e156))
- **AddTorrentDialog:** Dialog was never initialized with default values ([#1214](https://github.com/WDaan/VueTorrent/issues/1214))
([44fd8d3](https://github.com/WDaan/VueTorrent/commit/44fd8d38619b7a468f8f0a7e04c71cf9cd94683b))
- **AddTorrentDialog:** Fix persistence not working ([#1213](https://github.com/WDaan/VueTorrent/issues/1213))
([9ee6533](https://github.com/WDaan/VueTorrent/commit/9ee65335e129139a4cff4ba307d94b8075f66558))
- **AddTorrent:** Error while accessing preferences ([#1216](https://github.com/WDaan/VueTorrent/issues/1216))
([c760072](https://github.com/WDaan/VueTorrent/commit/c76007241dbd604f6e31bc506b88a065dc57d920))
- Chinese translations([#1189](https://github.com/WDaan/VueTorrent/issues/1189)) ([3f34cfc](https://github.com/WDaan/VueTorrent/commit/3f34cfcfdb9b9ff0a0c1030e2cb82fd671c562a0))
- **dashboard:** Sort not working anymore ([#1211](https://github.com/WDaan/VueTorrent/issues/1211))
([b2bbcb9](https://github.com/WDaan/VueTorrent/commit/b2bbcb987e7569bfb58bf01930f4d6cbcb9e95c3))
- **rss:** Update overflow rules to handle long name without spaces ([#1228](https://github.com/WDaan/VueTorrent/issues/1228))
([e2ce2e8](https://github.com/WDaan/VueTorrent/commit/e2ce2e86f5ff59499834a91b8de6c3bc8aa1e156))
### Improvements
* add Chinese translations ([#1185](https://github.com/WDaan/VueTorrent/issues/1185)) ([c50f1df](https://github.com/WDaan/VueTorrent/commit/c50f1df4d6027588e6d8fc9465d0bfeafd934f83))
* **AddTorrentDialog:** Add loading prop to add button ([#1192](https://github.com/WDaan/VueTorrent/issues/1192)) ([ac3bb69](https://github.com/WDaan/VueTorrent/commit/ac3bb69a13e9b2bef3030f0b500ee37f1c79b0c5))
* **AddTorrentDialog:** persist form in sessionStorage ([#1193](https://github.com/WDaan/VueTorrent/issues/1193)) ([e59f6bf](https://github.com/WDaan/VueTorrent/commit/e59f6bf206e7ace0fbc5eead955ada5caa63bd4a))
* **filters:** Reorder states ([197b9d7](https://github.com/WDaan/VueTorrent/commit/197b9d7d029c6669cccfb4c4fc183d62fed45de6))
* **rss:** Improve duplicate RSS article handling ([#1191](https://github.com/WDaan/VueTorrent/issues/1191)) ([ee01382](https://github.com/WDaan/VueTorrent/commit/ee0138277177dbd9c5054ed81fb8ab11d5b919f0))
* **rss:** Right click mark article as read ([#1210](https://github.com/WDaan/VueTorrent/issues/1210)) ([ee03b5c](https://github.com/WDaan/VueTorrent/commit/ee03b5cb58125df8fb073e264cc61bfda85ccd3b))
* **TorrentCard:** Darken selected item background ([#1215](https://github.com/WDaan/VueTorrent/issues/1215)) ([8214a0e](https://github.com/WDaan/VueTorrent/commit/8214a0ebd43637bbe3eb7098f5b0ff5a5d0951b8))
- add Chinese translations ([#1185](https://github.com/WDaan/VueTorrent/issues/1185))
([c50f1df](https://github.com/WDaan/VueTorrent/commit/c50f1df4d6027588e6d8fc9465d0bfeafd934f83))
- **AddTorrentDialog:** Add loading prop to add button ([#1192](https://github.com/WDaan/VueTorrent/issues/1192))
([ac3bb69](https://github.com/WDaan/VueTorrent/commit/ac3bb69a13e9b2bef3030f0b500ee37f1c79b0c5))
- **AddTorrentDialog:** persist form in sessionStorage ([#1193](https://github.com/WDaan/VueTorrent/issues/1193))
([e59f6bf](https://github.com/WDaan/VueTorrent/commit/e59f6bf206e7ace0fbc5eead955ada5caa63bd4a))
- **filters:** Reorder states ([197b9d7](https://github.com/WDaan/VueTorrent/commit/197b9d7d029c6669cccfb4c4fc183d62fed45de6))
- **rss:** Improve duplicate RSS article handling ([#1191](https://github.com/WDaan/VueTorrent/issues/1191))
([ee01382](https://github.com/WDaan/VueTorrent/commit/ee0138277177dbd9c5054ed81fb8ab11d5b919f0))
- **rss:** Right click mark article as read ([#1210](https://github.com/WDaan/VueTorrent/issues/1210))
([ee03b5c](https://github.com/WDaan/VueTorrent/commit/ee03b5cb58125df8fb073e264cc61bfda85ccd3b))
- **TorrentCard:** Darken selected item background ([#1215](https://github.com/WDaan/VueTorrent/issues/1215))
([8214a0e](https://github.com/WDaan/VueTorrent/commit/8214a0ebd43637bbe3eb7098f5b0ff5a5d0951b8))
## [1.8.0](https://github.com/WDaan/VueTorrent/compare/v1.7.4...v1.8.0) (2023-10-04)

View file

@ -4,6 +4,7 @@
<p>The sleekest looking WebUI for qBittorrent made with Vue.js!</p>
Join us on [Discord](https://discord.gg/KDQP7fR467)
</div>
![Vue](https://img.shields.io/badge/Vue-%5E3.3.4-brightgreen) ![qBittorrent](https://img.shields.io/badge/qBittorrent-4.4%2B-brightgreen)
@ -117,5 +118,6 @@ but before you do that:
<a href="https://www.buymeacoffee.com/wdaan"><img src="https://img.buymeacoffee.com/button-api/?text=Buy me a coffee&emoji=&slug=wdaan&button_colour=FFDD00&font_colour=000000&font_family=Arial&outline_colour=000000&coffee_colour=ffffff"></a>
## Contributors
- [@m4ximuel](https://github.com/m4ximuel)
- [@Larsluph](https://github.com/Larsluph)

View file

@ -5,4 +5,4 @@
{
"name": "Second torrent"
}
]
]

BIN
locales.zip Normal file

Binary file not shown.

View file

@ -4,17 +4,7 @@ import DnDZone from '@/components/DnDZone.vue'
import Navbar from '@/components/Navbar/Navbar.vue'
import { TitleOptions } from '@/constants/vuetorrent'
import { formatPercent, formatSpeed } from '@/helpers'
import {
useAddTorrentStore,
useAppStore,
useAuthStore,
useDialogStore,
useLogStore,
useMaindataStore,
usePreferenceStore,
useTorrentStore,
useVueTorrentStore
} from '@/stores'
import { useAddTorrentStore, useAppStore, useAuthStore, useDialogStore, useLogStore, useMaindataStore, usePreferenceStore, useTorrentStore, useVueTorrentStore } from '@/stores'
import { storeToRefs } from 'pinia'
import { onBeforeMount, watch, watchEffect } from 'vue'
@ -88,8 +78,8 @@ watchEffect(() => {
case TitleOptions.GLOBAL_SPEED:
document.title =
'[' +
`D: ${ formatSpeed(serverState.value?.dl_info_speed ?? 0, useBitSpeed.value) }, ` +
`U: ${ formatSpeed(serverState.value?.up_info_speed ?? 0, useBitSpeed.value) }` +
`D: ${formatSpeed(serverState.value?.dl_info_speed ?? 0, useBitSpeed.value)}, ` +
`U: ${formatSpeed(serverState.value?.up_info_speed ?? 0, useBitSpeed.value)}` +
'] VueTorrent'
break
case TitleOptions.FIRST_TORRENT_STATUS:
@ -97,9 +87,9 @@ watchEffect(() => {
if (torrent) {
document.title =
'[' +
`D: ${ formatSpeed(torrent.dlspeed, useBitSpeed.value) }, ` +
`U: ${ formatSpeed(torrent.upspeed, useBitSpeed.value) }, ` +
`${ formatPercent(torrent.progress) }` +
`D: ${formatSpeed(torrent.dlspeed, useBitSpeed.value)}, ` +
`U: ${formatSpeed(torrent.upspeed, useBitSpeed.value)}, ` +
`${formatPercent(torrent.progress)}` +
'] VueTorrent'
} else {
document.title = '[N/A] VueTorrent'
@ -118,8 +108,7 @@ watchEffect(() => {
<template>
<v-app class="text-noselect">
<component v-for="dialog in dialogStore.dialogs" :is="dialog.component"
v-bind="{ guid: dialog.guid, ...dialog.props }" />
<component v-for="dialog in dialogStore.dialogs" :is="dialog.component" v-bind="{ guid: dialog.guid, ...dialog.props }" />
<Navbar v-if="authStore.isAuthenticated" />
<v-main>
<router-view />

View file

@ -11,8 +11,7 @@ function openAddTorrentDialog() {
</script>
<template>
<v-bottom-navigation :active="addTorrentStore.pendingTorrentsCount > 0" class="pointer"
v-touch="{ up: openAddTorrentDialog }" @click="openAddTorrentDialog">
<v-bottom-navigation :active="addTorrentStore.pendingTorrentsCount > 0" class="pointer" v-touch="{ up: openAddTorrentDialog }" @click="openAddTorrentDialog">
<v-list-item :title="$t('navbar.addPanel.torrentsPendingCount', addTorrentStore.pendingTorrentsCount)" />
<v-spacer />
<v-list-item>

View file

@ -12,11 +12,8 @@ const vueTorrentStore = useVueTorrentStore()
<v-row data-testid="card-wrapper" :class="[`text-${color}`]">
<v-col data-testid="card-title" cols="7" class="text-subtitle-1">{{ title }}</v-col>
<v-col cols="5" class="">
<span data-testid="card-value"
class="text-subtitle-1 roboto">{{ formatDataValue(value, vueTorrentStore.useBinarySize) }}</span>
<span data-testid="card-unit" class="font-weight-light text-caption ml-1 text-subtitle-1">{{
formatDataUnit(value, vueTorrentStore.useBinarySize)
}}</span>
<span data-testid="card-value" class="text-subtitle-1 roboto">{{ formatDataValue(value, vueTorrentStore.useBinarySize) }}</span>
<span data-testid="card-unit" class="font-weight-light text-caption ml-1 text-subtitle-1">{{ formatDataUnit(value, vueTorrentStore.useBinarySize) }}</span>
</v-col>
</v-row>
</v-sheet>

View file

@ -36,6 +36,4 @@ defineExpose({
<v-combobox v-model="_value" ref="field" :items="historyValue" />
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -27,11 +27,8 @@ const vueTorrentStore = useVueTorrentStore()
<v-icon class="" :icon="icon" :color="color" />
</v-col>
<v-col cols="8" class="d-flex flex-column align-center justify-center">
<span class="text-subtitle-1 roboto"
:class="`text-${color}`">{{ formatSpeedValue(value, vueTorrentStore.useBitSpeed) }}</span>
<span class="text-caption" :class="`text-${color}`">{{
formatSpeedUnit(value, vueTorrentStore.useBitSpeed)
}}</span>
<span class="text-subtitle-1 roboto" :class="`text-${color}`">{{ formatSpeedValue(value, vueTorrentStore.useBitSpeed) }}</span>
<span class="text-caption" :class="`text-${color}`">{{ formatSpeedUnit(value, vueTorrentStore.useBitSpeed) }}</span>
</v-col>
</v-row>
</v-sheet>
@ -44,4 +41,4 @@ const vueTorrentStore = useVueTorrentStore()
font-family: 'Roboto Mono', sans-serif !important;
font-weight: 500;
}
</style>
</style>

View file

@ -11,7 +11,7 @@ const vuetorrentStore = useVueTorrentStore()
<template>
<div class="d-flex flex-column">
<div class="text-caption text-grey">
{{ $t(`torrent.properties.${ title }`) }}
{{ $t(`torrent.properties.${title}`) }}
</div>
<div>
{{ formatDataValue(torrent[value], vuetorrentStore.useBinarySize) }}

View file

@ -11,7 +11,7 @@ const vueTorrentStore = useVueTorrentStore()
<template>
<div class="d-flex flex-column">
<div class="text-caption text-grey">
{{ $t(`torrent.properties.${ title }`) }}
{{ $t(`torrent.properties.${title}`) }}
</div>
<div>
<span v-if="torrent[value] > 0">

View file

@ -11,7 +11,7 @@ const vuetorrentStore = useVueTorrentStore()
<template>
<div class="d-flex flex-column">
<div class="text-caption text-grey">
{{ $t(`torrent.properties.${ title }`) }}
{{ $t(`torrent.properties.${title}`) }}
</div>
<div>
{{ formatSpeedValue(torrent[value], vuetorrentStore.useBitSpeed) }}

View file

@ -116,7 +116,7 @@ async function exportTorrents() {
const link = document.createElement('a')
link.href = url
link.style.opacity = '0'
link.setAttribute('download', `${ hash }.torrent`)
link.setAttribute('download', `${hash}.torrent`)
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
@ -280,8 +280,7 @@ const menuData = computed<TRCMenuEntry[]>(() => [
</script>
<template>
<v-menu v-if="trcVisible" v-model="trcVisible" activator="parent" :close-on-content-click="true"
transition="slide-y-transition" scroll-strategy="none">
<v-menu v-if="trcVisible" v-model="trcVisible" activator="parent" :close-on-content-click="true" transition="slide-y-transition" scroll-strategy="none">
<v-list>
<v-list-item>
<div class="d-flex justify-space-around">
@ -294,8 +293,7 @@ const menuData = computed<TRCMenuEntry[]>(() => [
<v-tooltip location="top">
<template v-slot:activator="{ props }">
<v-btn density="compact" variant="plain" icon="mdi-fast-forward" v-bind="props"
@click="forceResumeTorrents" />
<v-btn density="compact" variant="plain" icon="mdi-fast-forward" v-bind="props" @click="forceResumeTorrents" />
</template>
<span>Force Resume</span>
</v-tooltip>
@ -309,8 +307,7 @@ const menuData = computed<TRCMenuEntry[]>(() => [
<v-tooltip location="top">
<template v-slot:activator="{ props }">
<v-btn color="red" density="compact" variant="plain" icon="mdi-delete-forever" v-bind="props"
@click="deleteTorrents" />
<v-btn color="red" density="compact" variant="plain" icon="mdi-delete-forever" v-bind="props" @click="deleteTorrents" />
</template>
<span>Delete</span>
</v-tooltip>

View file

@ -61,13 +61,11 @@ const isTorrentSelected = computed(() => dashboardStore.isTorrentInSelection(pro
</script>
<template>
<v-card :class="`sideborder ${torrent.state} pointer`"
:color="isTorrentSelected ? `torrent-${torrent.state}-darken-3` : undefined" width="100%" @click="onClick">
<v-card :class="`sideborder ${torrent.state} pointer`" :color="isTorrentSelected ? `torrent-${torrent.state}-darken-3` : undefined" width="100%" @click="onClick">
<v-card-title class="text-wrap text-subtitle-1 pt-1 pb-0">{{ torrent.name }}</v-card-title>
<v-card-text class="pa-2 pt-0">
<div class="d-flex gap flex-wrap">
<component :is="getComponent(ppt.type)" :torrent="torrent" v-bind="ppt.props"
v-for="ppt in torrentProperties" />
<component :is="getComponent(ppt.type)" :torrent="torrent" v-bind="ppt.props" v-for="ppt in torrentProperties" />
</div>
</v-card-text>
</v-card>

View file

@ -9,12 +9,15 @@ import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { toast } from 'vue3-toastify'
const props = withDefaults(defineProps<{
guid: string
openSuddenly?: boolean
}>(), {
openSuddenly: false
})
const props = withDefaults(
defineProps<{
guid: string
openSuddenly?: boolean
}>(),
{
openSuddenly: false
}
)
const { isOpened } = useDialog(props.guid)
const { t } = useI18n()
@ -45,12 +48,12 @@ const savePathField = ref<typeof HistoryField>()
const cookie = computed({
get: () => form.value.cookie,
set: value => form.value.cookie = value || undefined
set: value => (form.value.cookie = value || undefined)
})
const rename = computed({
get: () => form.value.rename,
set: value => form.value.rename = value || undefined
set: value => (form.value.rename = value || undefined)
})
const tagSearch = ref('')
@ -62,14 +65,14 @@ const tags = computed({
return []
}
},
set: (value: string[]) => form.value.tags = value.join(',')
set: (value: string[]) => (form.value.tags = value.join(','))
})
const categorySearch = ref('')
const categories = computed(() => maindataStore.categories.map(c => c.name))
const category = computed<string | undefined>({
get: () => form.value.category || categorySearch.value || undefined,
set: value => form.value.category = value || undefined
set: value => (form.value.category = value || undefined)
})
const downloadPath = computed({
@ -82,7 +85,7 @@ const downloadPath = computed({
const startNow = computed({
get: () => !form.value.paused,
set: value => form.value.paused = !value
set: value => (form.value.paused = !value)
})
const dlLimit = computed({
@ -127,36 +130,41 @@ const upLimit = computed({
const ratioLimit = computed({
get: () => form.value.ratioLimit,
set: val => form.value.ratioLimit = val || undefined
set: val => (form.value.ratioLimit = val || undefined)
})
const seedingTimeLimit = computed({
get: () => form.value.seedingTimeLimit,
set: val => form.value.seedingTimeLimit = val || undefined
set: val => (form.value.seedingTimeLimit = val || undefined)
})
const inactiveSeedingTimeLimit = computed({
get: () => form.value.inactiveSeedingTimeLimit,
set: val => form.value.inactiveSeedingTimeLimit = val || undefined
set: val => (form.value.inactiveSeedingTimeLimit = val || undefined)
})
function submit() {
if (!isFormValid.value) return
toast.promise(torrentStore.addTorrents(files.value, urls.value, form.value), {
pending: t('dialogs.add.pending'),
error: t('dialogs.add.error', addTorrentStore.pendingTorrentsCount),
success: t('dialogs.add.success', addTorrentStore.pendingTorrentsCount)
}, {
autoClose: 1500
})
.then(() => {
cookieField.value?.saveValueToHistory()
dlPathField.value?.saveValueToHistory()
savePathField.value?.saveValueToHistory()
addTorrentStore.resetForm()
close()
})
toast
.promise(
torrentStore.addTorrents(files.value, urls.value, form.value),
{
pending: t('dialogs.add.pending'),
error: t('dialogs.add.error', addTorrentStore.pendingTorrentsCount),
success: t('dialogs.add.success', addTorrentStore.pendingTorrentsCount)
},
{
autoClose: 1500
}
)
.then(() => {
cookieField.value?.saveValueToHistory()
dlPathField.value?.saveValueToHistory()
savePathField.value?.saveValueToHistory()
addTorrentStore.resetForm()
close()
})
}
function close() {
@ -169,8 +177,11 @@ const onCategoryChanged = () => {
</script>
<template>
<v-dialog v-model="isOpened" :class="$vuetify.display.mobile ? '' : 'w-75'"
:fullscreen="$vuetify.display.mobile" :transition="openSuddenly ? 'none' : 'dialog-bottom-transition'">
<v-dialog
v-model="isOpened"
:class="$vuetify.display.mobile ? '' : 'w-75'"
:fullscreen="$vuetify.display.mobile"
:transition="openSuddenly ? 'none' : 'dialog-bottom-transition'">
<v-card>
<v-card-title>
<v-toolbar color="transparent">
@ -182,10 +193,17 @@ const onCategoryChanged = () => {
<v-card-text>
<v-row>
<v-col cols="12">
<v-file-input v-model="files" :label="t('dialogs.add.files')"
:show-size="vueTorrentStore.useBinarySize ? 1024 : 1000"
accept=".torrent" counter multiple
persistent-clear persistent-hint prepend-icon="" variant="outlined">
<v-file-input
v-model="files"
:label="t('dialogs.add.files')"
:show-size="vueTorrentStore.useBinarySize ? 1024 : 1000"
accept=".torrent"
counter
multiple
persistent-clear
persistent-hint
prepend-icon=""
variant="outlined">
<template v-slot:prepend>
<v-icon color="accent">mdi-paperclip</v-icon>
</template>
@ -195,8 +213,7 @@ const onCategoryChanged = () => {
{{ filename }}
</v-chip>
</template>
<span v-if="fileNames.length === fileOverflowDisplayLimit + 1"
class="text-overline text-grey-darken-2 ml-2">
<span v-if="fileNames.length === fileOverflowDisplayLimit + 1" class="text-overline text-grey-darken-2 ml-2">
{{ t('dialogs.add.fileOverflow', fileNames.length - fileOverflowDisplayLimit) }}
</span>
</template>
@ -206,8 +223,14 @@ const onCategoryChanged = () => {
<v-icon color="accent">mdi-link</v-icon>
</template>
</v-textarea>
<HistoryField v-if="!!urls" v-model="cookie" :historyKey="HistoryKey.COOKIE" ref="cookieField" clearable
:label="$t('dialogs.add.cookie')" :placeholder="$t('dialogs.add.cookiePlaceholder')">
<HistoryField
v-if="!!urls"
v-model="cookie"
:historyKey="HistoryKey.COOKIE"
ref="cookieField"
clearable
:label="$t('dialogs.add.cookie')"
:placeholder="$t('dialogs.add.cookiePlaceholder')">
<template v-slot:prepend>
<v-icon color="accent">mdi-cookie</v-icon>
</template>
@ -220,8 +243,17 @@ const onCategoryChanged = () => {
</v-col>
<v-col cols="12" md="6">
<v-combobox v-model="tags" v-model:search="tagSearch" :hide-no-data="false" :items="maindataStore.tags"
:label="t('dialogs.add.tags')" chips clearable hide-details multiple autocomplete="tags">
<v-combobox
v-model="tags"
v-model:search="tagSearch"
:hide-no-data="false"
:items="maindataStore.tags"
:label="t('dialogs.add.tags')"
chips
clearable
hide-details
multiple
autocomplete="tags">
<template v-slot:prepend>
<v-icon color="accent">mdi-tag</v-icon>
</template>
@ -239,9 +271,16 @@ const onCategoryChanged = () => {
</v-col>
<v-col cols="12" md="6">
<v-combobox v-model="category" v-model:search="categorySearch" :hide-no-data="false" :items="categories"
:label="$t('dialogs.add.category')" clearable hide-details autocomplete="categories"
@update:modelValue="onCategoryChanged">
<v-combobox
v-model="category"
v-model:search="categorySearch"
:hide-no-data="false"
:items="categories"
:label="$t('dialogs.add.category')"
clearable
hide-details
autocomplete="categories"
@update:modelValue="onCategoryChanged">
<template v-slot:prepend>
<v-icon color="accent">mdi-label</v-icon>
</template>
@ -259,8 +298,13 @@ const onCategoryChanged = () => {
</v-col>
<v-col cols="12">
<HistoryField v-model="downloadPath" :history-key="HistoryKey.TORRENT_PATH" ref="dlPathField"
:disabled="form.autoTMM" :label="t('dialogs.add.downloadPath')" hide-details>
<HistoryField
v-model="downloadPath"
:history-key="HistoryKey.TORRENT_PATH"
ref="dlPathField"
:disabled="form.autoTMM"
:label="t('dialogs.add.downloadPath')"
hide-details>
<template v-slot:prepend>
<v-icon color="accent">mdi-tray-arrow-down</v-icon>
</template>
@ -268,8 +312,13 @@ const onCategoryChanged = () => {
</v-col>
<v-col cols="12">
<HistoryField v-model="form.savepath" :history-key="HistoryKey.TORRENT_PATH" ref="savePathField"
:disabled="form.autoTMM" :label="t('dialogs.add.savePath')" hide-details>
<HistoryField
v-model="form.savepath"
:history-key="HistoryKey.TORRENT_PATH"
ref="savePathField"
:disabled="form.autoTMM"
:label="t('dialogs.add.savePath')"
hide-details>
<template v-slot:prepend>
<v-icon color="accent">mdi-content-save</v-icon>
</template>
@ -277,42 +326,46 @@ const onCategoryChanged = () => {
</v-col>
<v-col cols="12" md="6">
<v-select v-model="form.contentLayout" :items="contentLayoutOptions"
:label="t('constants.contentLayout.title')" color="accent" hide-details rounded="xl"
variant="solo-filled" />
<v-select
v-model="form.contentLayout"
:items="contentLayoutOptions"
:label="t('constants.contentLayout.title')"
color="accent"
hide-details
rounded="xl"
variant="solo-filled" />
</v-col>
<v-col cols="12" md="6">
<v-select v-model="form.stopCondition" :items="stopConditionOptions"
:label="t('constants.stopCondition.title')" color="accent" hide-details rounded="xl"
variant="solo-filled" />
<v-select
v-model="form.stopCondition"
:items="stopConditionOptions"
:label="t('constants.stopCondition.title')"
color="accent"
hide-details
rounded="xl"
variant="solo-filled" />
</v-col>
</v-row>
<v-row class="mx-3">
<v-col cols="12" md="6">
<v-checkbox v-model="startNow" :label="t('dialogs.add.startNow')" color="accent"
density="compact" hide-details />
<v-checkbox v-model="startNow" :label="t('dialogs.add.startNow')" color="accent" density="compact" hide-details />
</v-col>
<v-col cols="12" md="6">
<v-checkbox v-model="form.addToTopOfQueue" :label="t('dialogs.add.addToTopOfQueue')" color="accent"
density="compact" hide-details />
<v-checkbox v-model="form.addToTopOfQueue" :label="t('dialogs.add.addToTopOfQueue')" color="accent" density="compact" hide-details />
</v-col>
<v-col cols="12" md="6">
<v-checkbox v-model="form.skip_checking" :label="t('dialogs.add.skipChecking')" color="accent"
density="compact" hide-details />
<v-checkbox v-model="form.skip_checking" :label="t('dialogs.add.skipChecking')" color="accent" density="compact" hide-details />
</v-col>
<v-col cols="12" md="6">
<v-checkbox v-model="form.autoTMM" :label="t('dialogs.add.autoTMM')" color="accent"
density="compact" hide-details />
<v-checkbox v-model="form.autoTMM" :label="t('dialogs.add.autoTMM')" color="accent" density="compact" hide-details />
</v-col>
<v-col cols="12" md="6">
<v-checkbox v-model="form.sequentialDownload" :label="t('dialogs.add.sequentialDownload')" color="accent"
density="compact" hide-details />
<v-checkbox v-model="form.sequentialDownload" :label="t('dialogs.add.sequentialDownload')" color="accent" density="compact" hide-details />
</v-col>
<v-col cols="12" md="6">
<v-checkbox v-model="form.firstLastPiecePrio" :label="t('dialogs.add.firstLastPiecePrio')" color="accent"
density="compact" hide-details />
<v-checkbox v-model="form.firstLastPiecePrio" :label="t('dialogs.add.firstLastPiecePrio')" color="accent" density="compact" hide-details />
</v-col>
</v-row>
@ -323,16 +376,14 @@ const onCategoryChanged = () => {
<v-expansion-panel-text>
<v-row>
<v-col cols="12" md="6">
<v-text-field v-model="dlLimit" :label="$t('dialogs.add.dlLimit')"
hide-details suffix="KiB/s">
<v-text-field v-model="dlLimit" :label="$t('dialogs.add.dlLimit')" hide-details suffix="KiB/s">
<template v-slot:prepend>
<v-icon color="accent">mdi-download</v-icon>
</template>
</v-text-field>
</v-col>
<v-col cols="12" md="6">
<v-text-field v-model="upLimit" :label="$t('dialogs.add.upLimit')"
hide-details suffix="KiB/s">
<v-text-field v-model="upLimit" :label="$t('dialogs.add.upLimit')" hide-details suffix="KiB/s">
<template v-slot:prepend>
<v-icon color="accent">mdi-upload</v-icon>
</template>
@ -340,21 +391,23 @@ const onCategoryChanged = () => {
</v-col>
<v-col cols="12" md="4">
<v-text-field v-model="ratioLimit"
:hint="$t('dialogs.add.limitHint')" :label="$t('dialogs.add.ratioLimit')"
type="number" />
<v-text-field v-model="ratioLimit" :hint="$t('dialogs.add.limitHint')" :label="$t('dialogs.add.ratioLimit')" type="number" />
</v-col>
<v-col cols="12" md="4">
<v-text-field v-model="seedingTimeLimit"
:label="$t('dialogs.add.seedingTimeLimit')"
:hint="$t('dialogs.add.limitHint')"
:suffix="$t('units.minutes')" type="number" />
<v-text-field
v-model="seedingTimeLimit"
:label="$t('dialogs.add.seedingTimeLimit')"
:hint="$t('dialogs.add.limitHint')"
:suffix="$t('units.minutes')"
type="number" />
</v-col>
<v-col cols="12" md="4">
<v-text-field v-model="inactiveSeedingTimeLimit"
:label="$t('dialogs.add.inactiveSeedingTimeLimit')"
:hint="$t('dialogs.add.limitHint')"
:suffix="$t('units.minutes')" type="number" />
<v-text-field
v-model="inactiveSeedingTimeLimit"
:label="$t('dialogs.add.inactiveSeedingTimeLimit')"
:hint="$t('dialogs.add.limitHint')"
:suffix="$t('units.minutes')"
type="number" />
</v-col>
</v-row>
</v-expansion-panel-text>
@ -367,8 +420,7 @@ const onCategoryChanged = () => {
<v-card-actions class="justify-center">
<v-btn :text="$t('dialogs.add.resetForm')" color="error" variant="flat" @click="addTorrentStore.resetForm()" />
<v-spacer />
<v-btn :disabled="!isFormValid" :text="$t('dialogs.add.submit')" color="accent"
type="submit" variant="elevated" @click="submit" />
<v-btn :disabled="!isFormValid" :text="$t('dialogs.add.submit')" color="accent" type="submit" variant="elevated" @click="submit" />
<v-btn :text="$t('common.close')" color="" variant="flat" @click="close" />
</v-card-actions>
</v-card>

View file

@ -49,15 +49,12 @@ onBeforeMount(() => {
<template>
<v-dialog v-model="isOpened">
<v-card>
<v-card-title>{{ $t(`dialogs.category.title.${ initialCategory ? 'edit' : 'create' }`) }}</v-card-title>
<v-card-title>{{ $t(`dialogs.category.title.${initialCategory ? 'edit' : 'create'}`) }}</v-card-title>
<v-card-text>
<v-form v-model="isFormValid" ref="form" @submit.prevent @keydown.enter.prevent="submit">
<v-text-field v-if="!!initialCategory" :model-value="initialCategory.name" disabled
:label="$t('dialogs.category.oldName')" />
<v-text-field v-model="formData.name" :rules="nameRules" :autofocus="!initialCategory"
:label="$t('dialogs.category.name')" />
<v-text-field v-model="formData.savePath" :autofocus="!!initialCategory"
:label="$t('dialogs.category.savePath')" />
<v-text-field v-if="!!initialCategory" :model-value="initialCategory.name" disabled :label="$t('dialogs.category.oldName')" />
<v-text-field v-model="formData.name" :rules="nameRules" :autofocus="!initialCategory" :label="$t('dialogs.category.name')" />
<v-text-field v-model="formData.savePath" :autofocus="!!initialCategory" :label="$t('dialogs.category.savePath')" />
<v-scroll-x-transition>
<div class="text-warning" v-if="!!initialCategory && initialCategory.name !== formData.name">
<v-icon>mdi-alert</v-icon>

View file

@ -71,8 +71,7 @@ onUnmounted(() => {
<div class="d-flex flex-wrap gap">
<span class="pa-1 border wrap-anywhere" v-for="torrent in selection">{{ torrent.name }}</span>
</div>
<v-checkbox v-model="vuetorrentStore.deleteWithFiles" hide-details
:label="$t('dialogs.delete.deleteWithFiles')" />
<v-checkbox v-model="vuetorrentStore.deleteWithFiles" hide-details :label="$t('dialogs.delete.deleteWithFiles')" />
<v-scroll-x-transition>
<div class="text-red" v-show="vuetorrentStore.deleteWithFiles">
<v-icon>mdi-alert</v-icon>

View file

@ -9,7 +9,7 @@ import { VForm } from 'vuetify/components'
const props = defineProps<{
guid: string
hashes: string[],
hashes: string[]
mode: 'dl' | 'save'
}>()
@ -65,9 +65,14 @@ onBeforeMount(() => {
<v-card-text>
<v-form v-model="isFormValid" ref="form" @submit.prevent>
<v-text-field v-if="oldPath" :model-value="oldPath" disabled :label="$t('dialogs.moveTorrent.oldPath')" />
<HistoryField v-model="formData.newPath" :historyKey="HistoryKey.TORRENT_PATH" ref="field"
:rules="rules" autofocus :label="$t('dialogs.moveTorrent.newPath')"
@keydown.enter="submit" />
<HistoryField
v-model="formData.newPath"
:historyKey="HistoryKey.TORRENT_PATH"
ref="field"
:rules="rules"
autofocus
:label="$t('dialogs.moveTorrent.newPath')"
@keydown.enter="submit" />
</v-form>
</v-card-text>
<v-card-actions>

View file

@ -65,8 +65,7 @@ onBeforeMount(() => {
<v-card-text>
<v-form v-model="isFormValid" ref="form" @submit.prevent>
<v-text-field v-if="oldName" :model-value="oldName" disabled :label="$t('dialogs.moveTorrentFile.oldName')" />
<v-text-field v-model="formData.newName" ref="input" :rules="rules" autofocus
:label="$t('dialogs.moveTorrent.newPath')" @keydown.enter="submit" />
<v-text-field v-model="formData.newName" ref="input" :rules="rules" autofocus :label="$t('dialogs.moveTorrent.newPath')" @keydown.enter="submit" />
</v-form>
</v-card-text>
<v-card-actions>

View file

@ -83,8 +83,7 @@ function closeInstallDialog() {
<v-spacer />
<v-btn :text="$t('dialogs.pluginManager.update')" color="accent" class="mr-2" :loading="updateLoading"
@click="updatePlugins" />
<v-btn :text="$t('dialogs.pluginManager.update')" color="accent" class="mr-2" :loading="updateLoading" @click="updatePlugins" />
<v-dialog v-model="installisOpened">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" color="primary">
@ -107,8 +106,7 @@ function closeInstallDialog() {
</v-dialog>
</v-card-title>
<v-card-text>
<v-data-table :headers="headers" items-per-page="-1" :items="searchEngineStore.searchPlugins"
:sort-by="[{ key: 'fullName', order: 'asc' }]" :loading="loading">
<v-data-table :headers="headers" items-per-page="-1" :items="searchEngineStore.searchPlugins" :sort-by="[{ key: 'fullName', order: 'asc' }]" :loading="loading">
<template v-slot:item.enabled="{ item }">
<v-checkbox-btn :model-value="item.enabled" @click="onTogglePlugin(item)" />
</template>

View file

@ -54,8 +54,7 @@ onMounted(() => {
<v-card-text>
<v-form v-model="isFormValid" ref="form" @submit.prevent>
<v-text-field v-if="oldName" :model-value="oldName" disabled :label="$t('dialogs.renameTorrent.oldName')" />
<v-text-field v-model="formData.newName" ref="field" :rules="rules" autofocus
:label="$t('dialogs.renameTorrent.newName')" @keydown.enter="submit" />
<v-text-field v-model="formData.newName" ref="field" :rules="rules" autofocus :label="$t('dialogs.renameTorrent.newName')" @keydown.enter="submit" />
</v-form>
</v-card-text>
<v-card-actions>

View file

@ -45,7 +45,7 @@ onBeforeMount(() => {
<template>
<v-dialog v-model="isOpened">
<v-card>
<v-card-title>{{ $t(`dialogs.rss.feed.title.${ initialFeed ? 'edit' : 'create' }`) }}</v-card-title>
<v-card-title>{{ $t(`dialogs.rss.feed.title.${initialFeed ? 'edit' : 'create'}`) }}</v-card-title>
<v-card-text>
<v-form v-model="isFormValid" ref="form" @submit.prevent>
<v-text-field v-model="formData.name" :label="$t('dialogs.rss.feed.name')" />

View file

@ -50,10 +50,13 @@ const contentLayoutOptions = [
{ title: t('constants.contentLayout.nosubfolder'), value: ContentLayout.NO_SUBFOLDER }
]
const categories = computed(() => {
return [{ title: t('common.none'), value: '' }, ...maindataStore.categories.map(category => ({
title: category.name,
value: category.name
}))]
return [
{ title: t('common.none'), value: '' },
...maindataStore.categories.map(category => ({
title: category.name,
value: category.name
}))
]
})
const lastMatch = computed(() => {
@ -145,26 +148,19 @@ onBeforeMount(async () => {
<v-text-field v-model="formData.mustContain" :label="$t('dialogs.rss.rule.mustContain')" />
<v-text-field v-model="formData.mustNotContain" :label="$t('dialogs.rss.rule.mustNotContain')" />
<v-checkbox v-model="formData.smartFilter" hide-details :label="$t('dialogs.rss.rule.smartFilter')" />
<v-text-field v-model="formData.episodeFilter"
:placeholder="$t('dialogs.rss.rule.episodeFilterPlaceholder')"
:label="$t('dialogs.rss.rule.episodeFilter')" />
<v-text-field v-model="formData.episodeFilter" :placeholder="$t('dialogs.rss.rule.episodeFilterPlaceholder')" :label="$t('dialogs.rss.rule.episodeFilter')" />
<v-divider class="mb-4" />
<v-select v-model="formData.assignedCategory" :items="categories"
:label="$t('dialogs.rss.rule.assignedCategory')" />
<v-text-field v-model="formData.savePath" :placeholder="$t('dialogs.rss.rule.savePathPlaceholder')"
:label="$t('dialogs.rss.rule.savePath')" />
<v-text-field v-model="formData.ignoreDays" type="number" :hint="$t('dialogs.rss.rule.ignoreDaysHint')"
:label="$t('dialogs.rss.rule.ignoreDays')" />
<v-select v-model="formData.assignedCategory" :items="categories" :label="$t('dialogs.rss.rule.assignedCategory')" />
<v-text-field v-model="formData.savePath" :placeholder="$t('dialogs.rss.rule.savePathPlaceholder')" :label="$t('dialogs.rss.rule.savePath')" />
<v-text-field v-model="formData.ignoreDays" type="number" :hint="$t('dialogs.rss.rule.ignoreDaysHint')" :label="$t('dialogs.rss.rule.ignoreDays')" />
<v-text-field v-model="lastMatch" disabled :label="$t('dialogs.rss.rule.lastMatch.label')" />
<v-divider />
<v-select v-model="formData.addPaused" :items="addPausedOptions"
:label="$t('constants.addPaused.title')" />
<v-select v-model="formData.torrentContentLayout" :items="contentLayoutOptions"
:label="$t('constants.contentLayout.title')" />
<v-select v-model="formData.addPaused" :items="addPausedOptions" :label="$t('constants.addPaused.title')" />
<v-select v-model="formData.torrentContentLayout" :items="contentLayoutOptions" :label="$t('constants.contentLayout.title')" />
<v-list-subheader>{{ $t('dialogs.rss.rule.affectedFeedsSubheader') }}</v-list-subheader>
@ -177,8 +173,7 @@ onBeforeMount(async () => {
</v-col>
</v-row>
<v-checkbox v-for="item in rssStore.feeds" v-model="formData.affectedFeeds" multiple hide-details
:label="item.name" :value="item.url" />
<v-checkbox v-for="item in rssStore.feeds" v-model="formData.affectedFeeds" multiple hide-details :label="item.name" :value="item.url" />
</v-col>
<v-divider :vertical="!$vuetify.display.mobile" />
@ -191,8 +186,7 @@ onBeforeMount(async () => {
<v-list-subheader inset v-else-if="item.type === 'subheader'">{{ item.value }}</v-list-subheader>
<v-list-item v-else class="mb-3">{{ item.value }}</v-list-item>
</template>
<v-list-item v-if="matchingArticles.length === 0"
:title="$t('dialogs.rss.rule.matchingArticles.noMatch')" />
<v-list-item v-if="matchingArticles.length === 0" :title="$t('dialogs.rss.rule.matchingArticles.noMatch')" />
</v-list>
</v-col>
</v-row>

View file

@ -44,10 +44,12 @@ async function submit() {
await maindataStore.setShareLimit(props.hashes, DISABLED, DISABLED, DISABLED)
break
case 'enabled':
await maindataStore.setShareLimit(props.hashes,
await maindataStore.setShareLimit(
props.hashes,
ratioLimitEnabled.value ? ratioLimit.value : DISABLED,
seedingTimeLimitEnabled.value ? seedingTimeLimit.value : DISABLED,
inactiveSeedingTimeLimitEnabled.value ? inactiveSeedingTimeLimit.value : DISABLED)
inactiveSeedingTimeLimitEnabled.value ? inactiveSeedingTimeLimit.value : DISABLED
)
break
}
close()
@ -95,27 +97,25 @@ onBeforeMount(async () => {
<v-row>
<v-col cols="12" class="d-flex align-center">
<span><v-checkbox-btn v-model="ratioLimitEnabled" :disabled="isFieldsDisabled" /></span>
<v-text-field v-model="ratioLimit"
:disabled="isFieldsDisabled || !ratioLimitEnabled"
density="compact"
hide-details
:label="$t('dialogs.share_limit.ratio_limit')" />
<v-text-field v-model="ratioLimit" :disabled="isFieldsDisabled || !ratioLimitEnabled" density="compact" hide-details :label="$t('dialogs.share_limit.ratio_limit')" />
</v-col>
<v-col cols="12" class="d-flex align-center">
<span><v-checkbox-btn v-model="seedingTimeLimitEnabled" :disabled="isFieldsDisabled" /></span>
<v-text-field v-model="seedingTimeLimit"
:disabled="isFieldsDisabled || !seedingTimeLimitEnabled"
density="compact"
hide-details
:label="$t('dialogs.share_limit.seeding_time_limit')" />
<v-text-field
v-model="seedingTimeLimit"
:disabled="isFieldsDisabled || !seedingTimeLimitEnabled"
density="compact"
hide-details
:label="$t('dialogs.share_limit.seeding_time_limit')" />
</v-col>
<v-col cols="12" class="d-flex align-center">
<span><v-checkbox-btn v-model="inactiveSeedingTimeLimitEnabled" :disabled="isFieldsDisabled" /></span>
<v-text-field v-model="inactiveSeedingTimeLimit"
:disabled="isFieldsDisabled || !inactiveSeedingTimeLimitEnabled"
density="compact"
hide-details
:label="$t('dialogs.share_limit.inactive_seeding_time_limit')" />
<v-text-field
v-model="inactiveSeedingTimeLimit"
:disabled="isFieldsDisabled || !inactiveSeedingTimeLimitEnabled"
density="compact"
hide-details
:label="$t('dialogs.share_limit.inactive_seeding_time_limit')" />
</v-col>
</v-row>
</v-form>
@ -129,6 +129,4 @@ onBeforeMount(async () => {
</v-dialog>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -55,14 +55,15 @@ onBeforeMount(async () => {
<v-card :title="$t(`dialogs.speed_limit.${mode}`)">
<v-card-text>
<v-form v-model="isFormValid" @submit.prevent @keydown.enter.prevent="submit">
<v-text-field v-model="value"
type="number"
autofocus
clearable
:label="$t('dialogs.speed_limit.label')"
prepend-inner-icon="mdi-speedometer"
suffix="kB/s"
@keydown.enter.prevent="submit" />
<v-text-field
v-model="value"
type="number"
autofocus
clearable
:label="$t('dialogs.speed_limit.label')"
prepend-inner-icon="mdi-speedometer"
suffix="kB/s"
@keydown.enter.prevent="submit" />
</v-form>
</v-card-text>
<v-card-actions>
@ -74,6 +75,4 @@ onBeforeMount(async () => {
</v-dialog>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -44,12 +44,11 @@ onBeforeMount(() => {
<template>
<v-dialog v-model="isOpened">
<v-card>
<v-card-title>{{ $t(`dialogs.tag.title.${ initialTag ? 'rename' : 'create' }`) }}</v-card-title>
<v-card-title>{{ $t(`dialogs.tag.title.${initialTag ? 'rename' : 'create'}`) }}</v-card-title>
<v-card-text>
<v-form v-model="isFormValid" ref="form" @submit.prevent @keydown.enter.prevent="submit">
<v-text-field v-if="initialTag" :model-value="initialTag" disabled :label="$t('dialogs.tag.oldName')" />
<v-text-field v-model="tagName" :rules="rules" autofocus :hint="$t('dialogs.tag.hint')"
:label="$t('dialogs.tag.name')" />
<v-text-field v-model="tagName" :rules="rules" autofocus :hint="$t('dialogs.tag.hint')" :label="$t('dialogs.tag.name')" />
<v-scroll-x-transition>
<div class="text-warning" v-if="!!initialTag && initialTag !== tagName">
<v-icon>mdi-alert</v-icon>

View file

@ -12,9 +12,11 @@ const dndZoneRef = ref<HTMLDivElement>()
function onDragEnter() {
if (
(route.name as string) === 'login'
|| (route.name as string) === 'settings' && route.params.tab === 'vuetorrent' && route.params.subtab === 'torrentCard'
|| !authStore.isAuthenticated) return
(route.name as string) === 'login' ||
((route.name as string) === 'settings' && route.params.tab === 'vuetorrent' && route.params.subtab === 'torrentCard') ||
!authStore.isAuthenticated
)
return
isOverDropZone.value = true
}
@ -24,10 +26,11 @@ function onDrop(files: File[] | null, event: DragEvent) {
if (!event.dataTransfer) return
// Handle .torrent files
const torrentFiles = (files || [])
.filter(file => file.type === 'application/x-bittorrent' || file.name.endsWith('.torrent'))
const links = event.dataTransfer.getData('text/plain').split('\n')
.filter(link => link.startsWith('magnet:') || link.startsWith('http'))
const torrentFiles = (files || []).filter(file => file.type === 'application/x-bittorrent' || file.name.endsWith('.torrent'))
const links = event.dataTransfer
.getData('text/plain')
.split('\n')
.filter(link => link.startsWith('magnet:') || link.startsWith('http'))
torrentFiles.forEach(addTorrentStore.pushTorrentToQueue)
links.forEach(addTorrentStore.pushTorrentToQueue)
@ -85,4 +88,4 @@ onUnmounted(() => {
border: 2px solid rgb(var(--v-theme-accent));
border-radius: 48px;
}
</style>
</style>

View file

@ -21,8 +21,7 @@ const toggleDrawer = () => {
</script>
<template>
<v-navigation-drawer v-model="isDrawerOpen" :location="vueTorrentStore.isDrawerRight ? 'right' : 'left'"
color="primary" disable-route-watcher>
<v-navigation-drawer v-model="isDrawerOpen" :location="vueTorrentStore.isDrawerRight ? 'right' : 'left'" color="primary" disable-route-watcher>
<v-list class="clean-px px-2 pt-0">
<v-list-item v-if="vueTorrentStore.showCurrentSpeed">
<CurrentSpeed />

View file

@ -40,7 +40,7 @@ const connectionStatusText = computed(() => {
key = 'unknown'
}
return t('navbar.side.bottom_actions.conn_status', { status: t(`constants.connectionStatus.${ key }`) })
return t('navbar.side.bottom_actions.conn_status', { status: t(`constants.connectionStatus.${key}`) })
})
const logout = async () => {

View file

@ -16,8 +16,7 @@ const maindataStore = useMaindataStore()
<v-sheet color="primary" class="mx-2">
<v-row class="pt-0">
<v-col class="px-1 pt-1">
<SpeedCard icon="mdi-chevron-down" color="download"
:value="maindataStore.serverState?.dl_info_speed ?? 0" />
<SpeedCard icon="mdi-chevron-down" color="download" :value="maindataStore.serverState?.dl_info_speed ?? 0" />
</v-col>
<v-col class="px-1 pt-1">
<SpeedCard icon="mdi-chevron-up" color="upload" :value="maindataStore.serverState?.up_info_speed ?? 0" />

View file

@ -6,34 +6,14 @@ import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const {
categories: _categories,
tags: _tags,
trackers: _trackers
} = storeToRefs(useMaindataStore())
const {
statusFilter,
categoryFilter,
tagFilter,
trackerFilter
} = storeToRefs(useTorrentStore())
const { categories: _categories, tags: _tags, trackers: _trackers } = storeToRefs(useMaindataStore())
const { statusFilter, categoryFilter, tagFilter, trackerFilter } = storeToRefs(useTorrentStore())
const vueTorrentStore = useVueTorrentStore()
const statuses = computed(() => Object.values(TorrentState).map(state => (
{ title: t(`torrent.state.${ state }`), value: state }
)))
const categories = computed(() => [
{ title: t('navbar.side.filters.uncategorized'), value: '' },
..._categories.value.map(c => ({ title: c.name, value: c.name }))
])
const tags = computed(() => [
{ title: t('navbar.side.filters.untagged'), value: null },
..._tags.value.map(tag => ({ title: tag, value: tag }))
])
const trackers = computed(() => [
{ title: t('navbar.side.filters.untracked'), value: '' },
..._trackers.value.map(tracker => ({ title: tracker, value: tracker }))
])
const statuses = computed(() => Object.values(TorrentState).map(state => ({ title: t(`torrent.state.${state}`), value: state })))
const categories = computed(() => [{ title: t('navbar.side.filters.uncategorized'), value: '' }, ..._categories.value.map(c => ({ title: c.name, value: c.name }))])
const tags = computed(() => [{ title: t('navbar.side.filters.untagged'), value: null }, ..._tags.value.map(tag => ({ title: tag, value: tag }))])
const trackers = computed(() => [{ title: t('navbar.side.filters.untracked'), value: '' }, ..._trackers.value.map(tracker => ({ title: tracker, value: tracker }))])
function selectAllStatuses() {
statusFilter.value = []
@ -58,19 +38,23 @@ function selectAllTrackers() {
<v-list-item-title class="px-0 text-uppercase white--text ml-1 font-weight-normal text-caption">
{{ t('navbar.side.filters.state') }}
</v-list-item-title>
<v-select v-model="statusFilter" :items="statuses" :placeholder="t('navbar.side.filters.disabled')"
bg-color="secondary"
class="text-accent pt-1" density="compact" hide-details multiple variant="solo">
<v-select
v-model="statusFilter"
:items="statuses"
:placeholder="t('navbar.side.filters.disabled')"
bg-color="secondary"
class="text-accent pt-1"
density="compact"
hide-details
multiple
variant="solo">
<template v-slot:prepend-item>
<v-list-item :title="$t('common.disable')" @click="selectAllStatuses" />
<v-divider />
</template>
<template v-slot:selection="{ item, index }">
<span v-if="index === 0 && statusFilter.length === 1"
class="text-accent">{{ t(`torrent.state.${ item.props.value }`) }}</span>
<span v-else-if="index === 0" class="text-accent">{{
t('navbar.side.filters.activeFilter', statusFilter.length)
}}</span>
<span v-if="index === 0 && statusFilter.length === 1" class="text-accent">{{ t(`torrent.state.${item.props.value}`) }}</span>
<span v-else-if="index === 0" class="text-accent">{{ t('navbar.side.filters.activeFilter', statusFilter.length) }}</span>
</template>
</v-select>
</v-list-item>
@ -79,8 +63,16 @@ function selectAllTrackers() {
<v-list-item-title class="px-0 text-uppercase white--text ml-1 font-weight-light text-subtitle-2">
{{ t('navbar.side.filters.category') }}
</v-list-item-title>
<v-select v-model="categoryFilter" :items="categories" :placeholder="t('navbar.side.filters.disabled')"
bg-color="secondary" class="text-accent pt-1" density="compact" hide-details multiple variant="solo">
<v-select
v-model="categoryFilter"
:items="categories"
:placeholder="t('navbar.side.filters.disabled')"
bg-color="secondary"
class="text-accent pt-1"
density="compact"
hide-details
multiple
variant="solo">
<template v-slot:prepend-item>
<v-list-item :title="$t('common.disable')" @click="selectAllCategories" />
<v-divider />
@ -100,8 +92,16 @@ function selectAllTrackers() {
<v-list-item-title class="px-0 text-uppercase white--text ml-1 font-weight-light text-subtitle-2">
{{ t('navbar.side.filters.tag') }}
</v-list-item-title>
<v-select v-model="tagFilter" :items="tags" :placeholder="t('navbar.side.filters.disabled')"
bg-color="secondary" class="text-accent pt-1" density="compact" hide-details multiple variant="solo">
<v-select
v-model="tagFilter"
:items="tags"
:placeholder="t('navbar.side.filters.disabled')"
bg-color="secondary"
class="text-accent pt-1"
density="compact"
hide-details
multiple
variant="solo">
<template v-slot:prepend-item>
<v-list-item :title="$t('common.disable')" @click="selectAllTags" />
<v-divider />
@ -117,13 +117,20 @@ function selectAllTrackers() {
</v-select>
</v-list-item>
<v-list-item v-if="vueTorrentStore.showTrackerFilter"
:class="{ 'px-0': true, 'pb-3': vueTorrentStore.showTrackerFilter }">
<v-list-item v-if="vueTorrentStore.showTrackerFilter" :class="{ 'px-0': true, 'pb-3': vueTorrentStore.showTrackerFilter }">
<v-list-item-title class="px-0 text-uppercase white--text ml-1 font-weight-light text-subtitle-2">
{{ t('navbar.side.filters.tracker') }}
</v-list-item-title>
<v-select v-model="trackerFilter" :items="trackers" :placeholder="t('navbar.side.filters.disabled')"
bg-color="secondary" class="text-accent pt-1" density="compact" hide-details multiple variant="solo">
<v-select
v-model="trackerFilter"
:items="trackers"
:placeholder="t('navbar.side.filters.disabled')"
bg-color="secondary"
class="text-accent pt-1"
density="compact"
hide-details
multiple
variant="solo">
<template v-slot:prepend-item>
<v-list-item :title="$t('common.disable')" @click="selectAllTrackers" />
<v-divider />

View file

@ -18,10 +18,7 @@ const ratio = computed(() => (props.session ? undefined : maindataStore.serverSt
<template>
<v-card variant="flat" color="primary">
<v-card-title class="px-0 pb-0 text-uppercase white--text ml-1 font-weight-normal text-caption">{{
title
}}
</v-card-title>
<v-card-title class="px-0 pb-0 text-uppercase white--text ml-1 font-weight-normal text-caption">{{ title }} </v-card-title>
<v-card-text class="px-0 pb-0">
<div class="d-flex flex-column gap">
<DataCard :title="$t('navbar.side.stats.downloaded')" :value="download" color="download" icon="mdi-arrow-down" />

View file

@ -20,11 +20,11 @@ const {
const globalFilterActive = computed(
() =>
isTextFilterActive.value && isTextFilterPresent.value ||
isStatusFilterActive.value && isStatusFilterPresent.value ||
isCategoryFilterActive.value && isCategoryFilterPresent.value ||
isTagFilterActive.value && isTagFilterPresent.value ||
isTrackerFilterActive.value && isTrackerFilterPresent.value
(isTextFilterActive.value && isTextFilterPresent.value) ||
(isStatusFilterActive.value && isStatusFilterPresent.value) ||
(isCategoryFilterActive.value && isCategoryFilterPresent.value) ||
(isTagFilterActive.value && isTagFilterPresent.value) ||
(isTrackerFilterActive.value && isTrackerFilterPresent.value)
)
const isTextFilterPresent = computed(() => textFilter.value.length > 0)
@ -33,13 +33,13 @@ const isCategoryFilterPresent = computed(() => categoryFilter.value.length > 0)
const isTagFilterPresent = computed(() => tagFilter.value.length > 0)
const isTrackerFilterPresent = computed(() => trackerFilter.value.length > 0)
const globalFilterColor = computed(() => globalFilterActive.value ? 'active-global' : 'active-global-disabled')
const textFilterColor = computed(() => isTextFilterActive.value ? 'active-text' : 'active-text-disabled')
const singleStatusFilterColor = computed(() => isStatusFilterActive.value ? `torrent-${ statusFilter.value[0] }` : `torrent-${ statusFilter.value[0] }-darken-2`)
const statusFilterColor = computed(() => isStatusFilterActive.value ? 'active-status' : 'active-status-disabled')
const categoryFilterColor = computed(() => isCategoryFilterActive.value ? 'active-category' : 'active-category-disabled')
const tagFilterColor = computed(() => isTagFilterActive.value ? 'active-tag' : 'active-tag-disabled')
const trackerFilterColor = computed(() => isTrackerFilterActive.value ? 'active-tracker' : 'active-tracker-disabled')
const globalFilterColor = computed(() => (globalFilterActive.value ? 'active-global' : 'active-global-disabled'))
const textFilterColor = computed(() => (isTextFilterActive.value ? 'active-text' : 'active-text-disabled'))
const singleStatusFilterColor = computed(() => (isStatusFilterActive.value ? `torrent-${statusFilter.value[0]}` : `torrent-${statusFilter.value[0]}-darken-2`))
const statusFilterColor = computed(() => (isStatusFilterActive.value ? 'active-status' : 'active-status-disabled'))
const categoryFilterColor = computed(() => (isCategoryFilterActive.value ? 'active-category' : 'active-category-disabled'))
const tagFilterColor = computed(() => (isTagFilterActive.value ? 'active-tag' : 'active-tag-disabled'))
const trackerFilterColor = computed(() => (isTrackerFilterActive.value ? 'active-tracker' : 'active-tracker-disabled'))
const filterPresentCount = computed(
() =>
@ -128,13 +128,9 @@ function resetTrackerFilter() {
<v-menu close-delay="0" open-delay="0" open-on-click open-on-hover open-on-focus>
<template v-slot:activator="{ props }">
<v-slide-x-transition>
<v-chip v-if="filterPresentCount > 0" v-bind="props" class="ml-6" :color="globalFilterColor" variant="elevated"
closable @click:close="resetAllFilters()">
<v-chip v-if="filterPresentCount > 0" v-bind="props" class="ml-6" :color="globalFilterColor" variant="elevated" closable @click:close="resetAllFilters()">
<template v-slot:prepend>
<v-icon class="mr-1" @click="toggleAllFilters()">{{
globalFilterActive ? 'mdi-filter' : 'mdi-filter-off'
}}
</v-icon>
<v-icon class="mr-1" @click="toggleAllFilters()">{{ globalFilterActive ? 'mdi-filter' : 'mdi-filter-off' }} </v-icon>
</template>
{{ t('navbar.top.active_filters.menu_label', filterActiveCount) }}
</v-chip>
@ -142,54 +138,38 @@ function resetTrackerFilter() {
</template>
<div class="d-flex flex-column gap mt-3">
<v-chip v-if="isTextFilterPresent" :color="textFilterColor" variant="elevated"
closable @click:close="resetTextFilter()">
<v-chip v-if="isTextFilterPresent" :color="textFilterColor" variant="elevated" closable @click:close="resetTextFilter()">
<template v-slot:prepend>
<v-icon class="mr-1" @click="toggleTextFilter()">{{
isTextFilterActive ? 'mdi-filter' : 'mdi-filter-off'
}}
</v-icon>
<v-icon class="mr-1" @click="toggleTextFilter()">{{ isTextFilterActive ? 'mdi-filter' : 'mdi-filter-off' }} </v-icon>
</template>
{{ t('navbar.top.active_filters.text', { value: textFilter }) }}
</v-chip>
<template v-if="isStatusFilterPresent">
<v-chip v-if="statusFilter.length === 1" :color="singleStatusFilterColor" variant="elevated"
closable @click:close="resetStatusFilter()">
<v-chip v-if="statusFilter.length === 1" :color="singleStatusFilterColor" variant="elevated" closable @click:close="resetStatusFilter()">
<template v-slot:prepend>
<v-icon class="mr-1" @click="toggleStatusFilter()">{{
isStatusFilterActive ? 'mdi-filter' : 'mdi-filter-off'
}}
</v-icon>
<v-icon class="mr-1" @click="toggleStatusFilter()">{{ isStatusFilterActive ? 'mdi-filter' : 'mdi-filter-off' }} </v-icon>
</template>
{{ t('navbar.top.active_filters.state', { value: t(`torrent.state.${ statusFilter[0] }`) }) }}
{{ t('navbar.top.active_filters.state', { value: t(`torrent.state.${statusFilter[0]}`) }) }}
</v-chip>
<v-chip v-else :color="statusFilterColor" variant="elevated"
closable @click:close="resetStatusFilter()">
<v-chip v-else :color="statusFilterColor" variant="elevated" closable @click:close="resetStatusFilter()">
<template v-slot:prepend>
<v-icon class="mr-1" @click="toggleStatusFilter()">{{
isStatusFilterActive ? 'mdi-filter' : 'mdi-filter-off'
}}
</v-icon>
<v-icon class="mr-1" @click="toggleStatusFilter()">{{ isStatusFilterActive ? 'mdi-filter' : 'mdi-filter-off' }} </v-icon>
</template>
{{ t('navbar.top.active_filters.multiple_state', statusFilter.length) }}
</v-chip>
</template>
<template v-if="isCategoryFilterPresent">
<v-chip v-if="categoryFilter.length === 1" :color="categoryFilterColor" variant="elevated"
closable @click:close="resetCategoryFilter()">
<v-chip v-if="categoryFilter.length === 1" :color="categoryFilterColor" variant="elevated" closable @click:close="resetCategoryFilter()">
<template v-slot:prepend>
<v-icon class="mr-1" @click="toggleCategoryFilter()">
{{ isCategoryFilterActive ? 'mdi-filter' : 'mdi-filter-off' }}
</v-icon>
</template>
{{
t('navbar.top.active_filters.category', { value: categoryFilter[0] === '' ? t('navbar.side.filters.uncategorized') : categoryFilter[0] })
}}
{{ t('navbar.top.active_filters.category', { value: categoryFilter[0] === '' ? t('navbar.side.filters.uncategorized') : categoryFilter[0] }) }}
</v-chip>
<v-chip v-else :color="categoryFilterColor" variant="elevated"
closable @click:close="resetCategoryFilter()">
<v-chip v-else :color="categoryFilterColor" variant="elevated" closable @click:close="resetCategoryFilter()">
<template v-slot:prepend>
<v-icon class="mr-1" @click="toggleCategoryFilter()">
{{ isCategoryFilterActive ? 'mdi-filter' : 'mdi-filter-off' }}
@ -200,44 +180,30 @@ function resetTrackerFilter() {
</template>
<template v-if="isTagFilterPresent">
<v-chip v-if="tagFilter.length === 1" :color="tagFilterColor" variant="elevated"
closable @click:close="resetTagFilter()">
<v-chip v-if="tagFilter.length === 1" :color="tagFilterColor" variant="elevated" closable @click:close="resetTagFilter()">
<template v-slot:prepend>
<v-icon class="mr-1" @click="toggleTagFilter()">{{
isTagFilterActive ? 'mdi-filter' : 'mdi-filter-off'
}}
</v-icon>
<v-icon class="mr-1" @click="toggleTagFilter()">{{ isTagFilterActive ? 'mdi-filter' : 'mdi-filter-off' }} </v-icon>
</template>
{{
t('navbar.top.active_filters.tag', { value: tagFilter[0] === null ? t('navbar.side.filters.untagged') : tagFilter[0] })
}}
{{ t('navbar.top.active_filters.tag', { value: tagFilter[0] === null ? t('navbar.side.filters.untagged') : tagFilter[0] }) }}
</v-chip>
<v-chip v-else :color="tagFilterColor" variant="elevated"
closable @click:close="resetTagFilter()">
<v-chip v-else :color="tagFilterColor" variant="elevated" closable @click:close="resetTagFilter()">
<template v-slot:prepend>
<v-icon class="mr-1" @click="toggleTagFilter()">{{
isTagFilterActive ? 'mdi-filter' : 'mdi-filter-off'
}}
</v-icon>
<v-icon class="mr-1" @click="toggleTagFilter()">{{ isTagFilterActive ? 'mdi-filter' : 'mdi-filter-off' }} </v-icon>
</template>
{{ t('navbar.top.active_filters.multiple_tag', tagFilter.length) }}
</v-chip>
</template>
<template v-if="isTrackerFilterPresent">
<v-chip v-if="trackerFilter.length === 1" :color="trackerFilterColor" variant="elevated"
closable @click:close="resetTrackerFilter()">
<v-chip v-if="trackerFilter.length === 1" :color="trackerFilterColor" variant="elevated" closable @click:close="resetTrackerFilter()">
<template v-slot:prepend>
<v-icon class="mr-1" @click="toggleTrackerFilter()">
{{ isTrackerFilterActive ? 'mdi-filter' : 'mdi-filter-off' }}
</v-icon>
</template>
{{
t('navbar.top.active_filters.tracker', { value: trackerFilter[0] === '' ? t('navbar.side.filters.untracked') : trackerFilter[0] })
}}
{{ t('navbar.top.active_filters.tracker', { value: trackerFilter[0] === '' ? t('navbar.side.filters.untracked') : trackerFilter[0] }) }}
</v-chip>
<v-chip v-else :color="trackerFilterColor" variant="elevated"
closable @click:close="resetTrackerFilter()">
<v-chip v-else :color="trackerFilterColor" variant="elevated" closable @click:close="resetTrackerFilter()">
<template v-slot:prepend>
<v-icon class="mr-1" @click="toggleTrackerFilter()">
{{ isTrackerFilterActive ? 'mdi-filter' : 'mdi-filter-off' }}

View file

@ -1,12 +1,5 @@
<script setup lang="ts">
import {
DiskIOMode,
DiskIOType,
ResumeDataStorageType,
UploadChokingAlgorithm,
UploadSlotsBehavior,
UtpTcpMixedMode
} from '@/constants/qbit/AppPreferences'
import { DiskIOMode, DiskIOType, ResumeDataStorageType, UploadChokingAlgorithm, UploadSlotsBehavior, UtpTcpMixedMode } from '@/constants/qbit/AppPreferences'
import { qbit } from '@/services'
import { usePreferenceStore } from '@/stores'
import { computed, onBeforeMount, ref } from 'vue'
@ -19,10 +12,12 @@ const resumeDataStorageTypeOptions = [
{ title: t('settings.advanced.qbittorrent.resumeDataStorageType.legacy'), value: ResumeDataStorageType.LEGACY },
{ title: t('settings.advanced.qbittorrent.resumeDataStorageType.sqlite'), value: ResumeDataStorageType.SQLITE }
]
const networkInterfaceOptions = ref([{
title: t('settings.advanced.qbittorrent.networking.networkInterfaces.any'),
value: ''
}])
const networkInterfaceOptions = ref([
{
title: t('settings.advanced.qbittorrent.networking.networkInterfaces.any'),
value: ''
}
])
const ipAddressesOptions = ref([
{ title: t('settings.advanced.qbittorrent.networking.ipAddress.all'), value: '' },
{ title: t('settings.advanced.qbittorrent.networking.ipAddress.allIPv4'), value: '0.0.0.0' },
@ -80,18 +75,20 @@ onBeforeMount(async () => {
<v-list>
<v-list-subheader>
{{ t('settings.advanced.qbittorrent.subheader') }} (<a
href="https://github.com/qbittorrent/qBittorrent/wiki/Explanation-of-Options-in-qBittorrent#Advanced"
target="_blank"
>{{ t('settings.advanced.openDoc') }}</a
>)
href="https://github.com/qbittorrent/qBittorrent/wiki/Explanation-of-Options-in-qBittorrent#Advanced"
target="_blank"
>{{ t('settings.advanced.openDoc') }}</a
>)
</v-list-subheader>
<v-list-item>
<v-row>
<v-col cols="12" sm="6">
<v-select v-model="preferenceStore.preferences!.resume_data_storage_type" hide-details
:items="resumeDataStorageTypeOptions"
:label="$t('settings.advanced.qbittorrent.resumeDataStorageType.label')" />
<v-select
v-model="preferenceStore.preferences!.resume_data_storage_type"
hide-details
:items="resumeDataStorageTypeOptions"
:label="$t('settings.advanced.qbittorrent.resumeDataStorageType.label')" />
</v-col>
<v-col cols="12" sm="6">
<v-text-field
@ -111,16 +108,11 @@ onBeforeMount(async () => {
:label="t('settings.advanced.qbittorrent.allocatedRam')" />
</v-col>
<v-col cols="12" sm="6">
<v-text-field v-model="torrentFileSizeLimit"
type="number"
hide-details
suffix="MiB"
:label="$t('settings.advanced.qbittorrent.torrentFileSizeLimit')" />
<v-text-field v-model="torrentFileSizeLimit" type="number" hide-details suffix="MiB" :label="$t('settings.advanced.qbittorrent.torrentFileSizeLimit')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="preferenceStore.preferences!.recheck_completed_torrents" hide-details
:label="t('settings.advanced.qbittorrent.recheckOnCompletion')" />
<v-checkbox v-model="preferenceStore.preferences!.recheck_completed_torrents" hide-details :label="t('settings.advanced.qbittorrent.recheckOnCompletion')" />
</v-col>
<v-col cols="12" sm="6">
<v-text-field
@ -132,12 +124,10 @@ onBeforeMount(async () => {
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="preferenceStore.preferences!.resolve_peer_countries" hide-details
:label="t('settings.advanced.qbittorrent.resolveCountries')" />
<v-checkbox v-model="preferenceStore.preferences!.resolve_peer_countries" hide-details :label="t('settings.advanced.qbittorrent.resolveCountries')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="preferenceStore.preferences!.reannounce_when_address_changed" hide-details
:label="t('settings.advanced.qbittorrent.reannounceOnIpPortChanged')" />
<v-checkbox v-model="preferenceStore.preferences!.reannounce_when_address_changed" hide-details :label="t('settings.advanced.qbittorrent.reannounceOnIpPortChanged')" />
</v-col>
</v-row>
</v-list-item>
@ -170,8 +160,7 @@ onBeforeMount(async () => {
<v-list-item>
<v-row>
<v-col cols="12" class="py-0">
<v-checkbox v-model="preferenceStore.preferences!.enable_embedded_tracker" hide-details
:label="t('settings.advanced.qbittorrent.embeddedTracker.enable')" />
<v-checkbox v-model="preferenceStore.preferences!.enable_embedded_tracker" hide-details :label="t('settings.advanced.qbittorrent.embeddedTracker.enable')" />
</v-col>
<v-col cols="12" class="py-0">
<v-text-field
@ -205,17 +194,14 @@ onBeforeMount(async () => {
<v-list-item>
<v-row>
<v-col cols="12" sm="6">
<v-text-field v-model="preferenceStore.preferences!.async_io_threads" type="number" hide-details
:label="t('settings.advanced.libtorrent.threads.asyncIoThreads')" />
<v-text-field v-model="preferenceStore.preferences!.async_io_threads" type="number" hide-details :label="t('settings.advanced.libtorrent.threads.asyncIoThreads')" />
</v-col>
<v-col cols="12" sm="6">
<v-text-field v-model="preferenceStore.preferences!.hashing_threads" type="number" hide-details
:label="t('settings.advanced.libtorrent.threads.hashingThreads')" />
<v-text-field v-model="preferenceStore.preferences!.hashing_threads" type="number" hide-details :label="t('settings.advanced.libtorrent.threads.hashingThreads')" />
</v-col>
<v-col cols="12" sm="6">
<v-text-field v-model="preferenceStore.preferences!.file_pool_size" type="number" hide-details
:label="t('settings.advanced.libtorrent.threads.filePoolSize')" />
<v-text-field v-model="preferenceStore.preferences!.file_pool_size" type="number" hide-details :label="t('settings.advanced.libtorrent.threads.filePoolSize')" />
</v-col>
<v-col cols="12" sm="6">
<v-text-field
@ -234,8 +220,7 @@ onBeforeMount(async () => {
<v-list-item>
<v-row>
<v-col cols="12" sm="6">
<v-text-field v-model="preferenceStore.preferences!.disk_cache" type="number" hide-details suffix="MiB"
:label="t('settings.advanced.libtorrent.disk.diskCache')" />
<v-text-field v-model="preferenceStore.preferences!.disk_cache" type="number" hide-details suffix="MiB" :label="t('settings.advanced.libtorrent.disk.diskCache')" />
</v-col>
<v-col cols="12" sm="6">
<v-text-field
@ -256,8 +241,7 @@ onBeforeMount(async () => {
</v-col>
<v-col cols="12" sm="4">
<v-select v-model="preferenceStore.preferences!.disk_io_type" hide-details :items="diskIoTypeOptions"
:label="t('settings.advanced.libtorrent.disk.diskIoType')" />
<v-select v-model="preferenceStore.preferences!.disk_io_type" hide-details :items="diskIoTypeOptions" :label="t('settings.advanced.libtorrent.disk.diskIoType')" />
</v-col>
<v-col cols="12" sm="4">
<v-select
@ -281,25 +265,28 @@ onBeforeMount(async () => {
<v-list-item>
<v-row>
<v-col cols="12" sm="6">
<v-text-field v-model="preferenceStore.preferences!.bdecode_depth_limit" type="number" hide-details
:label="t('settings.advanced.libtorrent.threads.bdecodeDepthLimit')" />
<v-text-field
v-model="preferenceStore.preferences!.bdecode_depth_limit"
type="number"
hide-details
:label="t('settings.advanced.libtorrent.threads.bdecodeDepthLimit')" />
</v-col>
<v-col cols="12" sm="6">
<v-text-field v-model="preferenceStore.preferences!.bdecode_token_limit" type="number" hide-details
:label="t('settings.advanced.libtorrent.threads.bdecodeTokenLimit')" />
<v-text-field
v-model="preferenceStore.preferences!.bdecode_token_limit"
type="number"
hide-details
:label="t('settings.advanced.libtorrent.threads.bdecodeTokenLimit')" />
</v-col>
<v-col cols="12" sm="4">
<v-checkbox v-model="preferenceStore.preferences!.enable_coalesce_read_write" hide-details
:label="t('settings.advanced.libtorrent.coalesceReadsWrites')" />
<v-checkbox v-model="preferenceStore.preferences!.enable_coalesce_read_write" hide-details :label="t('settings.advanced.libtorrent.coalesceReadsWrites')" />
</v-col>
<v-col cols="12" sm="4">
<v-checkbox v-model="preferenceStore.preferences!.enable_piece_extent_affinity" hide-details
:label="t('settings.advanced.libtorrent.pieceExtentAffinity')" />
<v-checkbox v-model="preferenceStore.preferences!.enable_piece_extent_affinity" hide-details :label="t('settings.advanced.libtorrent.pieceExtentAffinity')" />
</v-col>
<v-col cols="12" sm="4">
<v-checkbox v-model="preferenceStore.preferences!.enable_upload_suggestions" hide-details
:label="t('settings.advanced.libtorrent.sendUploadPieceSuggestions')" />
<v-checkbox v-model="preferenceStore.preferences!.enable_upload_suggestions" hide-details :label="t('settings.advanced.libtorrent.sendUploadPieceSuggestions')" />
</v-col>
<v-col cols="12" sm="4">
@ -336,20 +323,23 @@ onBeforeMount(async () => {
</v-col>
<v-col cols="12" sm="4">
<v-text-field v-model="preferenceStore.preferences!.socket_send_buffer_size" type="number"
:label="t('settings.advanced.libtorrent.socketSendBufferSize')"
:hint="$t('settings.advanced.libtorrent.socketSendBufferSizeHint')"
suffix="kiB" />
<v-text-field
v-model="preferenceStore.preferences!.socket_send_buffer_size"
type="number"
:label="t('settings.advanced.libtorrent.socketSendBufferSize')"
:hint="$t('settings.advanced.libtorrent.socketSendBufferSizeHint')"
suffix="kiB" />
</v-col>
<v-col cols="12" sm="4">
<v-text-field v-model="preferenceStore.preferences!.socket_receive_buffer_size" type="number"
:label="t('settings.advanced.libtorrent.socketReceiveBufferSize')"
:hint="$t('settings.advanced.libtorrent.socketReceiveBufferSizeHint')"
suffix="kiB" />
<v-text-field
v-model="preferenceStore.preferences!.socket_receive_buffer_size"
type="number"
:label="t('settings.advanced.libtorrent.socketReceiveBufferSize')"
:hint="$t('settings.advanced.libtorrent.socketReceiveBufferSizeHint')"
suffix="kiB" />
</v-col>
<v-col cols="12" sm="4">
<v-text-field v-model="preferenceStore.preferences!.socket_backlog_size" type="number" hide-details
:label="t('settings.advanced.libtorrent.socketBacklogSize')" />
<v-text-field v-model="preferenceStore.preferences!.socket_backlog_size" type="number" hide-details :label="t('settings.advanced.libtorrent.socketBacklogSize')" />
</v-col>
</v-row>
</v-list-item>
@ -382,8 +372,7 @@ onBeforeMount(async () => {
:label="t('settings.advanced.libtorrent.networking.upnpLeaseDuration')" />
</v-col>
<v-col cols="12" sm="6">
<v-text-field v-model="preferenceStore.preferences!.peer_tos" type="number" hide-details
:label="t('settings.advanced.libtorrent.networking.peerTos')" />
<v-text-field v-model="preferenceStore.preferences!.peer_tos" type="number" hide-details :label="t('settings.advanced.libtorrent.networking.peerTos')" />
</v-col>
<v-col cols="12">
@ -402,8 +391,7 @@ onBeforeMount(async () => {
<v-list-item>
<v-row>
<v-col cols="12" sm="6">
<v-checkbox v-model="preferenceStore.preferences!.idn_support_enabled" hide-details
:label="t('settings.advanced.libtorrent.security.idnSupport')" />
<v-checkbox v-model="preferenceStore.preferences!.idn_support_enabled" hide-details :label="t('settings.advanced.libtorrent.security.idnSupport')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox
@ -419,8 +407,7 @@ onBeforeMount(async () => {
:label="t('settings.advanced.libtorrent.security.validateHTTPSTrackerCertificate')" />
</v-col>
<v-col cols="12" sm="4">
<v-checkbox v-model="preferenceStore.preferences!.ssrf_mitigation" hide-details
:label="t('settings.advanced.libtorrent.security.mitigateSSRF')" />
<v-checkbox v-model="preferenceStore.preferences!.ssrf_mitigation" hide-details :label="t('settings.advanced.libtorrent.security.mitigateSSRF')" />
</v-col>
<v-col cols="12" sm="4">
<v-checkbox
@ -451,17 +438,14 @@ onBeforeMount(async () => {
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="preferenceStore.preferences!.announce_to_all_trackers" hide-details
:label="t('settings.advanced.libtorrent.announceAllTrackers')" />
<v-checkbox v-model="preferenceStore.preferences!.announce_to_all_trackers" hide-details :label="t('settings.advanced.libtorrent.announceAllTrackers')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="preferenceStore.preferences!.announce_to_all_tiers" hide-details
:label="t('settings.advanced.libtorrent.announceAllTiers')" />
<v-checkbox v-model="preferenceStore.preferences!.announce_to_all_tiers" hide-details :label="t('settings.advanced.libtorrent.announceAllTiers')" />
</v-col>
<v-col cols="12">
<v-text-field v-model="preferenceStore.preferences!.announce_ip" type="number" hide-details
:label="t('settings.advanced.libtorrent.announceIP')" />
<v-text-field v-model="preferenceStore.preferences!.announce_ip" type="number" hide-details :label="t('settings.advanced.libtorrent.announceIP')" />
</v-col>
<v-col cols="12" sm="6">
@ -472,13 +456,11 @@ onBeforeMount(async () => {
:label="t('settings.advanced.libtorrent.maxConcurrentHTTPAnnounces')" />
</v-col>
<v-col cols="12" sm="6">
<v-text-field v-model="preferenceStore.preferences!.stop_tracker_timeout" type="number" hide-details
:label="t('settings.advanced.libtorrent.stopTrackerTimeout')" />
<v-text-field v-model="preferenceStore.preferences!.stop_tracker_timeout" type="number" hide-details :label="t('settings.advanced.libtorrent.stopTrackerTimeout')" />
</v-col>
<v-col cols="12" sm="4">
<v-text-field v-model="preferenceStore.preferences!.peer_turnover" type="number" hide-details suffix="%"
:label="t('settings.advanced.libtorrent.peerTurnover')" />
<v-text-field v-model="preferenceStore.preferences!.peer_turnover" type="number" hide-details suffix="%" :label="t('settings.advanced.libtorrent.peerTurnover')" />
</v-col>
<v-col cols="12" sm="4">
<v-text-field
@ -498,8 +480,7 @@ onBeforeMount(async () => {
</v-col>
<v-col cols="12">
<v-text-field v-model="preferenceStore.preferences!.request_queue_size" type="number" hide-details
:label="t('settings.advanced.libtorrent.requestQueueSize')" />
<v-text-field v-model="preferenceStore.preferences!.request_queue_size" type="number" hide-details :label="t('settings.advanced.libtorrent.requestQueueSize')" />
</v-col>
</v-row>
</v-list-item>

View file

@ -20,13 +20,14 @@ const fileLogAgeTypeOptions = [
<v-list-item>
<v-row>
<v-col cols="12" sm="6">
<v-checkbox v-model="preferenceStore.preferences!.file_log_enabled" hide-details
:label="$t('settings.behavior.logs.file_log_enabled')" />
<v-checkbox v-model="preferenceStore.preferences!.file_log_enabled" hide-details :label="$t('settings.behavior.logs.file_log_enabled')" />
</v-col>
<v-col cols="12" sm="6">
<v-text-field v-model="preferenceStore.preferences!.file_log_path"
:disabled="!preferenceStore.preferences!.file_log_enabled"
hide-details :label="$t('settings.behavior.logs.file_log_path')" />
<v-text-field
v-model="preferenceStore.preferences!.file_log_path"
:disabled="!preferenceStore.preferences!.file_log_enabled"
hide-details
:label="$t('settings.behavior.logs.file_log_path')" />
</v-col>
</v-row>
</v-list-item>
@ -36,36 +37,42 @@ const fileLogAgeTypeOptions = [
<v-list-item>
<v-row>
<v-col cols="12" sm="6">
<v-checkbox v-model="preferenceStore.preferences!.file_log_backup_enabled"
:disabled="!preferenceStore.preferences!.file_log_enabled"
hide-details
:label="$t('settings.behavior.logs.file_log_backup_enabled')" />
<v-checkbox
v-model="preferenceStore.preferences!.file_log_backup_enabled"
:disabled="!preferenceStore.preferences!.file_log_enabled"
hide-details
:label="$t('settings.behavior.logs.file_log_backup_enabled')" />
</v-col>
<v-col cols="12" sm="6">
<v-text-field v-model="preferenceStore.preferences!.file_log_max_size"
:disabled="!preferenceStore.preferences!.file_log_enabled || !preferenceStore.preferences!.file_log_backup_enabled"
type="number"
hide-details
:label="$t('settings.behavior.logs.file_log_max_size')"
suffix="kiB" />
<v-text-field
v-model="preferenceStore.preferences!.file_log_max_size"
:disabled="!preferenceStore.preferences!.file_log_enabled || !preferenceStore.preferences!.file_log_backup_enabled"
type="number"
hide-details
:label="$t('settings.behavior.logs.file_log_max_size')"
suffix="kiB" />
</v-col>
<v-col cols="6">
<v-checkbox v-model="preferenceStore.preferences!.file_log_delete_old"
:disabled="!preferenceStore.preferences!.file_log_enabled"
hide-details
:label="$t('settings.behavior.logs.file_log_delete_old')" />
<v-checkbox
v-model="preferenceStore.preferences!.file_log_delete_old"
:disabled="!preferenceStore.preferences!.file_log_enabled"
hide-details
:label="$t('settings.behavior.logs.file_log_delete_old')" />
</v-col>
<v-col cols="3">
<v-text-field v-model="preferenceStore.preferences!.file_log_age"
:disabled="!preferenceStore.preferences!.file_log_enabled || !preferenceStore.preferences!.file_log_delete_old"
hide-details :label="$t('settings.behavior.logs.file_log_age')" />
<v-text-field
v-model="preferenceStore.preferences!.file_log_age"
:disabled="!preferenceStore.preferences!.file_log_enabled || !preferenceStore.preferences!.file_log_delete_old"
hide-details
:label="$t('settings.behavior.logs.file_log_age')" />
</v-col>
<v-col cols="3">
<v-select v-model="preferenceStore.preferences!.file_log_age_type"
:disabled="!preferenceStore.preferences!.file_log_enabled || !preferenceStore.preferences!.file_log_delete_old"
:items="fileLogAgeTypeOptions"
hide-details />
<v-select
v-model="preferenceStore.preferences!.file_log_age_type"
:disabled="!preferenceStore.preferences!.file_log_enabled || !preferenceStore.preferences!.file_log_delete_old"
:items="fileLogAgeTypeOptions"
hide-details />
</v-col>
</v-row>
</v-list-item>
@ -73,12 +80,9 @@ const fileLogAgeTypeOptions = [
<v-divider class="mt-3" />
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.performance_warning" hide-details
:label="$t('settings.behavior.performance_warning')" />
<v-checkbox v-model="preferenceStore.preferences!.performance_warning" hide-details :label="$t('settings.behavior.performance_warning')" />
</v-list-item>
</v-list>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -25,28 +25,23 @@ const thenTypes = ref([
<v-list-subheader>{{ t('settings.bittorrent.privacy.subheader') }}</v-list-subheader>
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.dht" hide-details
:label="t('settings.bittorrent.privacy.enableDHT')" />
<v-checkbox v-model="preferenceStore.preferences!.dht" hide-details :label="t('settings.bittorrent.privacy.enableDHT')" />
</v-list-item>
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.pex" hide-details
:label="t('settings.bittorrent.privacy.enablePeX')" />
<v-checkbox v-model="preferenceStore.preferences!.pex" hide-details :label="t('settings.bittorrent.privacy.enablePeX')" />
</v-list-item>
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.lsd" hide-details
:label="t('settings.bittorrent.privacy.enableLPD')" />
<v-checkbox v-model="preferenceStore.preferences!.lsd" hide-details :label="t('settings.bittorrent.privacy.enableLPD')" />
</v-list-item>
<v-list-item>
<v-select v-model="preferenceStore.preferences!.encryption" hide-details :items="encyptionModeOptions"
:label="t('settings.bittorrent.privacy.encryptionMode')" />
<v-select v-model="preferenceStore.preferences!.encryption" hide-details :items="encyptionModeOptions" :label="t('settings.bittorrent.privacy.encryptionMode')" />
</v-list-item>
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.anonymous_mode" hide-details
:label="t('settings.bittorrent.privacy.enableAnonymous')" />
<v-checkbox v-model="preferenceStore.preferences!.anonymous_mode" hide-details :label="t('settings.bittorrent.privacy.enableAnonymous')" />
<a href="https://github.com/qbittorrent/qBittorrent/wiki/Anonymous-Mode" target="_blank">
{{ t('settings.bittorrent.privacy.moreInfo') }}
</a>
@ -55,8 +50,7 @@ const thenTypes = ref([
<v-divider />
<v-list-item class="my-3">
<v-text-field v-model="preferenceStore.preferences!.max_active_checking_torrents" type="number" hide-details
:label="t('settings.bittorrent.maxActiveCheckingTorrents')" />
<v-text-field v-model="preferenceStore.preferences!.max_active_checking_torrents" type="number" hide-details :label="t('settings.bittorrent.maxActiveCheckingTorrents')" />
</v-list-item>
<v-divider />
@ -64,8 +58,7 @@ const thenTypes = ref([
<v-list-item>
<v-row>
<v-col cols="12" class="pb-0">
<v-checkbox v-model="preferenceStore.preferences!.queueing_enabled" hide-details
:label="t('settings.bittorrent.torrentQueueing.subheader')" />
<v-checkbox v-model="preferenceStore.preferences!.queueing_enabled" hide-details :label="t('settings.bittorrent.torrentQueueing.subheader')" />
</v-col>
<v-col cols="12" sm="6" md="4">
@ -141,12 +134,10 @@ const thenTypes = ref([
<v-list-item>
<v-row>
<v-col cols="6">
<v-checkbox v-model="preferenceStore.preferences!.max_ratio_enabled" hide-details
:label="t('settings.bittorrent.seedLimits.whenRatioReaches')" />
<v-checkbox v-model="preferenceStore.preferences!.max_ratio_enabled" hide-details :label="t('settings.bittorrent.seedLimits.whenRatioReaches')" />
</v-col>
<v-col cols="6">
<v-text-field v-model="preferenceStore.preferences!.max_ratio"
:disabled="!preferenceStore.preferences!.max_ratio_enabled" type="number" hide-details />
<v-text-field v-model="preferenceStore.preferences!.max_ratio" :disabled="!preferenceStore.preferences!.max_ratio_enabled" type="number" hide-details />
</v-col>
</v-row>
</v-list-item>
@ -154,8 +145,7 @@ const thenTypes = ref([
<v-list-item>
<v-row>
<v-col cols="6">
<v-checkbox v-model="preferenceStore.preferences!.max_seeding_time_enabled" hide-details
:label="t('settings.bittorrent.seedLimits.whenSeedingTimeReaches')" />
<v-checkbox v-model="preferenceStore.preferences!.max_seeding_time_enabled" hide-details :label="t('settings.bittorrent.seedLimits.whenSeedingTimeReaches')" />
</v-col>
<v-col cols="6">
<v-text-field
@ -171,8 +161,10 @@ const thenTypes = ref([
<v-list-item>
<v-row>
<v-col cols="6">
<v-checkbox v-model="preferenceStore.preferences!.max_inactive_seeding_time_enabled" hide-details
:label="t('settings.bittorrent.seedLimits.whenInactiveSeedingTimeReaches')" />
<v-checkbox
v-model="preferenceStore.preferences!.max_inactive_seeding_time_enabled"
hide-details
:label="t('settings.bittorrent.seedLimits.whenInactiveSeedingTimeReaches')" />
</v-col>
<v-col cols="6">
<v-text-field
@ -192,7 +184,11 @@ const thenTypes = ref([
<v-col>
<v-select
v-model="preferenceStore.preferences!.max_ratio_act"
:disabled="!preferenceStore.preferences!.max_ratio_enabled && !preferenceStore.preferences!.max_seeding_time_enabled && !preferenceStore.preferences!.max_inactive_seeding_time_enabled"
:disabled="
!preferenceStore.preferences!.max_ratio_enabled &&
!preferenceStore.preferences!.max_seeding_time_enabled &&
!preferenceStore.preferences!.max_inactive_seeding_time_enabled
"
hide-details
:items="thenTypes" />
</v-col>
@ -202,8 +198,7 @@ const thenTypes = ref([
<v-divider class="mt-3" />
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.add_trackers_enabled" hide-details
:label="t('settings.bittorrent.autoAddTrackers')" />
<v-checkbox v-model="preferenceStore.preferences!.add_trackers_enabled" hide-details :label="t('settings.bittorrent.autoAddTrackers')" />
</v-list-item>
<v-list-item>
<v-textarea

View file

@ -70,8 +70,7 @@ watch(
<template>
<v-list>
<v-list-item>
<v-select v-model="preferenceStore.preferences!.bittorrent_protocol" hide-details :items="bittorrent_protocol"
:label="t('settings.connection.protocol')" />
<v-select v-model="preferenceStore.preferences!.bittorrent_protocol" hide-details :items="bittorrent_protocol" :label="t('settings.connection.protocol')" />
</v-list-item>
<v-divider class="mt-3" />
@ -80,21 +79,16 @@ watch(
<v-list-item>
<v-row>
<v-col cols="12" sm="6">
<v-text-field v-model="preferenceStore.preferences!.listen_port" type="number" hide-details
:label="t('settings.connection.listeningPort.incomingConnectionPort')" />
<v-text-field v-model="preferenceStore.preferences!.listen_port" type="number" hide-details :label="t('settings.connection.listeningPort.incomingConnectionPort')" />
</v-col>
<v-col cols="12" sm="6" class="d-flex align-center justify-center">
<v-btn color="primary" @click="generateRandomPort">{{
t('settings.connection.listeningPort.randomPort')
}}
</v-btn>
<v-btn color="primary" @click="generateRandomPort">{{ t('settings.connection.listeningPort.randomPort') }} </v-btn>
</v-col>
</v-row>
</v-list-item>
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.upnp" hide-details
:label="t('settings.connection.listeningPort.useUPnP')" />
<v-checkbox v-model="preferenceStore.preferences!.upnp" hide-details :label="t('settings.connection.listeningPort.useUPnP')" />
</v-list-item>
<v-divider />
@ -158,19 +152,10 @@ watch(
<v-select v-model="preferenceStore.preferences!.proxy_type" hide-details :items="proxyTypes" />
</v-col>
<v-col cols="6" md="4">
<v-text-field
v-model="preferenceStore.preferences!.proxy_ip"
:disabled="isProxyDisabled"
hide-details
:label="t('settings.connection.proxy.host')" />
<v-text-field v-model="preferenceStore.preferences!.proxy_ip" :disabled="isProxyDisabled" hide-details :label="t('settings.connection.proxy.host')" />
</v-col>
<v-col cols="6" md="4">
<v-text-field
v-model="preferenceStore.preferences!.proxy_port"
:disabled="isProxyDisabled"
type="number"
hide-details
:label="t('settings.connection.proxy.port')" />
<v-text-field v-model="preferenceStore.preferences!.proxy_port" :disabled="isProxyDisabled" type="number" hide-details :label="t('settings.connection.proxy.port')" />
</v-col>
</v-row>
</v-list-item>
@ -178,11 +163,7 @@ watch(
<v-list-item>
<v-row no-gutters>
<v-col cols="12" sm="6" md="3">
<v-checkbox
v-model="preferenceStore.preferences!.proxy_bittorrent"
:disabled="isProxyDisabled"
hide-details
:label="t('settings.connection.proxy.bittorrent')" />
<v-checkbox v-model="preferenceStore.preferences!.proxy_bittorrent" :disabled="isProxyDisabled" hide-details :label="t('settings.connection.proxy.bittorrent')" />
</v-col>
<v-col cols="12" sm="6" md="3">
<v-checkbox
@ -192,18 +173,10 @@ watch(
:label="t('settings.connection.proxy.peerConnections')" />
</v-col>
<v-col cols="12" sm="6" md="3">
<v-checkbox
v-model="preferenceStore.preferences!.proxy_rss"
:disabled="isProxyDisabled || isProxySocks4"
hide-details
:label="t('settings.connection.proxy.rss')" />
<v-checkbox v-model="preferenceStore.preferences!.proxy_rss" :disabled="isProxyDisabled || isProxySocks4" hide-details :label="t('settings.connection.proxy.rss')" />
</v-col>
<v-col cols="12" sm="6" md="3">
<v-checkbox
v-model="preferenceStore.preferences!.proxy_misc"
:disabled="isProxyDisabled || isProxySocks4"
hide-details
:label="t('settings.connection.proxy.misc')" />
<v-checkbox v-model="preferenceStore.preferences!.proxy_misc" :disabled="isProxyDisabled || isProxySocks4" hide-details :label="t('settings.connection.proxy.misc')" />
</v-col>
</v-row>
</v-list-item>
@ -251,15 +224,12 @@ watch(
<v-list-subheader>{{ t('settings.connection.ipFiltering.subheader') }}</v-list-subheader>
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.ip_filter_enabled" hide-details
:label="t('settings.connection.ipFiltering.filterPath')" />
<v-text-field v-model="preferenceStore.preferences!.ip_filter_path"
:disabled="!preferenceStore.preferences!.ip_filter_enabled" hide-details />
<v-checkbox v-model="preferenceStore.preferences!.ip_filter_enabled" hide-details :label="t('settings.connection.ipFiltering.filterPath')" />
<v-text-field v-model="preferenceStore.preferences!.ip_filter_path" :disabled="!preferenceStore.preferences!.ip_filter_enabled" hide-details />
</v-list-item>
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.ip_filter_trackers" hide-details
:label="t('settings.connection.ipFiltering.applyToTrackers')" />
<v-checkbox v-model="preferenceStore.preferences!.ip_filter_trackers" hide-details :label="t('settings.connection.ipFiltering.applyToTrackers')" />
</v-list-item>
<v-list-item>
@ -267,8 +237,7 @@ watch(
</v-list-item>
<v-list-item>
<v-textarea v-model="preferenceStore.preferences!.banned_IPs" auto-grow clearable persistent-hint
:hint="t('settings.connection.ipFiltering.bannedIpsHint')" />
<v-textarea v-model="preferenceStore.preferences!.banned_IPs" auto-grow clearable persistent-hint :hint="t('settings.connection.ipFiltering.bannedIpsHint')" />
</v-list-item>
</v-list>
</template>

View file

@ -147,33 +147,25 @@ const closeDeleteDialog = async () => {
<v-list-subheader>{{ t('settings.downloads.whenAddTorrent.subheader') }}</v-list-subheader>
<v-list-item>
<v-select v-model="preferenceStore.preferences!.torrent_content_layout" hide-details :items="contentLayoutOptions"
:label="t('constants.contentLayout.title')" />
<v-select v-model="preferenceStore.preferences!.torrent_content_layout" hide-details :items="contentLayoutOptions" :label="t('constants.contentLayout.title')" />
<v-checkbox v-model="preferenceStore.preferences!.add_to_top_of_queue" hide-details
:label="t('settings.downloads.whenAddTorrent.addToTopOfQueue')" />
<v-checkbox v-model="preferenceStore.preferences!.add_to_top_of_queue" hide-details :label="t('settings.downloads.whenAddTorrent.addToTopOfQueue')" />
<v-checkbox v-model="preferenceStore.preferences!.merge_trackers" hide-details
:label="t('settings.downloads.whenAddTorrent.mergeTrackers')" />
<v-checkbox v-model="preferenceStore.preferences!.merge_trackers" hide-details :label="t('settings.downloads.whenAddTorrent.mergeTrackers')" />
<v-checkbox v-model="preferenceStore.preferences!.start_paused_enabled" hide-details
:label="t('settings.downloads.whenAddTorrent.doNotAutoStart')" />
<v-checkbox v-model="preferenceStore.preferences!.start_paused_enabled" hide-details :label="t('settings.downloads.whenAddTorrent.doNotAutoStart')" />
<v-select v-model="preferenceStore.preferences!.torrent_stop_condition" hide-details :items="stopConditionOptions"
:label="t('constants.stopCondition.title')" />
<v-select v-model="preferenceStore.preferences!.torrent_stop_condition" hide-details :items="stopConditionOptions" :label="t('constants.stopCondition.title')" />
<v-checkbox v-model="preferenceStore.preferences!.auto_delete_mode" hide-details
:label="t('settings.downloads.whenAddTorrent.autoDeleteMode')" />
<v-checkbox v-model="preferenceStore.preferences!.auto_delete_mode" hide-details :label="t('settings.downloads.whenAddTorrent.autoDeleteMode')" />
</v-list-item>
<v-divider />
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.preallocate_all" hide-details
:label="t('settings.downloads.publicSettings.preAllocateDisk')" />
<v-checkbox v-model="preferenceStore.preferences!.preallocate_all" hide-details :label="t('settings.downloads.publicSettings.preAllocateDisk')" />
<v-checkbox v-model="preferenceStore.preferences!.incomplete_files_ext" hide-details
:label="t('settings.downloads.publicSettings.appendQBExtension')" />
<v-checkbox v-model="preferenceStore.preferences!.incomplete_files_ext" hide-details :label="t('settings.downloads.publicSettings.appendQBExtension')" />
</v-list-item>
<v-divider />
@ -212,8 +204,7 @@ const closeDeleteDialog = async () => {
</v-col>
<v-col cols="12">
<v-text-field v-model="preferenceStore.preferences!.save_path" hide-details
:label="t('settings.downloads.saveManagement.defaultSavePath')" />
<v-text-field v-model="preferenceStore.preferences!.save_path" hide-details :label="t('settings.downloads.saveManagement.defaultSavePath')" />
</v-col>
<v-col cols="12">
@ -274,8 +265,7 @@ const closeDeleteDialog = async () => {
<v-container>
<v-row>
<v-col cols="12">
<v-text-field v-model="monitoredFoldersEditedItem.monitoredFolderPath"
:label="t('settings.downloads.monitoredFolders.monitoredFolderPath')" />
<v-text-field v-model="monitoredFoldersEditedItem.monitoredFolderPath" :label="t('settings.downloads.monitoredFolders.monitoredFolderPath')" />
</v-col>
<v-col cols="12">
<v-select
@ -329,8 +319,7 @@ const closeDeleteDialog = async () => {
<v-divider />
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.excluded_file_names_enabled" hide-details
:label="t('settings.downloads.excludedFileNames.label')" />
<v-checkbox v-model="preferenceStore.preferences!.excluded_file_names_enabled" hide-details :label="t('settings.downloads.excludedFileNames.label')" />
</v-list-item>
<v-list-item>
<v-textarea
@ -345,8 +334,7 @@ const closeDeleteDialog = async () => {
<v-divider />
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.mail_notification_enabled" hide-details
:label="t('settings.downloads.mailNotification.enabled')" />
<v-checkbox v-model="preferenceStore.preferences!.mail_notification_enabled" hide-details :label="t('settings.downloads.mailNotification.enabled')" />
</v-list-item>
<v-list-item>
<v-text-field
@ -401,8 +389,8 @@ const closeDeleteDialog = async () => {
!preferenceStore.preferences!.mail_notification_enabled || !preferenceStore.preferences!.mail_notification_auth_enabled
? ''
: showPassword
? 'mdi-eye'
: 'mdi-eye-off'
? 'mdi-eye'
: 'mdi-eye-off'
"
@click:append="showPassword = !showPassword" />
</v-col>
@ -415,15 +403,13 @@ const closeDeleteDialog = async () => {
<v-list-item>
<v-row>
<v-col cols="12" md="6">
<v-checkbox v-model="preferenceStore.preferences!.autorun_on_torrent_added_enabled" hide-details
:label="t('settings.downloads.runExternalProgram.onAddedEnabled')" />
<v-checkbox v-model="preferenceStore.preferences!.autorun_on_torrent_added_enabled" hide-details :label="t('settings.downloads.runExternalProgram.onAddedEnabled')" />
<v-text-field
v-model="preferenceStore.preferences!.autorun_on_torrent_added_program"
:disabled="!preferenceStore.preferences!.autorun_on_torrent_added_enabled"
hide-details
:label="t('settings.downloads.runExternalProgram.onAddedLabel')" />
<v-checkbox v-model="preferenceStore.preferences!.autorun_enabled" hide-details
:label="t('settings.downloads.runExternalProgram.onFinishedEnabled')" />
<v-checkbox v-model="preferenceStore.preferences!.autorun_enabled" hide-details :label="t('settings.downloads.runExternalProgram.onFinishedEnabled')" />
<v-text-field
v-model="preferenceStore.preferences!.autorun_program"
:disabled="!preferenceStore.preferences!.autorun_enabled"

View file

@ -85,8 +85,7 @@ watch(
</v-btn>
</v-col>
<v-col cols="6" class="d-flex align-center justify-center">
<v-btn color="accent" :loading="loading" :disabled="rssStore.feeds.length === 0"
:text="$t('settings.rss.feeds.refreshAll')" @click="refreshAllFeeds" />
<v-btn color="accent" :loading="loading" :disabled="rssStore.feeds.length === 0" :text="$t('settings.rss.feeds.refreshAll')" @click="refreshAllFeeds" />
</v-col>
</v-row>
</template>

View file

@ -1,5 +1,4 @@
<script setup lang="ts">
import { usePreferenceStore, useVueTorrentStore } from '@/stores'
const preferenceStore = usePreferenceStore()
@ -11,8 +10,7 @@ const vuetorrentStore = useVueTorrentStore()
<v-list-subheader>{{ $t('settings.rss.general.reader.subheader') }}</v-list-subheader>
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.rss_processing_enabled" hide-details
:label="$t('settings.rss.general.reader.enableProcessing')" />
<v-checkbox v-model="preferenceStore.preferences!.rss_processing_enabled" hide-details :label="$t('settings.rss.general.reader.enableProcessing')" />
<v-row>
<v-col cols="12" sm="6">
@ -24,8 +22,7 @@ const vuetorrentStore = useVueTorrentStore()
:label="$t('settings.rss.general.reader.feedsRefreshInterval')" />
</v-col>
<v-col cols="12" sm="6">
<v-text-field v-model="preferenceStore.preferences!.rss_max_articles_per_feed" type="number"
:label="$t('settings.rss.general.reader.maximumArticlesPerFeed')" />
<v-text-field v-model="preferenceStore.preferences!.rss_max_articles_per_feed" type="number" :label="$t('settings.rss.general.reader.maximumArticlesPerFeed')" />
</v-col>
</v-row>
</v-list-item>
@ -34,8 +31,7 @@ const vuetorrentStore = useVueTorrentStore()
<v-list-subheader>{{ $t('settings.rss.general.autoDownloader.subheader') }}</v-list-subheader>
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.rss_auto_downloading_enabled" hide-details class="ma-0 pa-0"
:label="$t('settings.rss.general.autoDownloader.enable')" />
<v-checkbox v-model="preferenceStore.preferences!.rss_auto_downloading_enabled" hide-details class="ma-0 pa-0" :label="$t('settings.rss.general.autoDownloader.enable')" />
</v-list-item>
<v-divider />
@ -60,9 +56,7 @@ const vuetorrentStore = useVueTorrentStore()
<v-divider />
<v-list-item>
<v-checkbox v-model="vuetorrentStore.useIdForRssLinks"
hide-details
:label="$t('settings.rss.general.useIdForRssLinks')" />
<v-checkbox v-model="vuetorrentStore.useIdForRssLinks" hide-details :label="$t('settings.rss.general.useIdForRssLinks')" />
</v-list-item>
</v-list>
</template>

View file

@ -22,20 +22,20 @@ const schedulerOptions = ref([
const upLimit = computed({
get: () => preferenceStore.preferences!.up_limit / 1024,
set: (value: number) => preferenceStore.preferences!.up_limit = value * 1024
set: (value: number) => (preferenceStore.preferences!.up_limit = value * 1024)
})
const dlLimit = computed({
get: () => preferenceStore.preferences!.dl_limit / 1024,
set: (value: number) => preferenceStore.preferences!.dl_limit = value * 1024
set: (value: number) => (preferenceStore.preferences!.dl_limit = value * 1024)
})
const altUpLimit = computed({
get: () => preferenceStore.preferences!.alt_up_limit / 1024,
set: (value: number) => preferenceStore.preferences!.alt_up_limit = value * 1024
set: (value: number) => (preferenceStore.preferences!.alt_up_limit = value * 1024)
})
const altDlLimit = computed({
get: () => preferenceStore.preferences!.alt_dl_limit / 1024,
set: (value: number) => preferenceStore.preferences!.alt_dl_limit = value * 1024
set: (value: number) => (preferenceStore.preferences!.alt_dl_limit = value * 1024)
})
</script>
@ -48,12 +48,10 @@ const altDlLimit = computed({
<v-row class="mx-1">
<v-col cols="12" md="6">
<v-text-field v-model="upLimit" hide-details suffix="kiB/s"
:label="t('settings.speed.upload')" />
<v-text-field v-model="upLimit" hide-details suffix="kiB/s" :label="t('settings.speed.upload')" />
</v-col>
<v-col cols="12" md="6">
<v-text-field v-model="dlLimit" hide-details suffix="kiB/s"
:label="t('settings.speed.download')" />
<v-text-field v-model="dlLimit" hide-details suffix="kiB/s" :label="t('settings.speed.download')" />
</v-col>
</v-row>
@ -69,12 +67,10 @@ const altDlLimit = computed({
<v-row class="mx-1">
<v-col cols="12" md="6">
<v-text-field v-model="altUpLimit" hide-details suffix="kiB/s"
:label="t('settings.speed.upload')" />
<v-text-field v-model="altUpLimit" hide-details suffix="kiB/s" :label="t('settings.speed.upload')" />
</v-col>
<v-col cols="12" md="6">
<v-text-field v-model="altDlLimit" hide-details suffix="kiB/s"
:label="t('settings.speed.download')" />
<v-text-field v-model="altDlLimit" hide-details suffix="kiB/s" :label="t('settings.speed.download')" />
</v-col>
</v-row>
@ -88,8 +84,7 @@ const altDlLimit = computed({
<v-divider class="mt-2" />
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.scheduler_enabled" hide-details
:label="t('settings.speed.scheduler.subheader')" />
<v-checkbox v-model="preferenceStore.preferences!.scheduler_enabled" hide-details :label="t('settings.speed.scheduler.subheader')" />
</v-list-item>
<v-list-item>
@ -98,12 +93,10 @@ const altDlLimit = computed({
<v-list-subheader>{{ t('settings.speed.scheduler.from') }}</v-list-subheader>
</v-col>
<v-col cols="4" md="2">
<v-text-field v-model="preferenceStore.preferences!.schedule_from_hour"
:disabled="!preferenceStore.preferences!.scheduler_enabled" type="number" />
<v-text-field v-model="preferenceStore.preferences!.schedule_from_hour" :disabled="!preferenceStore.preferences!.scheduler_enabled" type="number" />
</v-col>
<v-col cols="4" md="2">
<v-text-field v-model="preferenceStore.preferences!.schedule_from_min"
:disabled="!preferenceStore.preferences!.scheduler_enabled" type="number" />
<v-text-field v-model="preferenceStore.preferences!.schedule_from_min" :disabled="!preferenceStore.preferences!.scheduler_enabled" type="number" />
</v-col>
<v-spacer />
@ -112,12 +105,10 @@ const altDlLimit = computed({
<v-list-subheader>{{ t('settings.speed.scheduler.to') }}</v-list-subheader>
</v-col>
<v-col cols="4" md="2">
<v-text-field v-model="preferenceStore.preferences!.schedule_to_hour"
:disabled="!preferenceStore.preferences!.scheduler_enabled" type="number" />
<v-text-field v-model="preferenceStore.preferences!.schedule_to_hour" :disabled="!preferenceStore.preferences!.scheduler_enabled" type="number" />
</v-col>
<v-col cols="4" md="2">
<v-text-field v-model="preferenceStore.preferences!.schedule_to_min"
:disabled="!preferenceStore.preferences!.scheduler_enabled" type="number" />
<v-text-field v-model="preferenceStore.preferences!.schedule_to_min" :disabled="!preferenceStore.preferences!.scheduler_enabled" type="number" />
</v-col>
</v-row>
</v-list-item>
@ -136,18 +127,15 @@ const altDlLimit = computed({
<v-list-subheader>{{ t('settings.speed.subheader.settings') }}</v-list-subheader>
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.limit_utp_rate" hide-details
:label="t('settings.speed.settings.applyToUtp')" />
<v-checkbox v-model="preferenceStore.preferences!.limit_utp_rate" hide-details :label="t('settings.speed.settings.applyToUtp')" />
</v-list-item>
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.limit_tcp_overhead" hide-details
:label="t('settings.speed.settings.applyToTransportOverhead')" />
<v-checkbox v-model="preferenceStore.preferences!.limit_tcp_overhead" hide-details :label="t('settings.speed.settings.applyToTransportOverhead')" />
</v-list-item>
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.limit_lan_peers" hide-details
:label="t('settings.speed.settings.applyToPeersOnLan')" />
<v-checkbox v-model="preferenceStore.preferences!.limit_lan_peers" hide-details :label="t('settings.speed.settings.applyToPeersOnLan')" />
</v-list-item>
</v-list>
</template>

View file

@ -11,10 +11,9 @@ defineEmits<{ update: [value: void] }>()
<v-icon icon="mdi-drag-vertical" class="dnd-handle" />
</td>
<td>
<v-btn density="compact" :icon="property.active ? 'mdi-checkbox-marked' : 'mdi-checkbox-blank-outline'"
variant="flat" @click="$emit('update')" />
<v-btn density="compact" :icon="property.active ? 'mdi-checkbox-marked' : 'mdi-checkbox-blank-outline'" variant="flat" @click="$emit('update')" />
</td>
<td>{{ $t(`torrent.properties.${ property.name }`) }}</td>
<td>{{ $t(`torrent.properties.${property.name}`) }}</td>
</tr>
</template>

View file

@ -18,13 +18,7 @@ const titleOptionsList = [
{ title: t('constants.titleOptions.custom'), value: TitleOptions.CUSTOM }
]
const paginationSizes = ref([
{ title: t('settings.vuetorrent.general.paginationSize.infinite_scroll'), value: -1 },
5,
15,
30,
50
])
const paginationSizes = ref([{ title: t('settings.vuetorrent.general.paginationSize.infinite_scroll'), value: -1 }, 5, 15, 30, 50])
const theme = computed({
get() {
@ -86,57 +80,45 @@ onBeforeMount(() => {
<v-list-item>
<v-row>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.showCurrentSpeed" hide-details density="compact"
:label="t('settings.vuetorrent.general.showCurrentSpeed')" />
<v-checkbox v-model="vueTorrentStore.showCurrentSpeed" hide-details density="compact" :label="t('settings.vuetorrent.general.showCurrentSpeed')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.showSpeedGraph" hide-details density="compact"
:label="t('settings.vuetorrent.general.showSpeedGraph')" />
<v-checkbox v-model="vueTorrentStore.showSpeedGraph" hide-details density="compact" :label="t('settings.vuetorrent.general.showSpeedGraph')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.showAlltimeStat" hide-details density="compact"
:label="t('settings.vuetorrent.general.showAlltimeStat')" />
<v-checkbox v-model="vueTorrentStore.showAlltimeStat" hide-details density="compact" :label="t('settings.vuetorrent.general.showAlltimeStat')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.showSessionStat" hide-details density="compact"
:label="t('settings.vuetorrent.general.showSessionStat')" />
<v-checkbox v-model="vueTorrentStore.showSessionStat" hide-details density="compact" :label="t('settings.vuetorrent.general.showSessionStat')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.showFreeSpace" hide-details density="compact"
:label="t('settings.vuetorrent.general.showFreeSpace')" />
<v-checkbox v-model="vueTorrentStore.showFreeSpace" hide-details density="compact" :label="t('settings.vuetorrent.general.showFreeSpace')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.showTrackerFilter" hide-details density="compact"
:label="t('settings.vuetorrent.general.showTrackerFilter')" />
<v-checkbox v-model="vueTorrentStore.showTrackerFilter" hide-details density="compact" :label="t('settings.vuetorrent.general.showTrackerFilter')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.isDrawerRight" hide-details density="compact"
:label="t('settings.vuetorrent.general.isDrawerRight')" />
<v-checkbox v-model="vueTorrentStore.isDrawerRight" hide-details density="compact" :label="t('settings.vuetorrent.general.isDrawerRight')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.isPaginationOnTop" hide-details density="compact"
:label="t('settings.vuetorrent.general.isPaginationOnTop')" />
<v-checkbox v-model="vueTorrentStore.isPaginationOnTop" hide-details density="compact" :label="t('settings.vuetorrent.general.isPaginationOnTop')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.openSideBarOnStart" hide-details density="compact"
:label="t('settings.vuetorrent.general.openSideBarOnStart')" />
<v-checkbox v-model="vueTorrentStore.openSideBarOnStart" hide-details density="compact" :label="t('settings.vuetorrent.general.openSideBarOnStart')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.isShutdownButtonVisible" hide-details density="compact"
:label="t('settings.vuetorrent.general.isShutdownButtonVisible')" />
<v-checkbox v-model="vueTorrentStore.isShutdownButtonVisible" hide-details density="compact" :label="t('settings.vuetorrent.general.isShutdownButtonVisible')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.useBinarySize" hide-details density="compact"
:label="t('settings.vuetorrent.general.useBinarySize')" />
<v-checkbox v-model="vueTorrentStore.useBinarySize" hide-details density="compact" :label="t('settings.vuetorrent.general.useBinarySize')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.useBitSpeed" hide-details density="compact"
:label="t('settings.vuetorrent.general.useBitSpeed')" />
<v-checkbox v-model="vueTorrentStore.useBitSpeed" hide-details density="compact" :label="t('settings.vuetorrent.general.useBitSpeed')" />
</v-col>
</v-row>
</v-list-item>
@ -144,26 +126,21 @@ onBeforeMount(() => {
<v-list-item>
<v-row>
<v-col cols="12" md="4">
<v-text-field v-model="vueTorrentStore.refreshInterval" type="number" hide-details suffix="ms"
:label="t('settings.vuetorrent.general.refreshInterval')" />
<v-text-field v-model="vueTorrentStore.refreshInterval" type="number" hide-details suffix="ms" :label="t('settings.vuetorrent.general.refreshInterval')" />
</v-col>
<v-col cols="12" md="4">
<v-text-field v-model="vueTorrentStore.fileContentInterval" type="number" hide-details suffix="ms"
:label="t('settings.vuetorrent.general.fileContentInterval')" />
<v-text-field v-model="vueTorrentStore.fileContentInterval" type="number" hide-details suffix="ms" :label="t('settings.vuetorrent.general.fileContentInterval')" />
</v-col>
<v-col cols="12" md="4">
<v-text-field v-model="historyStore.historySize" type="number" hide-details
:label="t('settings.vuetorrent.general.historySize')" />
<v-text-field v-model="historyStore.historySize" type="number" hide-details :label="t('settings.vuetorrent.general.historySize')" />
</v-col>
</v-row>
<v-row>
<v-col cols="12" md="6">
<v-text-field v-model="vueTorrentStore.canvasRenderThreshold" type="number"
:label="t('settings.vuetorrent.general.canvasRenderThreshold')" />
<v-text-field v-model="vueTorrentStore.canvasRenderThreshold" type="number" :label="t('settings.vuetorrent.general.canvasRenderThreshold')" />
</v-col>
<v-col cols="12" md="6">
<v-text-field v-model="vueTorrentStore.canvasRefreshThreshold" type="number"
:label="t('settings.vuetorrent.general.canvasRefreshThreshold')" />
<v-text-field v-model="vueTorrentStore.canvasRefreshThreshold" type="number" :label="t('settings.vuetorrent.general.canvasRefreshThreshold')" />
</v-col>
</v-row>
</v-list-item>
@ -171,20 +148,19 @@ onBeforeMount(() => {
<v-list-item>
<v-row>
<v-col cols="12" sm="6" md="3">
<v-select v-model="vueTorrentStore.language" flat hide-details :items="LOCALES"
:label="t('settings.vuetorrent.general.language')" />
<v-select v-model="vueTorrentStore.language" flat hide-details :items="LOCALES" :label="t('settings.vuetorrent.general.language')" />
</v-col>
<v-col cols="12" sm="6" md="3">
<v-select v-model="vueTorrentStore.paginationSize" flat hide-details :items="paginationSizes"
:label="t('settings.vuetorrent.general.paginationSize.label')" />
<v-select v-model="vueTorrentStore.paginationSize" flat hide-details :items="paginationSizes" :label="t('settings.vuetorrent.general.paginationSize.label')" />
</v-col>
<v-col cols="12" sm="6" md="3">
<v-select v-model="vueTorrentStore.uiTitleType" flat hide-details :items="titleOptionsList"
:label="t('settings.vuetorrent.general.vueTorrentTitle')" />
<v-select v-model="vueTorrentStore.uiTitleType" flat hide-details :items="titleOptionsList" :label="t('settings.vuetorrent.general.vueTorrentTitle')" />
</v-col>
<v-col cols="12" sm="6" md="3">
<v-text-field :disabled="vueTorrentStore.uiTitleType !== TitleOptions.CUSTOM"
v-model="vueTorrentStore.uiTitleCustom" :label="t('settings.vuetorrent.general.customTitle')" />
<v-text-field
:disabled="vueTorrentStore.uiTitleType !== TitleOptions.CUSTOM"
v-model="vueTorrentStore.uiTitleCustom"
:label="t('settings.vuetorrent.general.customTitle')" />
</v-col>
</v-row>
</v-list-item>
@ -195,21 +171,15 @@ onBeforeMount(() => {
<h3>
{{ t('settings.vuetorrent.general.currentVersion') }}
<span v-if="!vueTorrentVersion">undefined</span>
<a v-else-if="vueTorrentVersion === 'DEV'" target="_blank"
href="https://github.com/WDaan/VueTorrent/">{{ vueTorrentVersion }}</a>
<a v-else target="_blank" :href="`https://github.com/WDaan/VueTorrent/releases/tag/v${vueTorrentVersion}`">{{
vueTorrentVersion
}}</a>
<a v-else-if="vueTorrentVersion === 'DEV'" target="_blank" href="https://github.com/WDaan/VueTorrent/">{{ vueTorrentVersion }}</a>
<a v-else target="_blank" :href="`https://github.com/WDaan/VueTorrent/releases/tag/v${vueTorrentVersion}`">{{ vueTorrentVersion }}</a>
</h3>
</v-col>
<v-col cols="12" md="3" class="d-flex align-center justify-center">
<h3>
{{ t('settings.vuetorrent.general.qbittorrentVersion') }}
<a target="_blank"
:href="`https://github.com/qbittorrent/qBittorrent/releases/tag/release-${appStore.version}`">{{
appStore.version
}}</a>
<a target="_blank" :href="`https://github.com/qbittorrent/qBittorrent/releases/tag/release-${appStore.version}`">{{ appStore.version }}</a>
</h3>
</v-col>
@ -218,8 +188,7 @@ onBeforeMount(() => {
</v-col>
<v-col cols="12" md="3">
<v-text-field v-model="vueTorrentStore.dateFormat" placeholder="DD/MM/YYYY, HH:mm:ss" hint="using Dayjs"
:label="t('settings.vuetorrent.general.dateFormat')" />
<v-text-field v-model="vueTorrentStore.dateFormat" placeholder="DD/MM/YYYY, HH:mm:ss" hint="using Dayjs" :label="t('settings.vuetorrent.general.dateFormat')" />
</v-col>
</v-row>
</v-list-item>
@ -227,10 +196,7 @@ onBeforeMount(() => {
<v-list-item>
<v-row>
<v-col cols="12" sm="6" class="d-flex align-center justify-center">
<v-btn color="primary" @click="registerMagnetHandler">{{
t('settings.vuetorrent.general.registerMagnet')
}}
</v-btn>
<v-btn color="primary" @click="registerMagnetHandler">{{ t('settings.vuetorrent.general.registerMagnet') }} </v-btn>
</v-col>
<v-col cols="12" sm="6" class="d-flex align-center justify-center">

View file

@ -36,17 +36,14 @@ watch(webUiPassword, newValue => {
<v-list-item>
<v-row>
<v-col cols="9">
<v-text-field v-model="preferenceStore.preferences!.web_ui_address" hide-details
:label="t('settings.webUI.interface.ipAddress')" />
<v-text-field v-model="preferenceStore.preferences!.web_ui_address" hide-details :label="t('settings.webUI.interface.ipAddress')" />
</v-col>
<v-col cols="3">
<v-text-field v-model="preferenceStore.preferences!.web_ui_port" hide-details
:label="t('settings.webUI.interface.port')" />
<v-text-field v-model="preferenceStore.preferences!.web_ui_port" hide-details :label="t('settings.webUI.interface.port')" />
</v-col>
<v-col cols="12" class="pt-0">
<v-checkbox v-model="preferenceStore.preferences!.web_ui_upnp" hide-details
:label="t('settings.webUI.interface.useUPnP')" />
<v-checkbox v-model="preferenceStore.preferences!.web_ui_upnp" hide-details :label="t('settings.webUI.interface.useUPnP')" />
</v-col>
</v-row>
</v-list-item>
@ -72,8 +69,7 @@ watch(webUiPassword, newValue => {
<v-list-item>
<v-row>
<v-col cols="12" sm="6">
<v-text-field v-model="preferenceStore.preferences!.web_ui_username" hide-details
:label="t('settings.webUI.authentication.username')" />
<v-text-field v-model="preferenceStore.preferences!.web_ui_username" hide-details :label="t('settings.webUI.authentication.username')" />
</v-col>
<v-col cols="12" sm="6">
<PasswordField
@ -86,12 +82,10 @@ watch(webUiPassword, newValue => {
</v-col>
<v-col cols="12" class="py-0">
<v-checkbox v-model="preferenceStore.preferences!.bypass_local_auth" hide-details
:label="t('settings.webUI.authentication.bypassLocalhost')" />
<v-checkbox v-model="preferenceStore.preferences!.bypass_local_auth" hide-details :label="t('settings.webUI.authentication.bypassLocalhost')" />
</v-col>
<v-col cols="12" class="pt-0">
<v-checkbox v-model="preferenceStore.preferences!.bypass_auth_subnet_whitelist_enabled" hide-details
:label="t('settings.webUI.authentication.bypassWhitelist')" />
<v-checkbox v-model="preferenceStore.preferences!.bypass_auth_subnet_whitelist_enabled" hide-details :label="t('settings.webUI.authentication.bypassWhitelist')" />
<v-textarea
v-model="preferenceStore.preferences!.bypass_auth_subnet_whitelist"
:disabled="!preferenceStore.preferences!.bypass_auth_subnet_whitelist_enabled"
@ -105,8 +99,7 @@ watch(webUiPassword, newValue => {
<v-list-item>
<v-row>
<v-col cols="12" sm="4">
<v-text-field v-model="preferenceStore.preferences!.web_ui_max_auth_fail_count" type="number" hide-details
:label="t('settings.webUI.authentication.maxAttempts')" />
<v-text-field v-model="preferenceStore.preferences!.web_ui_max_auth_fail_count" type="number" hide-details :label="t('settings.webUI.authentication.maxAttempts')" />
</v-col>
<v-col cols="12" sm="4">
@ -134,8 +127,7 @@ watch(webUiPassword, newValue => {
<v-list-item>
<v-row>
<v-col cols="12" class="pb-0">
<v-checkbox v-model="preferenceStore.preferences!.use_https" hide-details
:label="t('settings.webUI.https.subheader')" />
<v-checkbox v-model="preferenceStore.preferences!.use_https" hide-details :label="t('settings.webUI.https.subheader')" />
</v-col>
<v-col cols="12" class="pt-0">
<v-text-field
@ -155,8 +147,7 @@ watch(webUiPassword, newValue => {
</v-list-item>
<v-list-item>
<a href="https://httpd.apache.org/docs/current/ssl/ssl_faq.html#aboutcerts"
target="_blank">{{ t('settings.webUI.https.tip') }}</a>
<a href="https://httpd.apache.org/docs/current/ssl/ssl_faq.html#aboutcerts" target="_blank">{{ t('settings.webUI.https.tip') }}</a>
</v-list-item>
<v-divider />
@ -172,8 +163,7 @@ watch(webUiPassword, newValue => {
:label="t('settings.webUI.security.clickjacking')" />
</v-col>
<v-col cols="12" class="py-0">
<v-checkbox v-model="preferenceStore.preferences!.web_ui_csrf_protection_enabled" hide-details
density="compact" :label="t('settings.webUI.security.csrf')" />
<v-checkbox v-model="preferenceStore.preferences!.web_ui_csrf_protection_enabled" hide-details density="compact" :label="t('settings.webUI.security.csrf')" />
</v-col>
<v-col cols="12" class="py-0">
<v-checkbox
@ -205,8 +195,7 @@ watch(webUiPassword, newValue => {
<v-divider />
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.web_ui_use_custom_http_headers_enabled" hide-details
:label="t('settings.webUI.customHeaders')" />
<v-checkbox v-model="preferenceStore.preferences!.web_ui_use_custom_http_headers_enabled" hide-details :label="t('settings.webUI.customHeaders')" />
</v-list-item>
<v-list-item>
<v-textarea
@ -222,8 +211,7 @@ watch(webUiPassword, newValue => {
<v-divider />
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.web_ui_reverse_proxy_enabled" hide-details
:label="t('settings.webUI.reverseProxySupport')" />
<v-checkbox v-model="preferenceStore.preferences!.web_ui_reverse_proxy_enabled" hide-details :label="t('settings.webUI.reverseProxySupport')" />
</v-list-item>
<v-list-item>
<v-text-field
@ -238,14 +226,12 @@ watch(webUiPassword, newValue => {
<v-divider />
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.dyndns_enabled" hide-details
:label="t('settings.webUI.dynDns.subheader')" />
<v-checkbox v-model="preferenceStore.preferences!.dyndns_enabled" hide-details :label="t('settings.webUI.dynDns.subheader')" />
</v-list-item>
<v-list-item>
<v-row>
<v-col cols="8">
<v-select v-model="dynDnsProvider" :disabled="!preferenceStore.preferences!.dyndns_enabled" density="compact"
hide-details :items="dynDnsProviderOptions" />
<v-select v-model="dynDnsProvider" :disabled="!preferenceStore.preferences!.dyndns_enabled" density="compact" hide-details :items="dynDnsProviderOptions" />
</v-col>
<v-col cols="4">
<v-btn :disabled="!preferenceStore.preferences!.dyndns_enabled" @click="registerDynDNS">

View file

@ -16,7 +16,7 @@ const dialogStore = useDialogStore()
const maindataStore = useMaindataStore()
const { fileContentInterval } = storeToRefs(useVueTorrentStore())
const { pause: pauseTimer, resume: resumeTimer } = useIntervalFn(updateFileTree, fileContentInterval,{
const { pause: pauseTimer, resume: resumeTimer } = useIntervalFn(updateFileTree, fileContentInterval, {
immediate: false,
immediateCallback: true
})
@ -38,15 +38,15 @@ const fileSelection = computed({
const oldValue = cachedFiles.value.filter(f => f.priority !== FilePriority.DO_NOT_DOWNLOAD).map(f => f.index)
const filesToExclude = oldValue
.filter(index => !newValue.includes(index))
.map(index => cachedFiles.value.find(f => f.index === index))
.filter(f => f && f.priority !== FilePriority.DO_NOT_DOWNLOAD)
.map(f => (f as TorrentFile).index)
.filter(index => !newValue.includes(index))
.map(index => cachedFiles.value.find(f => f.index === index))
.filter(f => f && f.priority !== FilePriority.DO_NOT_DOWNLOAD)
.map(f => (f as TorrentFile).index)
const filesToInclude = newValue
.filter(index => !oldValue.includes(index))
.map(index => cachedFiles.value.find(f => f.index === index))
.filter(f => f && f.priority === FilePriority.DO_NOT_DOWNLOAD)
.map(f => (f as TorrentFile).index)
.filter(index => !oldValue.includes(index))
.map(index => cachedFiles.value.find(f => f.index === index))
.filter(f => f && f.priority === FilePriority.DO_NOT_DOWNLOAD)
.map(f => (f as TorrentFile).index)
if (filesToExclude.length) {
await maindataStore.setTorrentFilePriority(props.torrent.hash, filesToExclude, FilePriority.DO_NOT_DOWNLOAD)
@ -111,8 +111,7 @@ onMounted(() => {
<template>
<v-card :loading="loading" flat>
<RootNode v-model:opened="openedItems" v-model:selected="fileSelection" :root="tree"
@renameFolder="renameNode" @renameFile="renameNode" @setFilePriority="setFilePriority" />
<RootNode v-model:opened="openedItems" v-model:selected="fileSelection" :root="tree" @renameFolder="renameNode" @renameFile="renameNode" @setFilePriority="setFilePriority" />
<!--
TODO: add treeview after merge
https://github.com/vuetifyjs/vuetify/issues/13518

View file

@ -10,7 +10,7 @@ defineProps<{
node: TreeFile
}>()
defineEmits<{
renameFile: [node: TreeFile],
renameFile: [node: TreeFile]
setFilePriority: [node: TreeFile, priority: FilePriority]
}>()

View file

@ -9,7 +9,7 @@ defineProps<{
}>()
defineEmits<{
renameFolder: [node: TreeFolder]
renameFile: [node: TreeFile],
renameFile: [node: TreeFile]
setFilePriority: [node: TreeFile, priority: FilePriority]
}>()
@ -48,21 +48,24 @@ function getNodeDescription(node: TreeRoot | TreeFolder) {
:value="node.type === 'root' ? '(root)' : node.fullName">
<template v-slot:append="{ isActive }">
<span class="mr-2">{{ getNodeDescription(node) }}</span>
<v-btn v-if="node.type === 'folder'" color="accent" size="x-small" icon="mdi-pencil"
@click.stop="$emit('renameFolder', node)" />
<v-btn v-if="node.type === 'folder'" color="accent" size="x-small" icon="mdi-pencil" @click.stop="$emit('renameFolder', node)" />
<v-icon :icon="isActive ? 'mdi-chevron-up' : 'mdi-chevron-down'" />
</template>
</v-list-item>
</template>
<template v-for="child in node.children">
<FolderNode v-if="child.type === 'folder'" :node="child as TreeFolder"
@renameFolder="n => $emit('renameFolder', n)"
@renameFile="n => $emit('renameFile', n)"
@setFilePriority="(n, prio) => $emit('setFilePriority', n, prio)" />
<FileNode v-if="child.type === 'file'" :node="child as TreeFile"
@renameFile="n => $emit('renameFile', n)"
@setFilePriority="(n, prio) => $emit('setFilePriority', n, prio)" />
<FolderNode
v-if="child.type === 'folder'"
:node="child as TreeFolder"
@renameFolder="n => $emit('renameFolder', n)"
@renameFile="n => $emit('renameFile', n)"
@setFilePriority="(n, prio) => $emit('setFilePriority', n, prio)" />
<FileNode
v-if="child.type === 'file'"
:node="child as TreeFile"
@renameFile="n => $emit('renameFile', n)"
@setFilePriority="(n, prio) => $emit('setFilePriority', n, prio)" />
</template>
</v-list-group>
</template>

View file

@ -8,17 +8,18 @@ defineProps<{
}>()
defineEmits<{
renameFolder: [node: TreeFolder]
renameFile: [node: TreeFile],
renameFile: [node: TreeFile]
setFilePriority: [node: TreeFile, priority: FilePriority]
}>()
</script>
<template>
<v-list density="compact" select-strategy="classic">
<FolderNode :node="root"
@renameFolder="n => $emit('renameFolder', n)"
@renameFile="n => $emit('renameFile', n)"
@setFilePriority="(n, prio) => $emit('setFilePriority', n, prio)" />
<FolderNode
:node="root"
@renameFolder="n => $emit('renameFolder', n)"
@renameFile="n => $emit('renameFile', n)"
@setFilePriority="(n, prio) => $emit('setFilePriority', n, prio)" />
</v-list>
</template>

View file

@ -113,7 +113,7 @@ const longTextPpts = [
<v-expansion-panel-text>
<v-row>
<InfoBase v-for="ppt in datetimePpts">
<template v-slot:title>{{ $t(`torrent.properties.${ ppt.title }`) }}</template>
<template v-slot:title>{{ $t(`torrent.properties.${ppt.title}`) }}</template>
<template v-if="torrent[ppt.text] > 0" v-slot:text>
{{ dayjs(torrent[ppt.text] * 1000).format(vuetorrentStore.dateFormat ?? 'DD/MM/YYYY, HH:mm:ss') }}
</template>
@ -127,7 +127,7 @@ const longTextPpts = [
<v-expansion-panel-text>
<v-row>
<InfoBase v-for="ppt in durationPpts">
<template v-slot:title>{{ $t(`torrent.properties.${ ppt.title }`) }}</template>
<template v-slot:title>{{ $t(`torrent.properties.${ppt.title}`) }}</template>
<template v-slot:text>{{ dayjs.duration(torrent[ppt.text], 's').humanize() }}</template>
</InfoBase>
</v-row>
@ -139,36 +139,31 @@ const longTextPpts = [
<v-row>
<InfoBase>
<template v-slot:title>
<v-checkbox v-model="auto_tmm" hide-details density="compact"
:label="$t('torrent.properties.auto_tmm')" />
<v-checkbox v-model="auto_tmm" hide-details density="compact" :label="$t('torrent.properties.auto_tmm')" />
</template>
</InfoBase>
<InfoBase>
<template v-slot:title>
<v-checkbox v-model="f_l_piece_prio" hide-details density="compact"
:label="$t('torrent.properties.f_l_piece_prio')" />
<v-checkbox v-model="f_l_piece_prio" hide-details density="compact" :label="$t('torrent.properties.f_l_piece_prio')" />
</template>
</InfoBase>
<InfoBase>
<template v-slot:title>
<v-checkbox v-model="forced" hide-details density="compact"
:label="$t('torrent.properties.forced')" />
<v-checkbox v-model="forced" hide-details density="compact" :label="$t('torrent.properties.forced')" />
</template>
</InfoBase>
<InfoBase>
<template v-slot:title>
<v-checkbox v-model="seq_dl" hide-details density="compact"
:label="$t('torrent.properties.seq_dl')" />
<v-checkbox v-model="seq_dl" hide-details density="compact" :label="$t('torrent.properties.seq_dl')" />
</template>
</InfoBase>
<InfoBase>
<template v-slot:title>
<v-checkbox v-model="super_seeding" hide-details density="compact"
:label="$t('torrent.properties.super_seeding')" />
<v-checkbox v-model="super_seeding" hide-details density="compact" :label="$t('torrent.properties.super_seeding')" />
</template>
</InfoBase>
</v-row>
@ -179,7 +174,7 @@ const longTextPpts = [
<v-expansion-panel-text>
<v-row>
<InfoBase v-for="ppt in dataPpts">
<template v-slot:title>{{ $t(`torrent.properties.${ ppt.title }`) }}</template>
<template v-slot:title>{{ $t(`torrent.properties.${ppt.title}`) }}</template>
<template v-slot:text>{{ formatData(torrent[ppt.text], vuetorrentStore.useBinarySize) }}</template>
</InfoBase>
</v-row>
@ -190,7 +185,7 @@ const longTextPpts = [
<v-expansion-panel-text>
<v-row>
<InfoBase v-for="ppt in speedPpts">
<template v-slot:title>{{ $t(`torrent.properties.${ ppt.title }`) }}</template>
<template v-slot:title>{{ $t(`torrent.properties.${ppt.title}`) }}</template>
<template v-slot:text>{{ formatSpeed(torrent[ppt.text], vuetorrentStore.useBitSpeed) }}</template>
</InfoBase>
</v-row>
@ -201,7 +196,7 @@ const longTextPpts = [
<v-expansion-panel-text>
<v-row>
<InfoBase v-for="ppt in textPpts">
<template v-slot:title>{{ $t(`torrent.properties.${ ppt.title }`) }}</template>
<template v-slot:title>{{ $t(`torrent.properties.${ppt.title}`) }}</template>
<template v-slot:text>{{ torrent[ppt.text] }}</template>
</InfoBase>
</v-row>

View file

@ -2,16 +2,7 @@
import MoveTorrentDialog from '@/components/Dialogs/MoveTorrentDialog.vue'
import MoveTorrentFileDialog from '@/components/Dialogs/MoveTorrentFileDialog.vue'
import { FilePriority, PieceState, TorrentState } from '@/constants/qbit'
import {
formatData,
formatDataUnit,
formatDataValue,
formatPercent,
formatSpeed,
getDomainBody,
splitByUrl,
stringContainsUrl
} from '@/helpers'
import { formatData, formatDataUnit, formatDataValue, formatPercent, formatSpeed, getDomainBody, splitByUrl, stringContainsUrl } from '@/helpers'
import { useDialogStore, useMaindataStore, useTorrentStore, useVueTorrentStore } from '@/stores'
import { TorrentFile } from '@/types/qbit/models'
import { Torrent } from '@/types/vuetorrent'
@ -42,8 +33,8 @@ const torrentPieceOwned = ref(0)
const torrentPieceCount = ref(0)
const uploadSpeedAvg = ref(0)
const torrentStateColor = computed(() => `torrent-${ props.torrent.state }`)
const pieceSize = computed(() => `${ parseInt(formatDataValue(torrentPieceSize.value, true)) } ${ formatDataUnit(torrentPieceSize.value, true) }`)
const torrentStateColor = computed(() => `torrent-${props.torrent.state}`)
const pieceSize = computed(() => `${parseInt(formatDataValue(torrentPieceSize.value, true))} ${formatDataUnit(torrentPieceSize.value, true)}`)
const isFetchingMetadata = computed(() => props.torrent.state === TorrentState.META_DL)
const shouldRenderPieceState = computed(() => !isFetchingMetadata.value && torrentPieceCount.value > 0 && torrentPieceCount.value < vuetorrentStore.canvasRenderThreshold)
const shouldRefreshPieceState = computed(() => shouldRenderPieceState.value && torrentPieceCount.value < vuetorrentStore.canvasRefreshThreshold)
@ -138,15 +129,19 @@ function openMoveTorrentFileDialog() {
})
}
const { resume: resumeTimer, pause: pauseTimer } = useIntervalFn(async () => {
await updateTorrentFiles()
if (shouldRefreshPieceState.value) {
await renderTorrentPieceStates()
const { resume: resumeTimer, pause: pauseTimer } = useIntervalFn(
async () => {
await updateTorrentFiles()
if (shouldRefreshPieceState.value) {
await renderTorrentPieceStates()
}
},
vuetorrentStore.fileContentInterval,
{
immediate: true,
immediateCallback: true
}
}, vuetorrentStore.fileContentInterval, {
immediate: true,
immediateCallback: true
})
)
watch(
() => props.isActive,
@ -217,8 +212,7 @@ onUnmounted(() => {
<v-col cols="12" md="6">
<v-row>
<v-col cols="4">
<v-progress-circular :color="torrentStateColor" :indeterminate="isFetchingMetadata" :size="100"
:model-value="torrent?.progress * 100 ?? 0" :width="15">
<v-progress-circular :color="torrentStateColor" :indeterminate="isFetchingMetadata" :size="100" :model-value="torrent?.progress * 100 ?? 0" :width="15">
<template v-slot>
<span v-if="isFetchingMetadata">{{ $t('torrentDetail.overview.fetchingMetadata') }}</span>
<v-icon v-else-if="torrent.progress === 1" icon="mdi-check" size="x-large" />
@ -273,8 +267,7 @@ onUnmounted(() => {
<div>{{ $t('torrentDetail.overview.fileCount') }}:</div>
<div>{{ selectedFileCount }} / {{ torrentFileCount }}</div>
<div v-if="selectedFileCount === 1">{{ torrentFileName }}</div>
<v-btn v-if="selectedFileCount === 1" icon="mdi-pencil" color="accent" size="x-small"
@click="openMoveTorrentFileDialog" />
<v-btn v-if="selectedFileCount === 1" icon="mdi-pencil" color="accent" size="x-small" @click="openMoveTorrentFileDialog" />
</v-col>
</v-row>
@ -295,7 +288,7 @@ onUnmounted(() => {
<v-row>
<v-col cols="6">
<div>{{ $t('torrent.properties.state') }}:</div>
<v-chip variant="flat" :color="torrentStateColor">{{ $t(`torrent.state.${ torrent.state }`) }}</v-chip>
<v-chip variant="flat" :color="torrentStateColor">{{ $t(`torrent.state.${torrent.state}`) }}</v-chip>
</v-col>
<v-col cols="6">
<div>{{ $t('torrent.properties.category') }}:</div>

View file

@ -72,8 +72,7 @@ watch(() => props.isActive, setupTimer)
<div>
<v-list-item-title class="overflow-visible text-select">
<span v-if="peer.country_code">
<img v-if="isWindows" :alt="codeToFlag(peer.country_code).char" :src="codeToFlag(peer.country_code).url"
:title="peer.country" style="max-width: 32px" />
<img v-if="isWindows" :alt="codeToFlag(peer.country_code).char" :src="codeToFlag(peer.country_code).url" :title="peer.country" style="max-width: 32px" />
<span v-else :title="peer.country">{{ codeToFlag(peer.country_code).char }}</span>
</span>
<span>{{ peer.ip }}</span>

View file

@ -162,8 +162,7 @@ watch(() => props.isActive, setupTimer)
<v-card-text>
<v-form v-model="editTrackerDialog.isFormValid" @submit.prevent>
<v-text-field :model-value="editTrackerDialog.oldUrl" disabled
:label="$t('torrentDetail.trackers.editTracker.oldUrl')" />
<v-text-field :model-value="editTrackerDialog.oldUrl" disabled :label="$t('torrentDetail.trackers.editTracker.oldUrl')" />
<v-text-field
v-model="editTrackerDialog.newUrl"
id="input"
@ -176,9 +175,7 @@ watch(() => props.isActive, setupTimer)
<v-card-actions>
<v-spacer />
<v-btn color="error" :disabled="!editTrackerDialog.isFormValid"
@click="editTrackerDialog.isVisible = false">{{ t('common.cancel') }}
</v-btn>
<v-btn color="error" :disabled="!editTrackerDialog.isFormValid" @click="editTrackerDialog.isVisible = false">{{ t('common.cancel') }} </v-btn>
<v-btn color="accent" @click="editTracker">{{ t('common.ok') }}</v-btn>
</v-card-actions>
</v-card>
@ -221,8 +218,7 @@ watch(() => props.isActive, setupTimer)
</v-card>
</v-dialog>
<v-btn variant="flat" :disabled="torrentTrackers.length === 3" :text="t('torrentDetail.trackers.reannounce')"
color="primary" @click="reannounceTrackers" />
<v-btn variant="flat" :disabled="torrentTrackers.length === 3" :text="t('torrentDetail.trackers.reannounce')" color="primary" @click="reannounceTrackers" />
</div>
</v-list-item>
</v-list>

View file

@ -2,18 +2,10 @@ import { useOffsetPagination } from '@vueuse/core'
import { computed, MaybeRef, MaybeRefOrGetter, toValue } from 'vue'
export function useArrayPagination<T>(items: MaybeRefOrGetter<T[]>, pageSize: MaybeRefOrGetter<number>, page: MaybeRef<number> = 1) {
const {
currentPage,
currentPageSize,
pageCount,
isFirstPage,
isLastPage,
next,
prev,
} = useOffsetPagination({
const { currentPage, currentPageSize, pageCount, isFirstPage, isLastPage, next, prev } = useOffsetPagination({
total: () => toValue(items).length,
page,
pageSize: () => toValue(pageSize) === -1 ? toValue(items).length : toValue(pageSize),
pageSize: () => (toValue(pageSize) === -1 ? toValue(items).length : toValue(pageSize))
})
const paginatedResults = computed(() => {
@ -32,4 +24,4 @@ export function useArrayPagination<T>(items: MaybeRefOrGetter<T[]>, pageSize: Ma
prev,
paginatedResults
}
}
}

View file

@ -51,7 +51,7 @@ export function useTorrentBuilder() {
seq_dl: data.seq_dl,
size: data.size,
state: data.state,
stateString: t(`torrent.state.${ data.state }`),
stateString: t(`torrent.state.${data.state}`),
super_seeding: data.super_seeding,
tags: data.tags.length > 0 ? data.tags.split(', ').map(t => t.trim()) : [],
time_active: data.time_active,
@ -97,7 +97,7 @@ export function useTorrentBuilder() {
infohash_v2: data.infohash_v2 || faker.string.uuid(),
last_activity: data.last_activity || faker.number.int({ min: 0, max: 50 }),
magnet: data.magnet_uri || faker.internet.url(),
name: data.name || `Torrent ${ index + 1 }`,
name: data.name || `Torrent ${index + 1}`,
num_leechs: data.num_leechs || faker.number.int(available_peers),
num_seeds: data.num_seeds || faker.number.int(available_seeds),
priority: data.priority || FilePriority.NORMAL,
@ -111,7 +111,7 @@ export function useTorrentBuilder() {
seq_dl: data.seq_dl || faker.datatype.boolean(),
size: data.size || faker.number.int({ min: 1000, max: total_size }),
state,
stateString: t(`torrent.state.${ state }`),
stateString: t(`torrent.state.${state}`),
super_seeding: data.super_seeding || faker.datatype.boolean(),
tags: data.tags || '',
time_active: data.time_active || faker.number.int({ min: 1000, max: 900000 }),
@ -133,8 +133,8 @@ export function useTorrentBuilder() {
// @ts-expect-error: Type is missing the following properties from type 'Torrent': ...
return Object.freeze({
...data,
avgDownloadSpeed: data.downloaded / ((dlDuration == 0) ? -1 : dlDuration),
avgUploadSpeed: data.uploaded / ((ulDuration == 0) ? -1 : ulDuration),
avgDownloadSpeed: data.downloaded / (dlDuration == 0 ? -1 : dlDuration),
avgUploadSpeed: data.uploaded / (ulDuration == 0 ? -1 : ulDuration),
globalSpeed: data.dlspeed + data.upspeed,
globalVolume: data.downloaded + data.uploaded
})

View file

@ -13,4 +13,4 @@ export enum FilterState {
CHECKING = 'checking',
MOVING = 'moving',
ERRORED = 'errored'
}
}

View file

@ -1,4 +1,4 @@
export enum TorrentOperatingMode {
AUTO_MANAGED = 'AutoManaged',
FORCED = 'Forced'
}
}

View file

@ -9,15 +9,4 @@ import { TorrentOperatingMode } from './TorrentOperatingMode'
import { TorrentState } from './TorrentState'
import { TrackerStatus } from './TrackerStatus'
export {
AppPreferences,
ConnectionStatus,
FilterState,
LogType,
PieceState,
FilePriority,
SortOptions,
TrackerStatus,
TorrentOperatingMode,
TorrentState
}
export { AppPreferences, ConnectionStatus, FilterState, LogType, PieceState, FilePriority, SortOptions, TrackerStatus, TorrentOperatingMode, TorrentState }

View file

@ -2,4 +2,4 @@ export enum HistoryKey {
COOKIE = 'cookie',
SEARCH_ENGINE_QUERY = 'searchEngineQuery',
TORRENT_PATH = 'torrentPath'
}
}

View file

@ -3,4 +3,4 @@ export enum TitleOptions {
GLOBAL_SPEED,
FIRST_TORRENT_STATUS,
CUSTOM
}
}

View file

@ -1,13 +1,4 @@
import {
capitalize,
codeToFlag,
extractHostname,
getDomainBody,
splitByUrl,
stringContainsUrl,
titleCase,
uuidFromRaw
} from './text'
import { capitalize, codeToFlag, extractHostname, getDomainBody, splitByUrl, stringContainsUrl, titleCase, uuidFromRaw } from './text'
import { expect, test } from 'vitest'
test('helpers/text/titleCase', () => {
@ -81,7 +72,7 @@ test('helpers/text/codeToFlag', () => {
test('helpers/text/uuidFromRaw', () => {
expect(uuidFromRaw(0n)).toBe('00000000-0000-0000-0000-000000000000')
expect(uuidFromRaw(1n)).toBe('00000000-0000-0000-0000-000000000001')
expect(uuidFromRaw(0xAAAAAn)).toBe('00000000-0000-0000-0000-0000000aaaaa')
expect(uuidFromRaw(0x00000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFn)).toBe('ffffffff-ffff-ffff-ffff-ffffffffffff')
expect(uuidFromRaw(0xABCDEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFn)).toBe('ffffffff-ffff-ffff-ffff-ffffffffffff')
expect(uuidFromRaw(0xaaaaan)).toBe('00000000-0000-0000-0000-0000000aaaaa')
expect(uuidFromRaw(0x00000ffffffffffffffffffffffffffffffffn)).toBe('ffffffff-ffff-ffff-ffff-ffffffffffff')
expect(uuidFromRaw(0xabcdeffffffffffffffffffffffffffffffffn)).toBe('ffffffff-ffff-ffff-ffff-ffffffffffff')
})

View file

@ -85,6 +85,6 @@ export function codeToFlag(code: string) {
}
export function uuidFromRaw(bits: bigint) {
let bitString = bits.toString(16).slice(-32).padStart(32, "0");
return `${bitString.slice(-32, -24)}-${bitString.slice(-24, -20)}-${bitString.slice(-20, -16)}-${bitString.slice(-16, -12)}-${bitString.slice(-12)}`;
let bitString = bits.toString(16).slice(-32).padStart(32, '0')
return `${bitString.slice(-32, -24)}-${bitString.slice(-24, -20)}-${bitString.slice(-20, -16)}-${bitString.slice(-16, -12)}-${bitString.slice(-12)}`
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,6 @@
import en from './en.json'
import fr from './fr.json'
import nl from './nl.json'
import ru from './ru.json'
import zh_hans from './zh-Hans.json'
import zh_hant from './zh-Hant.json'
@ -12,6 +13,7 @@ type LocaleDef = {
export enum Locales {
EN = 'en',
FR = 'fr',
NL = 'nl',
RU = 'ru',
ZH_HANS = 'zh-Hans',
ZH_HANT = 'zh-Hant'
@ -20,6 +22,7 @@ export enum Locales {
export const LOCALES: LocaleDef[] = [
{ title: 'English', value: Locales.EN },
{ title: 'Français', value: Locales.FR },
{ title: 'Nederlands', value: Locales.NL },
{ title: 'Русский', value: Locales.RU },
{ title: '简体中文', value: Locales.ZH_HANS },
{ title: '繁體中文', value: Locales.ZH_HANT }
@ -28,6 +31,7 @@ export const LOCALES: LocaleDef[] = [
export const messages: Record<Locales, any> = {
[Locales.EN]: en,
[Locales.FR]: fr,
[Locales.NL]: nl,
[Locales.RU]: ru,
[Locales.ZH_HANS]: zh_hans,
[Locales.ZH_HANT]: zh_hant

20
src/locales/nl.json Normal file
View file

@ -0,0 +1,20 @@
{
"common": {
"cancel": "Annuleren",
"close": "Sluiten",
"delete": "Verwijderen",
"disable": "Uitschakelen",
"emptyList": "Niets te zien hier!",
"none": "(Geen)",
"save": "Opslaan",
"selectAll": "Alles selecteren"
},
"constants": {
"bittorrentProtocols": {
"tcp_utp": "TCP en μTP"
},
"connectionStatus": {
"connected": "Verbonden"
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -16,12 +16,7 @@ import { useDisplay } from 'vuetify'
const { t } = useI18n()
const router = useRouter()
const display = useDisplay()
const {
currentPage: dashboardPage,
isSelectionMultiple,
selectedTorrents,
torrentCountString
} = storeToRefs(useDashboardStore())
const { currentPage: dashboardPage, isSelectionMultiple, selectedTorrents, torrentCountString } = storeToRefs(useDashboardStore())
const dashboardStore = useDashboardStore()
const dialogStore = useDialogStore()
const maindataStore = useMaindataStore()
@ -99,11 +94,7 @@ const torrentTitleFilter = computed({
}, 300)
})
const {
paginatedResults: paginatedTorrents,
currentPage,
pageCount
} = useArrayPagination(filteredTorrents, vuetorrentStore.paginationSize, dashboardPage)
const { paginatedResults: paginatedTorrents, currentPage, pageCount } = useArrayPagination(filteredTorrents, vuetorrentStore.paginationSize, dashboardPage)
const hasSearchFilter = computed(() => !!torrentStore.textFilter && torrentStore.textFilter.length > 0)
const isAllTorrentsSelected = computed(() => filteredTorrents.value.length <= selectedTorrents.value.length)
@ -272,32 +263,49 @@ function endPress() {
<v-row class="ma-0 pa-0 mb-2">
<v-expand-x-transition>
<v-card v-show="isSearchFilterVisible" color="transparent">
<v-text-field id="searchInput" v-model="torrentTitleFilter" :label="t('dashboard.searchInputLabel')" clearable
density="compact" hide-details prepend-inner-icon="mdi-magnify" rounded="pill" single-line
style="width: 200px" variant="solo" @click:clear="resetInput()" />
<v-text-field
id="searchInput"
v-model="torrentTitleFilter"
:label="t('dashboard.searchInputLabel')"
clearable
density="compact"
hide-details
prepend-inner-icon="mdi-magnify"
rounded="pill"
single-line
style="width: 200px"
variant="solo"
@click:clear="resetInput()" />
</v-card>
</v-expand-x-transition>
<v-tooltip :text="t('dashboard.toggleSearchFilter')" location="top">
<template v-slot:activator="{ props }">
<v-btn :icon="isSearchFilterVisible ? 'mdi-chevron-left-circle' : 'mdi-text-box-search'" v-bind="props"
variant="plain" @click="toggleSearchFilter()" />
<v-btn :icon="isSearchFilterVisible ? 'mdi-chevron-left-circle' : 'mdi-text-box-search'" v-bind="props" variant="plain" @click="toggleSearchFilter()" />
</template>
</v-tooltip>
<v-tooltip :text="t('dashboard.toggleSelectMode')" location="top">
<template v-slot:activator="{ props }">
<v-btn :icon="isSelectionMultiple ? 'mdi-checkbox-marked' : 'mdi-checkbox-blank-outline'" v-bind="props"
variant="plain" @click="toggleSelectMode" />
<v-btn :icon="isSelectionMultiple ? 'mdi-checkbox-marked' : 'mdi-checkbox-blank-outline'" v-bind="props" variant="plain" @click="toggleSelectMode" />
</template>
</v-tooltip>
<v-tooltip :text="t('dashboard.toggleSortOrder')" location="top">
<template v-slot:activator="{ props }">
<v-btn :icon="sortOptions.reverseOrder ? 'mdi-arrow-up-thin' : 'mdi-arrow-down-thin'" v-bind="props"
variant="plain" @click="sortOptions.reverseOrder = !sortOptions.reverseOrder" />
<v-btn
:icon="sortOptions.reverseOrder ? 'mdi-arrow-up-thin' : 'mdi-arrow-down-thin'"
v-bind="props"
variant="plain"
@click="sortOptions.reverseOrder = !sortOptions.reverseOrder" />
</template>
</v-tooltip>
<div class="pa-0" style="width: 10em">
<v-autocomplete v-model="sortOptions.sortBy" :items="torrentSortOptions" :label="t('dashboard.sortLabel')"
auto-select-first density="compact" hide-details variant="solo-filled" />
<v-autocomplete
v-model="sortOptions.sortBy"
:items="torrentSortOptions"
:label="t('dashboard.sortLabel')"
auto-select-first
density="compact"
hide-details
variant="solo-filled" />
</div>
<v-col class="align-center justify-center">
<span class="text-uppercase" style="float: right; font-size: 0.8em">
@ -310,8 +318,13 @@ function endPress() {
<v-card v-show="isSelectionMultiple" color="transparent">
<v-tooltip :text="t('common.selectAll')" location="bottom">
<template v-slot:activator="{ props }">
<v-btn :icon="isAllTorrentsSelected ? 'mdi-checkbox-marked' : 'mdi-checkbox-blank-outline'"
class="text-grey" color="transparent" style="left: -8px" v-bind="props" @click="toggleSelectAll" />
<v-btn
:icon="isAllTorrentsSelected ? 'mdi-checkbox-marked' : 'mdi-checkbox-blank-outline'"
class="text-grey"
color="transparent"
style="left: -8px"
v-bind="props"
@click="toggleSelectAll" />
</template>
</v-tooltip>
<span class="text-grey">{{ t('dashboard.selectAll') }}</span>
@ -324,21 +337,28 @@ function endPress() {
<div v-else>
<v-list id="torrentList" class="pa-0" color="transparent">
<v-list-item v-if="vuetorrentStore.isPaginationOnTop && !vuetorrentStore.isInfiniteScrollActive">
<v-pagination v-model="currentPage" :length="pageCount" next-icon="mdi-menu-right" prev-icon="mdi-menu-left"
@input="scrollToTop" />
<v-pagination v-model="currentPage" :length="pageCount" next-icon="mdi-menu-right" prev-icon="mdi-menu-left" @input="scrollToTop" />
</v-list-item>
<v-list-item v-for="torrent in paginatedTorrents" :id="`torrent-${torrent.hash}`"
:class="display.mobile ? 'mb-2' : 'mb-4'" class="pa-0" @contextmenu="onRightClick($event, torrent)"
@touchcancel="endPress" @touchend="endPress" @touchmove="endPress"
@touchstart="startPress($event, torrent)"
@dblclick.prevent="goToInfo(torrent.hash)">
<v-list-item
v-for="torrent in paginatedTorrents"
:id="`torrent-${torrent.hash}`"
:class="display.mobile ? 'mb-2' : 'mb-4'"
class="pa-0"
@contextmenu="onRightClick($event, torrent)"
@touchcancel="endPress"
@touchend="endPress"
@touchmove="endPress"
@touchstart="startPress($event, torrent)"
@dblclick.prevent="goToInfo(torrent.hash)">
<div class="d-flex align-center">
<v-expand-x-transition>
<v-card v-show="isSelectionMultiple" class="mr-3" color="transparent">
<v-btn
:icon="dashboardStore.isTorrentInSelection(torrent.hash) ? 'mdi-checkbox-marked' : 'mdi-checkbox-blank-outline'"
color="transparent" variant="flat" @click="toggleSelectTorrent(torrent.hash)" />
color="transparent"
variant="flat"
@click="toggleSelectTorrent(torrent.hash)" />
</v-card>
</v-expand-x-transition>
<Torrent :torrent="torrent" />
@ -346,8 +366,7 @@ function endPress() {
</v-list-item>
<v-list-item v-if="!vuetorrentStore.isPaginationOnTop && !vuetorrentStore.isInfiniteScrollActive">
<v-pagination v-model="currentPage" :length="pageCount" next-icon="mdi-menu-right" prev-icon="mdi-menu-left"
@input="scrollToTop" />
<v-pagination v-model="currentPage" :length="pageCount" next-icon="mdi-menu-right" prev-icon="mdi-menu-left" @input="scrollToTop" />
</v-list-item>
</v-list>
</div>

View file

@ -47,10 +47,7 @@ const redirectOnSuccess = () => {
onMounted(async () => {
if (route.query.username && route.query.password) {
await authStore.login(
route.query.username as string,
route.query.password as string
)
await authStore.login(route.query.username as string, route.query.password as string)
}
})
@ -68,8 +65,7 @@ watchEffect(() => {
<v-card-subtitle>{{ t('login.subtitle') }}</v-card-subtitle>
<v-card-text>
<v-form v-model="rulesOk" @submit.prevent="login">
<v-text-field v-model="loginForm.username" :label="t('login.username')" autofocus :rules="rules.username"
@keydown.enter.prevent="login" variant="outlined">
<v-text-field v-model="loginForm.username" :label="t('login.username')" autofocus :rules="rules.username" @keydown.enter.prevent="login" variant="outlined">
<template v-slot:prepend>
<v-icon color="accent" icon="mdi-account" />
</template>

View file

@ -41,7 +41,7 @@ const goHome = () => {
router.push({ name: 'dashboard' })
}
const getLogTypeClassName = (log: Log) => {
return `logtype-${ LogType[log?.type]?.toLowerCase() }`
return `logtype-${LogType[log?.type]?.toLowerCase()}`
}
const getLogTypeName = (log: Log) => {
return LogType[log.type]
@ -91,13 +91,11 @@ onUnmounted(() => {
<v-list-item>
<v-row>
<v-col cols="6">
<v-select v-model="logTypeFilter" :items="logTypeOptions" :label="$t('logs.filters.type')" hide-details
multiple chips>
<v-select v-model="logTypeFilter" :items="logTypeOptions" :label="$t('logs.filters.type')" hide-details multiple chips>
<template v-slot:prepend-item>
<v-list-item :title="$t('common.selectAll')" @click="toggleSelectAll">
<template v-slot:prepend>
<v-checkbox-btn :indeterminate="someTypesSelected && !allTypesSelected"
:model-value="someTypesSelected" />
<v-checkbox-btn :indeterminate="someTypesSelected && !allTypesSelected" :model-value="someTypesSelected" />
</template>
</v-list-item>
<v-divider />
@ -106,8 +104,7 @@ onUnmounted(() => {
</v-col>
<v-col cols="6">
<v-select v-model="sortBy" :items="headers" :label="$t('logs.filters.sortBy.label')" hide-details multiple
chips />
<v-select v-model="sortBy" :items="headers" :label="$t('logs.filters.sortBy.label')" hide-details multiple chips />
</v-col>
</v-row>
</v-list-item>

View file

@ -17,4 +17,4 @@ onBeforeMount(async () => {
}
await router.push({ name: 'dashboard' })
})
</script>
</script>

View file

@ -90,7 +90,8 @@ onUnmounted(() => {
<v-row align="center" justify="center" no-gutters>
<v-col>
<h1 class="subtitle-1 ml-2" style="font-size: 1.6em !important">
{{ t('rssArticles.title') }} </h1>
{{ t('rssArticles.title') }}
</h1>
</v-col>
<v-col>
<div class="d-flex justify-end">
@ -124,26 +125,16 @@ onUnmounted(() => {
<template v-for="(article, index) in paginatedResults">
<v-divider v-if="index > 0" color="white" />
<v-list-item :class="{ 'rss-read': article.isRead }" @click="showDescription(article)"
@contextmenu="markAsRead(article)">
<v-list-item :class="{ 'rss-read': article.isRead }" @click="showDescription(article)" @contextmenu="markAsRead(article)">
<div class="d-flex">
<div>
<v-list-item-title class="wrap-anywhere" style="white-space: unset">{{
article.title
}}
</v-list-item-title>
<v-list-item-title class="wrap-anywhere" style="white-space: unset">{{ article.title }} </v-list-item-title>
<v-list-item-subtitle class="d-block">
<div>{{ article.parsedDate.toLocaleString() }}</div>
<div>{{
t('rssArticles.item.feedName', { name: rssStore.getFeedNames(article.id).join(' | ') })
}}
</div>
<div>{{ t('rssArticles.item.feedName', { name: rssStore.getFeedNames(article.id).join(' | ') }) }}</div>
<div v-if="article.author">{{ t('rssArticles.item.author', { author: article.author }) }}</div>
<div v-if="article.category">{{
t('rssArticles.item.category', { category: article.category })
}}
</div>
<div v-if="article.category">{{ t('rssArticles.item.category', { category: article.category }) }}</div>
</v-list-item-subtitle>
</div>

View file

@ -53,10 +53,10 @@ const plugins = computed(() => {
]
searchEngineStore.searchPlugins
.filter((plugin: SearchPlugin) => plugin.enabled)
.forEach((plugin: SearchPlugin) => {
plugins.push({ title: plugin.name, value: plugin.name })
})
.filter((plugin: SearchPlugin) => plugin.enabled)
.forEach((plugin: SearchPlugin) => {
plugins.push({ title: plugin.name, value: plugin.name })
})
return plugins
})
@ -173,8 +173,7 @@ onBeforeUnmount(() => {
<v-spacer />
<v-btn icon="mdi-plus-circle-outline" variant="plain" color="accent" @click="createNewTab" />
<v-btn icon="mdi-minus-circle-outline" variant="plain" color="error" :disabled="searchData.length === 1"
@click="deleteTab" />
<v-btn icon="mdi-minus-circle-outline" variant="plain" color="error" :disabled="searchData.length === 1" @click="deleteTab" />
</v-container>
</v-row>
@ -182,9 +181,16 @@ onBeforeUnmount(() => {
<v-list-item>
<v-row class="mt-1">
<v-col cols="12" md="6">
<HistoryField v-model="selectedTab.query" :history-key="HistoryKey.SEARCH_ENGINE_QUERY" ref="queryInput"
autofocus density="compact" hide-details clearable
:label="$t('searchEngine.query')" @keydown.enter.prevent="runNewSearch" />
<HistoryField
v-model="selectedTab.query"
:history-key="HistoryKey.SEARCH_ENGINE_QUERY"
ref="queryInput"
autofocus
density="compact"
hide-details
clearable
:label="$t('searchEngine.query')"
@keydown.enter.prevent="runNewSearch" />
</v-col>
<v-col cols="6" sm="5" md="2">
@ -222,14 +228,11 @@ onBeforeUnmount(() => {
<v-divider class="my-3" />
<v-list-item>
<v-data-table :headers="headers" :items="filteredResults"
:footer-props="{ itemsPerPageOptions: [10, 25, 50, 100, -1] }"
:items-per-page.sync="selectedTab.itemsPerPage">
<v-data-table :headers="headers" :items="filteredResults" :footer-props="{ itemsPerPageOptions: [10, 25, 50, 100, -1] }" :items-per-page.sync="selectedTab.itemsPerPage">
<template v-slot:top>
<v-row>
<v-col cols="12">
<v-text-field v-model="selectedTab.filters.title" density="compact" hide-details
:label="$t('searchEngine.filters.title.label')" />
<v-text-field v-model="selectedTab.filters.title" density="compact" hide-details :label="$t('searchEngine.filters.title.label')" />
</v-col>
</v-row>
</template>

View file

@ -101,7 +101,6 @@ watchEffect(() => {
onMounted(() => {
document.addEventListener('keydown', handleKeyboardShortcut)
updateTabHandle()
})
onBeforeUnmount(() => {
document.removeEventListener('keydown', handleKeyboardShortcut)
@ -126,15 +125,14 @@ onBeforeUnmount(() => {
<v-row class="ma-0 pa-0">
<v-tabs v-model="tab" bg-color="primary" grow show-arrows>
<v-tab v-for="{text, value} in tabs" :key="value" :value="value" :href="`#/settings/${value}`" :text="text" />
<v-tab v-for="{ text, value } in tabs" :key="value" :value="value" :href="`#/settings/${value}`" :text="text" />
</v-tabs>
</v-row>
<v-window v-model="tab" :touch="false">
<v-window-item value="vuetorrent">
<v-tabs v-model="innerTabV" grow color="accent" show-arrows>
<v-tab v-for="{text, value} in tabsV" :value="value" :text="text"
:href="`#/settings/vuetorrent/${value}`" :class="{ 'text-accent': innerTabV === value }" />
<v-tab v-for="{ text, value } in tabsV" :value="value" :text="text" :href="`#/settings/vuetorrent/${value}`" :class="{ 'text-accent': innerTabV === value }" />
<!-- the class attribute is a workaround for https://github.com/vuetifyjs/vuetify/issues/18756 -->
</v-tabs>
@ -170,8 +168,7 @@ onBeforeUnmount(() => {
<v-window-item value="rss">
<v-tabs v-model="innerTabR" grow color="accent" bg-color="transparent">
<v-tab v-for="{text, value} in tabsR" :key="value" :value="value" :text="text"
:href="`#/settings/rss/${value}`" :class="{ 'text-accent': innerTabR === value }" />
<v-tab v-for="{ text, value } in tabsR" :key="value" :value="value" :text="text" :href="`#/settings/rss/${value}`" :class="{ 'text-accent': innerTabR === value }" />
<!-- the class attribute is a workaround for https://github.com/vuetifyjs/vuetify/issues/18756 -->
</v-tabs>
<v-window v-model="innerTabR" :touch="false">

View file

@ -2,7 +2,6 @@ import { routes } from '@/pages'
import { useAuthStore } from '@/stores'
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(process.env.BASE_URL),
routes

View file

@ -91,7 +91,27 @@ export default createVuetify({
theme: {
defaultTheme: Theme.LIGHT,
variations: {
colors: ['torrent-error', 'torrent-missingFiles', 'torrent-uploading', 'torrent-forcedUP', 'torrent-pausedUP', 'torrent-queuedUP', 'torrent-stalledUP', 'torrent-checkingUP', 'torrent-allocating', 'torrent-downloading', 'torrent-forcedDL', 'torrent-metaDL', 'torrent-pausedDL', 'torrent-queuedDL', 'torrent-stalledDL', 'torrent-checkingDL', 'torrent-checkingResumeData', 'torrent-moving', 'torrent-unknown'],
colors: [
'torrent-error',
'torrent-missingFiles',
'torrent-uploading',
'torrent-forcedUP',
'torrent-pausedUP',
'torrent-queuedUP',
'torrent-stalledUP',
'torrent-checkingUP',
'torrent-allocating',
'torrent-downloading',
'torrent-forcedDL',
'torrent-metaDL',
'torrent-pausedDL',
'torrent-queuedDL',
'torrent-stalledDL',
'torrent-checkingDL',
'torrent-checkingResumeData',
'torrent-moving',
'torrent-unknown'
],
lighten: 3,
darken: 3
},

View file

@ -3,7 +3,9 @@ import { AddTorrentPayload } from '@/types/qbit/payloads'
import { defineStore } from 'pinia'
import { computed, reactive, ref } from 'vue'
export const useAddTorrentStore = defineStore('addTorrents', () => {
export const useAddTorrentStore = defineStore(
'addTorrents',
() => {
const preferenceStore = usePreferenceStore()
const isFirstInit = ref(true)
@ -80,4 +82,5 @@ export const useAddTorrentStore = defineStore('addTorrents', () => {
}
]
}
})
}
)

View file

@ -5,118 +5,117 @@ import { useI18n } from 'vue-i18n'
import { useTorrentStore } from './torrents'
import { useVueTorrentStore } from './vuetorrent'
export const useDashboardStore = defineStore(
'dashboard',
() => {
const currentPage = ref(1)
const isSelectionMultiple = ref(false)
const selectedTorrents = ref<string[]>([])
const latestSelectedTorrent = ref<string>()
export const useDashboardStore = defineStore('dashboard', () => {
const currentPage = ref(1)
const isSelectionMultiple = ref(false)
const selectedTorrents = ref<string[]>([])
const latestSelectedTorrent = ref<string>()
const { t } = useI18n()
const torrentStore = useTorrentStore()
const vuetorrentStore = useVueTorrentStore()
const { t } = useI18n()
const torrentStore = useTorrentStore()
const vuetorrentStore = useVueTorrentStore()
const torrentCountString = computed(() => {
if (selectedTorrents.value.length) {
const selectedSize = selectedTorrents.value
const torrentCountString = computed(() => {
if (selectedTorrents.value.length) {
const selectedSize = selectedTorrents.value
.map(hash => torrentStore.getTorrentByHash(hash))
.filter(torrent => torrent !== undefined)
.map(torrent => torrent!.size)
.reduce((partial, size) => partial + size, 0)
return t('dashboard.selectedTorrentsCount', {
count: selectedTorrents.value.length,
total: torrentStore.filteredTorrents.length,
size: formatData(selectedSize, vuetorrentStore.useBinarySize)
})
} else {
return t('dashboard.torrentsCount', torrentStore.filteredTorrents.length)
}
})
return t('dashboard.selectedTorrentsCount', {
count: selectedTorrents.value.length,
total: torrentStore.filteredTorrents.length,
size: formatData(selectedSize, vuetorrentStore.useBinarySize)
})
} else {
return t('dashboard.torrentsCount', torrentStore.filteredTorrents.length)
}
})
function isTorrentInSelection(hash: string) {
return selectedTorrents.value.includes(hash)
function isTorrentInSelection(hash: string) {
return selectedTorrents.value.includes(hash)
}
function selectTorrent(hash: string) {
if (!isTorrentInSelection(hash)) {
selectedTorrents.value.push(hash)
}
function selectTorrent(hash: string) {
if (!isTorrentInSelection(hash)) {
selectedTorrents.value.push(hash)
}
latestSelectedTorrent.value = hash
}
latestSelectedTorrent.value = hash
function selectTorrents(...hashes: string[]) {
isSelectionMultiple.value = true
hashes.forEach(selectTorrent)
}
function unselectTorrent(hash: string) {
const index = selectedTorrents.value.indexOf(hash)
if (index >= 0) {
selectedTorrents.value.splice(index, 1)
}
}
function selectTorrents(...hashes: string[]) {
isSelectionMultiple.value = true
hashes.forEach(selectTorrent)
function toggleSelect(hash: string) {
if (isTorrentInSelection(hash)) {
unselectTorrent(hash)
} else {
selectTorrent(hash)
}
}
function unselectTorrent(hash: string) {
const index = selectedTorrents.value.indexOf(hash)
if (index >= 0) {
selectedTorrents.value.splice(index, 1)
}
function spanTorrentSelection(endHash: string) {
if (!latestSelectedTorrent.value) return
const latestIndex = torrentStore.getTorrentIndexByHash(latestSelectedTorrent.value)
const endIndex = torrentStore.getTorrentIndexByHash(endHash)
const start = Math.min(endIndex, latestIndex)
const end = Math.max(endIndex, latestIndex)
const hashes = torrentStore.filteredTorrents.slice(start, end + 1).map(t => t.hash)
selectTorrents(...hashes)
}
function selectAllTorrents() {
isSelectionMultiple.value = true
selectedTorrents.value.splice(0, selectedTorrents.value.length, ...torrentStore.torrents.map(t => t.hash))
latestSelectedTorrent.value = torrentStore.torrents[0]?.hash
}
function unselectAllTorrents() {
selectedTorrents.value = []
}
watch(selectedTorrents, newValue => {
if (newValue.length === 0) {
latestSelectedTorrent.value = undefined
}
})
function toggleSelect(hash: string) {
if (isTorrentInSelection(hash)) {
unselectTorrent(hash)
} else {
selectTorrent(hash)
}
}
function spanTorrentSelection(endHash: string) {
if (!latestSelectedTorrent.value) return
const latestIndex = torrentStore.getTorrentIndexByHash(latestSelectedTorrent.value)
const endIndex = torrentStore.getTorrentIndexByHash(endHash)
const start = Math.min(endIndex, latestIndex)
const end = Math.max(endIndex, latestIndex)
const hashes = torrentStore.filteredTorrents.slice(start, end + 1).map(t => t.hash)
selectTorrents(...hashes)
}
function selectAllTorrents() {
isSelectionMultiple.value = true
selectedTorrents.value.splice(0, selectedTorrents.value.length, ...torrentStore.torrents.map(t => t.hash))
latestSelectedTorrent.value = torrentStore.torrents[0]?.hash
}
function unselectAllTorrents() {
selectedTorrents.value = []
}
watch(selectedTorrents, newValue => {
if (newValue.length === 0) {
latestSelectedTorrent.value = undefined
}
})
watch(() => torrentStore.filteredTorrents, newValue => {
watch(
() => torrentStore.filteredTorrents,
newValue => {
const pageCount = Math.ceil(newValue.length / vuetorrentStore.paginationSize)
if (pageCount < currentPage.value) {
currentPage.value = Math.max(1, pageCount)
}
})
return {
currentPage,
isSelectionMultiple,
selectedTorrents,
latestSelectedTorrent,
torrentCountString,
isTorrentInSelection,
selectTorrent,
selectTorrents,
unselectTorrent,
spanTorrentSelection,
selectAllTorrents,
unselectAllTorrents,
toggleSelect
}
},
)
)
return {
currentPage,
isSelectionMultiple,
selectedTorrents,
latestSelectedTorrent,
torrentCountString,
isTorrentInSelection,
selectTorrent,
selectTorrents,
unselectTorrent,
spanTorrentSelection,
selectAllTorrents,
unselectAllTorrents,
toggleSelect
}
})

View file

@ -4,44 +4,48 @@ import { reactive, ref } from 'vue'
type History = Partial<Record<HistoryKey, string[]>>
export const useHistoryStore = defineStore('history', () => {
const _history = reactive<History>({})
const historySize = ref(3)
export const useHistoryStore = defineStore(
'history',
() => {
const _history = reactive<History>({})
const historySize = ref(3)
function pushValueToHistory(key: HistoryKey, value: string) {
const historyValue = getHistory(key)
historyValue.splice(0, 0, value)
function pushValueToHistory(key: HistoryKey, value: string) {
const historyValue = getHistory(key)
historyValue.splice(0, 0, value)
const valueIndex = historyValue.indexOf(value, 1)
if (valueIndex !== -1) {
historyValue.splice(valueIndex, 1)
}
if (historyValue.length > historySize.value) {
historyValue.splice(historySize.value, historyValue.length - historySize.value)
}
_history[key] = historyValue
}
function getHistory(key: HistoryKey) {
return _history[key] || []
}
return {
_history,
historySize,
pushValueToHistory,
getHistory
}
}, {
persist: {
enabled: true,
strategies: [
{
storage: localStorage,
key: 'vuetorrent_history'
const valueIndex = historyValue.indexOf(value, 1)
if (valueIndex !== -1) {
historyValue.splice(valueIndex, 1)
}
]
if (historyValue.length > historySize.value) {
historyValue.splice(historySize.value, historyValue.length - historySize.value)
}
_history[key] = historyValue
}
function getHistory(key: HistoryKey) {
return _history[key] || []
}
return {
_history,
historySize,
pushValueToHistory,
getHistory
}
},
{
persist: {
enabled: true,
strategies: [
{
storage: localStorage,
key: 'vuetorrent_history'
}
]
}
}
})
)

View file

@ -28,4 +28,4 @@ export {
useSearchEngineStore,
useTorrentStore,
useVueTorrentStore
}
}

View file

@ -122,10 +122,10 @@ export const useMaindataStore = defineStore('maindata', () => {
if (vueTorrentStore.showTrackerFilter) {
trackers.value = data
.map(t => t.tracker)
.map(url => extractHostname(url))
.filter((domain, index, self) => index === self.indexOf(domain) && domain)
.sort()
.map(t => t.tracker)
.map(url => extractHostname(url))
.filter((domain, index, self) => index === self.indexOf(domain) && domain)
.sort()
}
// update torrents

View file

@ -19,7 +19,7 @@ export const useRssStore = defineStore(
})
const unreadArticles = computed(() => _articles.value.filter(article => !article.isRead))
const articles = computed(() => filters.unread ? unreadArticles.value : _articles.value)
const articles = computed(() => (filters.unread ? unreadArticles.value : _articles.value))
async function refreshFeed(feedName: string) {
await qbit.refreshFeed(feedName)

View file

@ -7,194 +7,198 @@ import { Torrent } from '@/types/vuetorrent'
import { defineStore } from 'pinia'
import { computed, MaybeRefOrGetter, reactive, ref, toValue } from 'vue'
export const useTorrentStore = defineStore('torrents', () => {
const torrents = ref<Torrent[]>([])
export const useTorrentStore = defineStore(
'torrents',
() => {
const torrents = ref<Torrent[]>([])
const isTextFilterActive = ref(true)
const isStatusFilterActive = ref(true)
const isCategoryFilterActive = ref(true)
const isTagFilterActive = ref(true)
const isTrackerFilterActive = ref(true)
const isTextFilterActive = ref(true)
const isStatusFilterActive = ref(true)
const isCategoryFilterActive = ref(true)
const isTagFilterActive = ref(true)
const isTrackerFilterActive = ref(true)
const textFilter = ref('')
const statusFilter = ref<TorrentState[]>([])
const categoryFilter = ref<string[]>([])
const tagFilter = ref<(string | null)[]>([])
const trackerFilter = ref<(string | null)[]>([])
const textFilter = ref('')
const statusFilter = ref<TorrentState[]>([])
const categoryFilter = ref<string[]>([])
const tagFilter = ref<(string | null)[]>([])
const trackerFilter = ref<(string | null)[]>([])
const torrentsWithFilters = computed(() => {
return torrents.value.filter(torrent => {
if (statusFilter.value.length > 0 && isStatusFilterActive.value && !statusFilter.value.includes(torrent.state)) return false
if (categoryFilter.value.length > 0 && isCategoryFilterActive.value && !categoryFilter.value.includes(torrent.category)) return false
if (tagFilter.value.length > 0 && isTagFilterActive.value) {
if (torrent.tags.length === 0 && tagFilter.value.includes(null)) return true
if (!torrent.tags.some(tag => tagFilter.value.includes(tag))) return false
}
if (trackerFilter.value.length > 0 && isTrackerFilterActive.value && !trackerFilter.value.includes(extractHostname(torrent.tracker))) return false
return true
})
})
const filteredTorrents = computed(() => searchQuery.results.value)
const sortOptions = reactive({
isCustomSortEnabled: false,
sortBy: SortOptions.DEFAULT,
reverseOrder: false
})
const getTorrentsPayload = computed<GetTorrentPayload>(() => {
return {
sort: sortOptions.isCustomSortEnabled ? SortOptions.DEFAULT : sortOptions.sortBy,
reverse: sortOptions.reverseOrder
}
})
const searchQuery = useSearchQuery(
torrentsWithFilters,
() => isTextFilterActive.value ? textFilter.value : null,
torrent => torrent.name,
results => {
if (sortOptions.isCustomSortEnabled) {
if (sortOptions.sortBy === 'priority') {
results.sort((a, b) => {
if (a.priority > 0 && b.priority > 0) return a.priority - b.priority
else if (a.priority <= 0 && b.priority <= 0) return a.added_on - b.added_on
else if (a.priority <= 0) return 1
else return -1
})
} else {
results.sort((a, b) => a[sortOptions.sortBy] - b[sortOptions.sortBy] || a.added_on - b.added_on)
const torrentsWithFilters = computed(() => {
return torrents.value.filter(torrent => {
if (statusFilter.value.length > 0 && isStatusFilterActive.value && !statusFilter.value.includes(torrent.state)) return false
if (categoryFilter.value.length > 0 && isCategoryFilterActive.value && !categoryFilter.value.includes(torrent.category)) return false
if (tagFilter.value.length > 0 && isTagFilterActive.value) {
if (torrent.tags.length === 0 && tagFilter.value.includes(null)) return true
if (!torrent.tags.some(tag => tagFilter.value.includes(tag))) return false
}
if (sortOptions.reverseOrder) results.reverse()
if (trackerFilter.value.length > 0 && isTrackerFilterActive.value && !trackerFilter.value.includes(extractHostname(torrent.tracker))) return false
return true
})
})
const filteredTorrents = computed(() => searchQuery.results.value)
const sortOptions = reactive({
isCustomSortEnabled: false,
sortBy: SortOptions.DEFAULT,
reverseOrder: false
})
const getTorrentsPayload = computed<GetTorrentPayload>(() => {
return {
sort: sortOptions.isCustomSortEnabled ? SortOptions.DEFAULT : sortOptions.sortBy,
reverse: sortOptions.reverseOrder
}
return results
})
const searchQuery = useSearchQuery(
torrentsWithFilters,
() => (isTextFilterActive.value ? textFilter.value : null),
torrent => torrent.name,
results => {
if (sortOptions.isCustomSortEnabled) {
if (sortOptions.sortBy === 'priority') {
results.sort((a, b) => {
if (a.priority > 0 && b.priority > 0) return a.priority - b.priority
else if (a.priority <= 0 && b.priority <= 0) return a.added_on - b.added_on
else if (a.priority <= 0) return 1
else return -1
})
} else {
results.sort((a, b) => a[sortOptions.sortBy] - b[sortOptions.sortBy] || a.added_on - b.added_on)
}
if (sortOptions.reverseOrder) results.reverse()
}
return results
}
)
async function setTorrentCategory(hashes: string[], category: string) {
await qbit.setCategory(hashes, category)
}
)
async function setTorrentCategory(hashes: string[], category: string) {
await qbit.setCategory(hashes, category)
}
async function addTorrentTags(hashes: string[], tags: string[]) {
await qbit.addTorrentTag(hashes, tags)
}
async function addTorrentTags(hashes: string[], tags: string[]) {
await qbit.addTorrentTag(hashes, tags)
}
async function removeTorrentTags(hashes: string[], tags: string[]) {
await qbit.removeTorrentTag(hashes, tags)
}
async function removeTorrentTags(hashes: string[], tags: string[]) {
await qbit.removeTorrentTag(hashes, tags)
}
function getTorrentByHash(hash: string) {
return torrents.value.find(t => t.hash === hash)
}
function getTorrentByHash(hash: string) {
return torrents.value.find(t => t.hash === hash)
}
function getTorrentIndexByHash(hash: string) {
return filteredTorrents.value.findIndex(t => t.hash === hash)
}
function getTorrentIndexByHash(hash: string) {
return filteredTorrents.value.findIndex(t => t.hash === hash)
}
async function deleteTorrents(hashes: string[], deleteWithFiles: boolean) {
await qbit.deleteTorrents(hashes, deleteWithFiles)
}
async function deleteTorrents(hashes: string[], deleteWithFiles: boolean) {
await qbit.deleteTorrents(hashes, deleteWithFiles)
}
async function moveTorrents(mode: 'dl' | 'save', hashes: string[], newPath: string) {
switch (mode) {
case 'dl':
return await qbit.setTorrentDownloadPath(hashes, newPath)
case 'save':
return await qbit.setTorrentSavePath(hashes, newPath)
}
}
async function moveTorrents(mode: 'dl' | 'save', hashes: string[], newPath: string) {
switch (mode) {
case 'dl':
return await qbit.setTorrentDownloadPath(hashes, newPath)
case 'save':
return await qbit.setTorrentSavePath(hashes, newPath)
async function addTorrents(torrents: File[], urls: string, payload: AddTorrentPayload) {
return await qbit.addTorrents(torrents, urls, payload)
}
async function getTorrentProperties(hash: string) {
return await qbit.getTorrentProperties(hash)
}
async function renameTorrent(hash: string, newName: string) {
await qbit.setTorrentName(hash, newName)
}
async function resumeTorrents(hashes: MaybeRefOrGetter<string[]>) {
await qbit.resumeTorrents(toValue(hashes))
}
async function forceResumeTorrents(hashes: MaybeRefOrGetter<string[]>) {
await qbit.forceStartTorrents(toValue(hashes))
}
async function pauseTorrents(hashes: MaybeRefOrGetter<string[]>) {
await qbit.pauseTorrents(toValue(hashes))
}
async function recheckTorrents(hashes: MaybeRefOrGetter<string[]>) {
await qbit.recheckTorrents(toValue(hashes))
}
async function setTorrentPriority(hashes: string[], priority: 'increasePrio' | 'decreasePrio' | 'topPrio' | 'bottomPrio') {
await qbit.setTorrentPriority(hashes, priority)
}
async function exportTorrent(hash: string) {
return await qbit.exportTorrent(hash)
}
return {
torrents,
isTextFilterActive,
isStatusFilterActive,
isCategoryFilterActive,
isTagFilterActive,
isTrackerFilterActive,
textFilter,
statusFilter,
categoryFilter,
tagFilter,
trackerFilter,
torrentsWithFilters,
filteredTorrents,
sortOptions,
getTorrentsPayload,
searchQuery,
setTorrentCategory,
addTorrentTags,
removeTorrentTags,
getTorrentByHash,
getTorrentIndexByHash,
deleteTorrents,
moveTorrents,
addTorrents,
getTorrentProperties,
renameTorrent,
resumeTorrents,
forceResumeTorrents,
pauseTorrents,
recheckTorrents,
setTorrentPriority,
exportTorrent
}
},
{
persist: {
enabled: true,
strategies: [
{
storage: localStorage,
key: 'vuetorrent_torrents',
paths: [
'isTextFilterActive',
'textFilter',
'isStatusFilterActive',
'statusFilter',
'isCategoryFilterActive',
'categoryFilter',
'isTagFilterActive',
'tagFilter',
'isTrackerFilterActive',
'trackerFilter',
'sortOptions'
]
}
]
}
}
async function addTorrents(torrents: File[], urls: string, payload: AddTorrentPayload) {
return await qbit.addTorrents(torrents, urls, payload)
}
async function getTorrentProperties(hash: string) {
return await qbit.getTorrentProperties(hash)
}
async function renameTorrent(hash: string, newName: string) {
await qbit.setTorrentName(hash, newName)
}
async function resumeTorrents(hashes: MaybeRefOrGetter<string[]>) {
await qbit.resumeTorrents(toValue(hashes))
}
async function forceResumeTorrents(hashes: MaybeRefOrGetter<string[]>) {
await qbit.forceStartTorrents(toValue(hashes))
}
async function pauseTorrents(hashes: MaybeRefOrGetter<string[]>) {
await qbit.pauseTorrents(toValue(hashes))
}
async function recheckTorrents(hashes: MaybeRefOrGetter<string[]>) {
await qbit.recheckTorrents(toValue(hashes))
}
async function setTorrentPriority(hashes: string[], priority: 'increasePrio' | 'decreasePrio' | 'topPrio' | 'bottomPrio') {
await qbit.setTorrentPriority(hashes, priority)
}
async function exportTorrent(hash: string) {
return await qbit.exportTorrent(hash)
}
return {
torrents,
isTextFilterActive,
isStatusFilterActive,
isCategoryFilterActive,
isTagFilterActive,
isTrackerFilterActive,
textFilter,
statusFilter,
categoryFilter,
tagFilter,
trackerFilter,
torrentsWithFilters,
filteredTorrents,
sortOptions,
getTorrentsPayload,
searchQuery,
setTorrentCategory,
addTorrentTags,
removeTorrentTags,
getTorrentByHash,
getTorrentIndexByHash,
deleteTorrents,
moveTorrents,
addTorrents,
getTorrentProperties,
renameTorrent,
resumeTorrents,
forceResumeTorrents,
pauseTorrents,
recheckTorrents,
setTorrentPriority,
exportTorrent
}
}, {
persist: {
enabled: true,
strategies: [
{
storage: localStorage,
key: 'vuetorrent_torrents',
paths: [
'isTextFilterActive',
'textFilter',
'isStatusFilterActive',
'statusFilter',
'isCategoryFilterActive',
'categoryFilter',
'isTagFilterActive',
'tagFilter',
'isTrackerFilterActive',
'trackerFilter',
'sortOptions'
]
}
]
}
})
)

View file

@ -1,11 +1,4 @@
import {
DashboardProperty,
PropertyData,
propsData,
propsMetadata,
TitleOptions,
TorrentProperty
} from '@/constants/vuetorrent'
import { DashboardProperty, PropertyData, propsData, propsMetadata, TitleOptions, TorrentProperty } from '@/constants/vuetorrent'
import { Theme } from '@/plugins/vuetify'
import { useMediaQuery } from '@vueuse/core'
import { defineStore } from 'pinia'

View file

@ -116,7 +116,7 @@ export default interface AppPreferences {
dyndns_username: string
/** Port used for embedded tracker */
embedded_tracker_port: number
/** Enable port forwarding for embedded tracker */
/** Enable port forwarding for embedded tracker */
embedded_tracker_port_forwarding: boolean
/** True enables coalesce reads & writes */
enable_coalesce_read_write: boolean

View file

@ -52,8 +52,8 @@ export default interface Torrent {
upspeed: number
// computed
avgDownloadSpeed: number,
avgUploadSpeed: number,
avgDownloadSpeed: number
avgUploadSpeed: number
globalSpeed: number
globalVolume: number