Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0048d71b74 | |||
| c633347ecc | |||
| ecd6a0ec32 | |||
| 30507d9d01 | |||
| 6245a794d5 | |||
| 5c822e4890 | |||
| cab06fe43f | |||
| b4ff5f3351 | |||
| 0834802481 | |||
| 3afd74dcbf | |||
| b1a0bdfbb6 | |||
| 6d38bc5e1d | |||
| 5ba1ba15c9 | |||
| db05d2abfb | |||
| 1840c4e39a | |||
| c5a3f424d6 | |||
| 04aa6b81b5 | |||
| 5ff3b9b996 | |||
| 228e4a61a4 | |||
| df38ea5413 | |||
| 709f6ca6ca | |||
| b012fc29e4 | |||
| bdad723aef | |||
| f97d46cec3 | |||
| 7381eaef57 | |||
| 72c922b311 | |||
| fd14a8152c | |||
| 5cb60c317d | |||
| 847de75713 | |||
| 58723cf3e8 | |||
| c613168bfb | |||
| 6f83de77f2 | |||
| 3f6ef3beb4 | |||
| 14e50c1465 | |||
| 0aa9fdca14 |
@@ -76,15 +76,15 @@ jobs:
|
||||
RUSTDOCFLAGS: --cfg docsrs
|
||||
msrv:
|
||||
# check that we can build using the minimal rust version that is specified by this crate
|
||||
name: 1.82.0 / check
|
||||
name: 1.85.0 / check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install 1.82.0
|
||||
- name: Install 1.85.0
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: 1.82.0
|
||||
toolchain: 1.85.0
|
||||
|
||||
- name: cargo +1.82.0 check
|
||||
- name: cargo +1.85.0 check
|
||||
run: cargo check
|
||||
|
||||
@@ -309,6 +309,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
overwrite: true
|
||||
|
||||
publish-chocolatey-package:
|
||||
needs: [publish-github-release]
|
||||
@@ -429,6 +430,11 @@ jobs:
|
||||
path: artifacts
|
||||
merge-multiple: true
|
||||
|
||||
- name: Ensure repository is up-to-date
|
||||
run: |
|
||||
git fetch --all
|
||||
git pull
|
||||
|
||||
- name: Set version variable
|
||||
run: |
|
||||
version="$(cat artifacts/release-version)"
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## v0.5.0 (2025-03-01)
|
||||
## v0.5.1 (2025-03-01)
|
||||
|
||||
### Feat
|
||||
|
||||
|
||||
Generated
+198
-107
@@ -99,9 +99,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.96"
|
||||
version = "1.0.97"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4"
|
||||
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
@@ -137,13 +137,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.86"
|
||||
version = "0.1.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d"
|
||||
checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -190,9 +190,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.8.0"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
|
||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
@@ -219,9 +219,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.10.0"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
|
||||
[[package]]
|
||||
name = "cargo-husky"
|
||||
@@ -321,7 +321,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -338,11 +338,10 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.2.0"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -374,9 +373,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.15.10"
|
||||
version = "0.15.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b"
|
||||
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"libc",
|
||||
@@ -411,7 +410,7 @@ dependencies = [
|
||||
"crossterm_winapi",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"rustix",
|
||||
"rustix 0.38.44",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
@@ -457,7 +456,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -468,7 +467,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -500,7 +499,7 @@ dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -577,7 +576,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -594,9 +593,9 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.14.0"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
@@ -613,6 +612,15 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum_display_style_derive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
@@ -733,7 +741,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -879,9 +887,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.10.0"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a"
|
||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
@@ -1122,7 +1130,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1177,9 +1185,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.5"
|
||||
version = "2.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
|
||||
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
|
||||
|
||||
[[package]]
|
||||
name = "instability"
|
||||
@@ -1191,7 +1199,7 @@ dependencies = [
|
||||
"indoc",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1216,10 +1224,19 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.14"
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
@@ -1231,12 +1248,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.170"
|
||||
@@ -1259,6 +1270,12 @@ version = "0.4.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.7.5"
|
||||
@@ -1307,7 +1324,7 @@ dependencies = [
|
||||
"log-mdc",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde-value",
|
||||
"serde_json",
|
||||
@@ -1329,7 +1346,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "managarr"
|
||||
version = "0.5.0"
|
||||
version = "0.5.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
@@ -1348,10 +1365,11 @@ dependencies = [
|
||||
"derive_setters",
|
||||
"deunicode",
|
||||
"dirs-next",
|
||||
"enum_display_style_derive",
|
||||
"human-panic",
|
||||
"indicatif",
|
||||
"indoc",
|
||||
"itertools",
|
||||
"itertools 0.14.0",
|
||||
"log",
|
||||
"log4rs",
|
||||
"managarr-tree-widget",
|
||||
@@ -1373,6 +1391,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"urlencoding",
|
||||
"validate_theme_derive",
|
||||
"veil",
|
||||
]
|
||||
|
||||
@@ -1442,14 +1461,14 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mockito"
|
||||
version = "1.6.1"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "652cd6d169a36eaf9d1e6bce1a221130439a966d7f27858af66a33a66e9c4ee2"
|
||||
checksum = "7760e0e418d9b7e5777c0374009ca4c93861b9066f18cb334a20ce50ab63aa48"
|
||||
dependencies = [
|
||||
"assert-json-diff",
|
||||
"bytes",
|
||||
@@ -1461,7 +1480,7 @@ dependencies = [
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"log",
|
||||
"rand",
|
||||
"rand 0.9.0",
|
||||
"regex",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
@@ -1566,7 +1585,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1672,9 +1691,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.31"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
@@ -1694,7 +1713,7 @@ version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
"zerocopy 0.7.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1736,27 +1755,27 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.2.0"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b"
|
||||
checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.93"
|
||||
version = "1.0.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
||||
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.38"
|
||||
version = "1.0.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
||||
checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -1768,8 +1787,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.3",
|
||||
"zerocopy 0.8.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1779,7 +1809,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1791,6 +1831,15 @@ dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ratatui"
|
||||
version = "0.29.0"
|
||||
@@ -1803,7 +1852,7 @@ dependencies = [
|
||||
"crossterm",
|
||||
"indoc",
|
||||
"instability",
|
||||
"itertools",
|
||||
"itertools 0.13.0",
|
||||
"lru",
|
||||
"paste",
|
||||
"strum",
|
||||
@@ -1815,9 +1864,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.9"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f"
|
||||
checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
@@ -1914,9 +1963,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.11"
|
||||
version = "0.17.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73"
|
||||
checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
@@ -1928,21 +1977,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rstest"
|
||||
version = "0.23.0"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a2c585be59b6b5dd66a9d2084aa1d8bd52fbdb806eafdeffb52791147862035"
|
||||
checksum = "6fc39292f8613e913f7df8fa892b8944ceb47c247b78e1b1ae2f09e019be789d"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"futures-util",
|
||||
"rstest_macros",
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rstest_macros"
|
||||
version = "0.23.0"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "825ea780781b15345a146be27eaefb05085e337e869bff01b4306a4fd4a9ad5a"
|
||||
checksum = "1f168d99749d307be9de54d23fd226628d99768225ef08f6ffb52e0182a27746"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"glob",
|
||||
@@ -1952,7 +2001,7 @@ dependencies = [
|
||||
"regex",
|
||||
"relative-path",
|
||||
"rustc_version",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
@@ -1980,7 +2029,20 @@ dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.9.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -2025,15 +2087,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.19"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
|
||||
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.19"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "scc"
|
||||
@@ -2090,9 +2152,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.25"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
|
||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
@@ -2121,14 +2183,14 @@ checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.139"
|
||||
version = "1.0.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
|
||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -2192,7 +2254,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2299,7 +2361,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2321,9 +2383,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.98"
|
||||
version = "2.0.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
|
||||
checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2347,7 +2409,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2373,15 +2435,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.17.1"
|
||||
version = "3.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230"
|
||||
checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"getrandom 0.3.1",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"rustix 1.0.1",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -2391,7 +2453,7 @@ version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"rustix 0.38.44",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -2418,7 +2480,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2433,9 +2495,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.37"
|
||||
version = "0.3.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
|
||||
checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"libc",
|
||||
@@ -2448,9 +2510,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
@@ -2488,7 +2550,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2503,9 +2565,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.26.1"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37"
|
||||
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"tokio",
|
||||
@@ -2621,9 +2683,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.17"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
@@ -2637,7 +2699,7 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"itertools 0.13.0",
|
||||
"unicode-segmentation",
|
||||
"unicode-width 0.1.14",
|
||||
]
|
||||
@@ -2719,6 +2781,15 @@ dependencies = [
|
||||
"getrandom 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "validate_theme_derive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
@@ -2743,7 +2814,7 @@ checksum = "5b2d5567b6fbd34e8f0488d56b648e67c0d999535f4af2060d14f9074b43e833"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2801,7 +2872,7 @@ dependencies = [
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -2836,7 +2907,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -3141,7 +3212,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -3152,7 +3223,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"zerocopy-derive",
|
||||
"zerocopy-derive 0.7.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6"
|
||||
dependencies = [
|
||||
"zerocopy-derive 0.8.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3163,7 +3243,18 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3183,7 +3274,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -3212,5 +3303,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
+16
-6
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "managarr"
|
||||
version = "0.5.0"
|
||||
version = "0.5.1"
|
||||
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
||||
description = "A TUI and CLI to manage your Servarrs"
|
||||
keywords = ["managarr", "ratatui", "dashboard", "servarr", "tui"]
|
||||
@@ -10,9 +10,12 @@ homepage = "https://github.com/Dark-Alex-17/managarr"
|
||||
readme = "README.md"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
rust-version = "1.82.0"
|
||||
rust-version = "1.85.0"
|
||||
exclude = [".github", "CONTRIBUTING.md", "*.log", "tags"]
|
||||
|
||||
[workspace]
|
||||
members = ["proc_macros/enum_display_style_derive", "proc_macros/validate_theme_derive"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.68"
|
||||
backtrace = "0.3.74"
|
||||
@@ -41,11 +44,16 @@ ratatui = { version = "0.29.0", features = [
|
||||
"unstable-widget-ref",
|
||||
] }
|
||||
urlencoding = "2.1.2"
|
||||
clap = { version = "4.5.20", features = ["derive", "cargo", "env", "wrap_help"] }
|
||||
clap = { version = "4.5.20", features = [
|
||||
"derive",
|
||||
"cargo",
|
||||
"env",
|
||||
"wrap_help",
|
||||
] }
|
||||
clap_complete = "4.5.33"
|
||||
itertools = "0.13.0"
|
||||
itertools = "0.14.0"
|
||||
ctrlc = "3.4.5"
|
||||
colored = "2.1.0"
|
||||
colored = "3.0.0"
|
||||
async-trait = "0.1.83"
|
||||
dirs-next = "2.0.0"
|
||||
managarr-tree-widget = "0.24.0"
|
||||
@@ -55,13 +63,15 @@ deunicode = "1.6.0"
|
||||
paste = "1.0.15"
|
||||
openssl = { version = "0.10.70", features = ["vendored"] }
|
||||
veil = "0.2.0"
|
||||
validate_theme_derive = { path = "proc_macros/validate_theme_derive" }
|
||||
enum_display_style_derive = { path = "proc_macros/enum_display_style_derive" }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2.0.16"
|
||||
mockall = "0.13.0"
|
||||
mockito = "1.0.0"
|
||||
pretty_assertions = "1.3.0"
|
||||
rstest = "0.23.0"
|
||||
rstest = "0.25.0"
|
||||
serial_test = "3.2.0"
|
||||
|
||||
[dev-dependencies.cargo-husky]
|
||||
|
||||
@@ -8,7 +8,7 @@ default: run
|
||||
.PHONY: test test-cov build run lint lint-fix fmt analyze sonar release delete-tag
|
||||
|
||||
test:
|
||||
@cargo test
|
||||
@cargo test --all
|
||||
|
||||
## Run all tests with coverage - `cargo install cargo-tarpaulin`
|
||||
test-cov:
|
||||
|
||||
@@ -206,6 +206,16 @@ Key:
|
||||
|
||||
- [ ] Support for Tautulli
|
||||
|
||||
### Themes
|
||||
Managarr ships with a few themes out of the box. Here's a few examples:
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
You can also create your own custom themes as well. To learn more about what themes are built-in to Managarr and how
|
||||
to create your own custom themes, check out the [Themes README](themes/README.md).
|
||||
|
||||
### The Managarr CLI
|
||||
Managarr can be used in one of two ways: As a TUI, or as a CLI for managing your Servarrs.
|
||||
|
||||
@@ -218,7 +228,7 @@ To see all available commands, simply run `managarr --help`:
|
||||
|
||||
```shell
|
||||
$ managarr --help
|
||||
managarr 0.5.0
|
||||
managarr 0.5.1
|
||||
Alex Clarke <alex.j.tusa@gmail.com>
|
||||
|
||||
A TUI and CLI to manage your Servarrs
|
||||
@@ -235,6 +245,8 @@ Commands:
|
||||
Options:
|
||||
--disable-spinner Disable the spinner (can sometimes make parsing output challenging) [env: MANAGARR_DISABLE_SPINNER=]
|
||||
--config-file <CONFIG_FILE> The Managarr configuration file to use [env: MANAGARR_CONFIG_FILE=]
|
||||
--themes-file <THEMES_FILE> The Managarr themes file to use [env: MANAGARR_THEMES_FILE=]
|
||||
--theme <THEME> The name of the Managarr theme to use [env: MANAGARR_THEME=]
|
||||
--servarr-name <SERVARR_NAME> For multi-instance configurations, you need to specify the name of the instance configuration that you want to use.
|
||||
This is useful when you have multiple instances of the same Servarr defined in your config file.
|
||||
By default, if left empty, the first configured Servarr instance listed in the config file will be used.
|
||||
@@ -315,6 +327,7 @@ managarr --config-file /path/to/config.yml
|
||||
|
||||
### Example Configuration:
|
||||
```yaml
|
||||
theme: default
|
||||
radarr:
|
||||
- host: 192.168.0.78
|
||||
port: 7878
|
||||
@@ -357,6 +370,7 @@ tautulli:
|
||||
|
||||
### Example Multi-Instance Configuration:
|
||||
```yaml
|
||||
theme: default
|
||||
radarr:
|
||||
- host: 192.168.0.78 # No name specified, so this instance's name will default to 'Radarr 1'
|
||||
port: 7878
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "enum_display_style_derive"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
quote = "1.0.39"
|
||||
syn = "2.0.99"
|
||||
darling = "0.20.10"
|
||||
@@ -0,0 +1,76 @@
|
||||
mod macro_models;
|
||||
|
||||
use crate::macro_models::DisplayStyleArgs;
|
||||
use darling::FromVariant;
|
||||
use quote::quote;
|
||||
use syn::{Data, DeriveInput, parse_macro_input};
|
||||
|
||||
/// Derive macro for generating a `to_display_str` method for an enum.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Using default values for the display style:
|
||||
///
|
||||
/// ```
|
||||
/// use enum_display_style_derive::EnumDisplayStyle;
|
||||
///
|
||||
/// #[derive(EnumDisplayStyle)]
|
||||
/// enum Weekend {
|
||||
/// Saturday,
|
||||
/// Sunday,
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(Weekend::Saturday.to_display_str(), "Saturday");
|
||||
/// assert_eq!(Weekend::Sunday.to_display_str(), "Sunday");
|
||||
/// ```
|
||||
///
|
||||
/// Using custom values for the display style:
|
||||
///
|
||||
/// ```
|
||||
/// use enum_display_style_derive::EnumDisplayStyle;
|
||||
///
|
||||
/// #[derive(EnumDisplayStyle)]
|
||||
/// enum MonitorStatus {
|
||||
/// #[display_style(name = "Monitor Transactions")]
|
||||
/// Active,
|
||||
/// #[display_style(name = "Don't Monitor Transactions")]
|
||||
/// None,
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(MonitorStatus::Active.to_display_str(), "Monitor Transactions");
|
||||
/// assert_eq!(MonitorStatus::None.to_display_str(), "Don't Monitor Transactions");
|
||||
/// ```
|
||||
#[proc_macro_derive(EnumDisplayStyle, attributes(display_style))]
|
||||
pub fn enum_display_style_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let enum_name = &input.ident;
|
||||
|
||||
let mut match_arms = Vec::new();
|
||||
|
||||
if let Data::Enum(data_enum) = &input.data {
|
||||
let variants = &data_enum.variants;
|
||||
|
||||
for variant in variants {
|
||||
let variant_ident = &variant.ident;
|
||||
let variant_display_name = DisplayStyleArgs::from_variant(variant)
|
||||
.unwrap()
|
||||
.name
|
||||
.unwrap_or_else(|| variant_ident.to_string());
|
||||
|
||||
match_arms.push(quote! {
|
||||
#enum_name::#variant_ident => #variant_display_name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
quote! {
|
||||
impl<'a> #enum_name {
|
||||
pub fn to_display_str(self) -> &'a str {
|
||||
match self {
|
||||
#(#match_arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
use darling::FromVariant;
|
||||
|
||||
#[derive(Debug, FromVariant)]
|
||||
#[darling(attributes(display_style))]
|
||||
pub struct DisplayStyleArgs {
|
||||
pub name: Option<String>,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "validate_theme_derive"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
quote = "1.0.39"
|
||||
syn = "2.0.99"
|
||||
|
||||
[dev-dependencies]
|
||||
log = "0.4.17"
|
||||
@@ -0,0 +1,106 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{Data, DeriveInput, Fields, parse_macro_input};
|
||||
|
||||
/// Derive macro for generating a `validate` method for a Theme struct.
|
||||
/// The `validate` method ensures that all values with the `validate` attribute are not `None`.
|
||||
/// Otherwise, an error message it output to both the log file and stdout and the program exits.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Valid themes pass through the program transitively without any messages being output.
|
||||
///
|
||||
/// ```
|
||||
/// use validate_theme_derive::ValidateTheme;
|
||||
///
|
||||
/// #[derive(ValidateTheme, Default)]
|
||||
/// struct Theme {
|
||||
/// pub name: String,
|
||||
/// #[validate]
|
||||
/// pub good: Option<Style>,
|
||||
/// #[validate]
|
||||
/// pub bad: Option<Style>,
|
||||
/// pub ugly: Option<Style>,
|
||||
/// }
|
||||
///
|
||||
/// struct Style {
|
||||
/// color: String,
|
||||
/// }
|
||||
///
|
||||
/// let theme = Theme {
|
||||
/// good: Some(Style { color: "Green".to_owned() }),
|
||||
/// bad: Some(Style { color: "Red".to_owned() }),
|
||||
/// ..Theme::default()
|
||||
/// };
|
||||
///
|
||||
/// // Since only `good` and `bad` have the `validate` attribute, the `validate` method will only check those fields.
|
||||
/// theme.validate();
|
||||
/// // Since both `good` and `bad` have values, the program will not exit and no message is output.
|
||||
/// ```
|
||||
///
|
||||
/// Invalid themes will output an error message to both the log file and stdout and the program will exit.
|
||||
///
|
||||
/// ```should_panic
|
||||
/// use validate_theme_derive::ValidateTheme;
|
||||
///
|
||||
/// #[derive(ValidateTheme, Default)]
|
||||
/// struct Theme {
|
||||
/// pub name: String,
|
||||
/// #[validate]
|
||||
/// pub good: Option<Style>,
|
||||
/// #[validate]
|
||||
/// pub bad: Option<Style>,
|
||||
/// pub ugly: Option<Style>,
|
||||
/// }
|
||||
///
|
||||
/// struct Style {
|
||||
/// color: String,
|
||||
/// }
|
||||
///
|
||||
/// let theme = Theme {
|
||||
/// bad: Some(Style { color: "Red".to_owned() }),
|
||||
/// ..Theme::default()
|
||||
/// };
|
||||
///
|
||||
/// // Since `good` has the `validate` attribute and since `good` is `None`, the `validate` method will output an error message and exit the program.
|
||||
/// theme.validate();
|
||||
/// ```
|
||||
#[proc_macro_derive(ValidateTheme, attributes(validate))]
|
||||
pub fn derive_validate_theme(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let struct_name = &input.ident;
|
||||
|
||||
let mut validation_checks = Vec::new();
|
||||
|
||||
if let Data::Struct(data_struct) = &input.data {
|
||||
if let Fields::Named(fields) = &data_struct.fields {
|
||||
for field in &fields.named {
|
||||
let field_name = &field.ident;
|
||||
|
||||
let has_validate_attr = field
|
||||
.attrs
|
||||
.iter()
|
||||
.any(|attr| attr.path().is_ident("validate"));
|
||||
|
||||
if has_validate_attr {
|
||||
validation_checks.push(quote! {
|
||||
if self.#field_name.is_none() {
|
||||
log::error!("{} is missing a color value.", stringify!(#field_name));
|
||||
eprintln!("{} is missing a color value.", stringify!(#field_name));
|
||||
std::process::exit(1);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
quote! {
|
||||
impl #struct_name {
|
||||
pub fn validate(&self) {
|
||||
#(#validation_checks)*
|
||||
}
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 382 KiB After Width: | Height: | Size: 340 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 211 KiB After Width: | Height: | Size: 241 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 220 KiB |
@@ -31,6 +31,7 @@ mod tests {
|
||||
};
|
||||
let sonarr_config_2 = ServarrConfig::default();
|
||||
let config = AppConfig {
|
||||
theme: None,
|
||||
radarr: Some(vec![radarr_config_1.clone(), radarr_config_2.clone()]),
|
||||
sonarr: Some(vec![sonarr_config_1.clone(), sonarr_config_2.clone()]),
|
||||
};
|
||||
|
||||
+61
-1
@@ -44,128 +44,188 @@ generate_keybindings! {
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub struct KeyBinding {
|
||||
pub key: Key,
|
||||
pub alt: Option<Key>,
|
||||
pub desc: &'static str,
|
||||
}
|
||||
|
||||
pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings {
|
||||
add: KeyBinding {
|
||||
key: Key::Char('a'),
|
||||
alt: None,
|
||||
desc: "add",
|
||||
},
|
||||
up: KeyBinding {
|
||||
key: Key::Up,
|
||||
alt: Some(Key::Char('k')),
|
||||
desc: "up",
|
||||
},
|
||||
down: KeyBinding {
|
||||
key: Key::Down,
|
||||
alt: Some(Key::Char('j')),
|
||||
desc: "down",
|
||||
},
|
||||
left: KeyBinding {
|
||||
key: Key::Left,
|
||||
alt: Some(Key::Char('h')),
|
||||
desc: "left",
|
||||
},
|
||||
right: KeyBinding {
|
||||
key: Key::Right,
|
||||
alt: Some(Key::Char('l')),
|
||||
desc: "right",
|
||||
},
|
||||
backspace: KeyBinding {
|
||||
key: Key::Backspace,
|
||||
alt: None,
|
||||
desc: "backspace",
|
||||
},
|
||||
next_servarr: KeyBinding {
|
||||
key: Key::Tab,
|
||||
alt: None,
|
||||
desc: "next servarr",
|
||||
},
|
||||
previous_servarr: KeyBinding {
|
||||
key: Key::BackTab,
|
||||
alt: None,
|
||||
desc: "previous servarr",
|
||||
},
|
||||
clear: KeyBinding {
|
||||
key: Key::Char('c'),
|
||||
alt: None,
|
||||
desc: "clear",
|
||||
},
|
||||
auto_search: KeyBinding {
|
||||
key: Key::Char('S'),
|
||||
alt: None,
|
||||
desc: "auto search",
|
||||
},
|
||||
search: KeyBinding {
|
||||
key: Key::Char('s'),
|
||||
alt: None,
|
||||
desc: "search",
|
||||
},
|
||||
settings: KeyBinding {
|
||||
key: Key::Char('S'),
|
||||
alt: None,
|
||||
desc: "settings",
|
||||
},
|
||||
filter: KeyBinding {
|
||||
key: Key::Char('f'),
|
||||
alt: None,
|
||||
desc: "filter",
|
||||
},
|
||||
sort: KeyBinding {
|
||||
key: Key::Char('o'),
|
||||
alt: None,
|
||||
desc: "sort",
|
||||
},
|
||||
edit: KeyBinding {
|
||||
key: Key::Char('e'),
|
||||
alt: None,
|
||||
desc: "edit",
|
||||
},
|
||||
events: KeyBinding {
|
||||
key: Key::Char('e'),
|
||||
alt: None,
|
||||
desc: "events",
|
||||
},
|
||||
logs: KeyBinding {
|
||||
key: Key::Char('l'),
|
||||
key: Key::Char('L'),
|
||||
alt: None,
|
||||
desc: "logs",
|
||||
},
|
||||
tasks: KeyBinding {
|
||||
key: Key::Char('t'),
|
||||
alt: None,
|
||||
desc: "tasks",
|
||||
},
|
||||
test: KeyBinding {
|
||||
key: Key::Char('t'),
|
||||
alt: None,
|
||||
desc: "test",
|
||||
},
|
||||
test_all: KeyBinding {
|
||||
key: Key::Char('T'),
|
||||
alt: None,
|
||||
desc: "test all",
|
||||
},
|
||||
toggle_monitoring: KeyBinding {
|
||||
key: Key::Char('m'),
|
||||
alt: None,
|
||||
desc: "toggle monitoring",
|
||||
},
|
||||
refresh: KeyBinding {
|
||||
key: Key::Ctrl('r'),
|
||||
alt: None,
|
||||
desc: "refresh",
|
||||
},
|
||||
update: KeyBinding {
|
||||
key: Key::Char('u'),
|
||||
alt: None,
|
||||
desc: "update",
|
||||
},
|
||||
home: KeyBinding {
|
||||
key: Key::Home,
|
||||
alt: None,
|
||||
desc: "home",
|
||||
},
|
||||
end: KeyBinding {
|
||||
key: Key::End,
|
||||
alt: None,
|
||||
desc: "end",
|
||||
},
|
||||
delete: KeyBinding {
|
||||
key: Key::Delete,
|
||||
alt: None,
|
||||
desc: "delete",
|
||||
},
|
||||
submit: KeyBinding {
|
||||
key: Key::Enter,
|
||||
alt: None,
|
||||
desc: "submit",
|
||||
},
|
||||
confirm: KeyBinding {
|
||||
key: Key::Ctrl('s'),
|
||||
alt: None,
|
||||
desc: "submit",
|
||||
},
|
||||
quit: KeyBinding {
|
||||
key: Key::Char('q'),
|
||||
alt: None,
|
||||
desc: "quit",
|
||||
},
|
||||
esc: KeyBinding {
|
||||
key: Key::Esc,
|
||||
alt: None,
|
||||
desc: "close",
|
||||
},
|
||||
};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! matches_key {
|
||||
($binding:ident, $key:expr) => {
|
||||
$crate::app::key_binding::DEFAULT_KEYBINDINGS.$binding.key == $key
|
||||
|| ($crate::app::key_binding::DEFAULT_KEYBINDINGS
|
||||
.$binding
|
||||
.alt
|
||||
.is_some()
|
||||
&& $crate::app::key_binding::DEFAULT_KEYBINDINGS
|
||||
.$binding
|
||||
.alt
|
||||
.unwrap()
|
||||
== $key)
|
||||
};
|
||||
($binding:ident, $key:expr, $ignore_alt_navigation:expr) => {
|
||||
$crate::app::key_binding::DEFAULT_KEYBINDINGS.$binding.key == $key
|
||||
|| !$ignore_alt_navigation
|
||||
&& ($crate::app::key_binding::DEFAULT_KEYBINDINGS
|
||||
.$binding
|
||||
.alt
|
||||
.is_some()
|
||||
&& $crate::app::key_binding::DEFAULT_KEYBINDINGS
|
||||
.$binding
|
||||
.alt
|
||||
.unwrap()
|
||||
== $key)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ mod test {
|
||||
#[case(DEFAULT_KEYBINDINGS.sort, Key::Char('o'), "sort")]
|
||||
#[case(DEFAULT_KEYBINDINGS.edit, Key::Char('e'), "edit")]
|
||||
#[case(DEFAULT_KEYBINDINGS.events, Key::Char('e'), "events")]
|
||||
#[case(DEFAULT_KEYBINDINGS.logs, Key::Char('l'), "logs")]
|
||||
#[case(DEFAULT_KEYBINDINGS.logs, Key::Char('L'), "logs")]
|
||||
#[case(DEFAULT_KEYBINDINGS.tasks, Key::Char('t'), "tasks")]
|
||||
#[case(DEFAULT_KEYBINDINGS.test, Key::Char('t'), "test")]
|
||||
#[case(DEFAULT_KEYBINDINGS.test_all, Key::Char('T'), "test all")]
|
||||
|
||||
@@ -270,6 +270,7 @@ pub struct Data<'a> {
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
|
||||
pub struct AppConfig {
|
||||
pub theme: Option<String>,
|
||||
pub radarr: Option<Vec<ServarrConfig>>,
|
||||
pub sonarr: Option<Vec<ServarrConfig>>,
|
||||
}
|
||||
|
||||
+18
-17
@@ -1,9 +1,9 @@
|
||||
use radarr_handlers::RadarrHandler;
|
||||
use sonarr_handlers::SonarrHandler;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::matches_key;
|
||||
use crate::models::{HorizontallyScrollableText, Route};
|
||||
|
||||
mod radarr_handlers;
|
||||
@@ -22,40 +22,42 @@ pub trait KeyEventHandler<'a, 'b, T: Into<Route> + Copy> {
|
||||
fn handle_key_event(&mut self) {
|
||||
let key = self.get_key();
|
||||
match key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.up.key => {
|
||||
_ if matches_key!(up, key, self.ignore_alt_navigation()) => {
|
||||
if self.is_ready() {
|
||||
self.handle_scroll_up();
|
||||
}
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.down.key => {
|
||||
_ if matches_key!(down, key, self.ignore_alt_navigation()) => {
|
||||
if self.is_ready() {
|
||||
self.handle_scroll_down();
|
||||
}
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.home.key => {
|
||||
_ if matches_key!(home, key) => {
|
||||
if self.is_ready() {
|
||||
self.handle_home();
|
||||
}
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.end.key => {
|
||||
_ if matches_key!(end, key) => {
|
||||
if self.is_ready() {
|
||||
self.handle_end();
|
||||
}
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.delete.key => {
|
||||
_ if matches_key!(delete, key) => {
|
||||
if self.is_ready() {
|
||||
self.handle_delete();
|
||||
}
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.left.key || key == DEFAULT_KEYBINDINGS.right.key => {
|
||||
_ if matches_key!(left, key, self.ignore_alt_navigation())
|
||||
|| matches_key!(right, key, self.ignore_alt_navigation()) =>
|
||||
{
|
||||
self.handle_left_right_action()
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.submit.key => {
|
||||
_ if matches_key!(submit, key) => {
|
||||
if self.is_ready() {
|
||||
self.handle_submit();
|
||||
}
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.esc.key => self.handle_esc(),
|
||||
_ if matches_key!(esc, key) => self.handle_esc(),
|
||||
_ => {
|
||||
if self.is_ready() {
|
||||
self.handle_char_key_event();
|
||||
@@ -71,6 +73,7 @@ pub trait KeyEventHandler<'a, 'b, T: Into<Route> + Copy> {
|
||||
fn accepts(active_block: T) -> bool;
|
||||
fn new(key: Key, app: &'a mut App<'b>, active_block: T, context: Option<T>) -> Self;
|
||||
fn get_key(&self) -> Key;
|
||||
fn ignore_alt_navigation(&self) -> bool;
|
||||
fn is_ready(&self) -> bool;
|
||||
fn handle_scroll_up(&mut self);
|
||||
fn handle_scroll_down(&mut self);
|
||||
@@ -84,12 +87,12 @@ pub trait KeyEventHandler<'a, 'b, T: Into<Route> + Copy> {
|
||||
}
|
||||
|
||||
pub fn handle_events(key: Key, app: &mut App<'_>) {
|
||||
if key == DEFAULT_KEYBINDINGS.next_servarr.key {
|
||||
if matches_key!(next_servarr, key) {
|
||||
app.reset();
|
||||
app.server_tabs.next();
|
||||
app.pop_and_push_navigation_stack(app.server_tabs.get_active_route());
|
||||
app.cancellation_token.cancel();
|
||||
} else if key == DEFAULT_KEYBINDINGS.previous_servarr.key {
|
||||
} else if matches_key!(previous_servarr, key) {
|
||||
app.reset();
|
||||
app.server_tabs.previous();
|
||||
app.pop_and_push_navigation_stack(app.server_tabs.get_active_route());
|
||||
@@ -115,8 +118,7 @@ fn handle_clear_errors(app: &mut App<'_>) {
|
||||
|
||||
fn handle_prompt_toggle(app: &mut App<'_>, key: Key) {
|
||||
match key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.left.key || key == DEFAULT_KEYBINDINGS.right.key => {
|
||||
match app.get_current_route() {
|
||||
_ if matches_key!(left, key) || matches_key!(right, key) => match app.get_current_route() {
|
||||
Route::Radarr(_, _) => {
|
||||
app.data.radarr_data.prompt_confirm = !app.data.radarr_data.prompt_confirm
|
||||
}
|
||||
@@ -124,8 +126,7 @@ fn handle_prompt_toggle(app: &mut App<'_>, key: Key) {
|
||||
app.data.sonarr_data.prompt_confirm = !app.data.sonarr_data.prompt_confirm
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@@ -149,7 +150,7 @@ macro_rules! handle_text_box_left_right_keys {
|
||||
macro_rules! handle_text_box_keys {
|
||||
($self:expr, $key:expr, $input:expr) => {
|
||||
match $self.key {
|
||||
_ if $key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.backspace.key => {
|
||||
_ if $crate::matches_key!(backspace, $key) => {
|
||||
$input.pop();
|
||||
}
|
||||
Key::Char(character) => {
|
||||
@@ -165,7 +166,7 @@ macro_rules! handle_prompt_left_right_keys {
|
||||
($self:expr, $confirm_prompt:expr, $data:ident) => {
|
||||
if $self.app.data.$data.selected_block.get_active_block() == $confirm_prompt {
|
||||
handle_prompt_toggle($self.app, $self.key);
|
||||
} else if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.left.key {
|
||||
} else if $crate::matches_key!(left, $self.key) {
|
||||
$self.app.data.$data.selected_block.left();
|
||||
} else {
|
||||
$self.app.data.$data.selected_block.right();
|
||||
|
||||
@@ -4,6 +4,7 @@ mod tests {
|
||||
|
||||
use chrono::DateTime;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
@@ -541,6 +542,22 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_blocklist_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = BlocklistHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_blocklist_item_id() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handle_table_events;
|
||||
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::table_handler::TableHandlingConfig;
|
||||
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
|
||||
@@ -9,6 +7,7 @@ use crate::models::radarr_models::BlocklistItem;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, BLOCKLIST_BLOCKS};
|
||||
use crate::models::stateful_table::SortOption;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::{handle_table_events, matches_key};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "blocklist_handler_tests.rs"]
|
||||
@@ -51,6 +50,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
|
||||
BLOCKLIST_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -143,10 +146,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
|
||||
let key = self.key;
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::Blocklist => match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
_ if matches_key!(refresh, key) => {
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.clear.key => {
|
||||
_ if matches_key!(clear, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::BlocklistClearAllItemsPrompt.into());
|
||||
@@ -154,7 +157,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
|
||||
_ => (),
|
||||
},
|
||||
ActiveRadarrBlock::DeleteBlocklistItemPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
if matches_key!(confirm, key) {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteBlocklistItem(
|
||||
self.extract_blocklist_item_id(),
|
||||
@@ -164,7 +167,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
|
||||
}
|
||||
}
|
||||
ActiveRadarrBlock::BlocklistClearAllItemsPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
if matches_key!(confirm, key) {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::ClearBlocklist);
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handle_table_events;
|
||||
use crate::handlers::table_handler::TableHandlingConfig;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::models::radarr_models::CollectionMovie;
|
||||
@@ -11,6 +9,7 @@ use crate::models::servarr_data::radarr::radarr_data::{
|
||||
};
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::{handle_table_events, matches_key};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "collection_details_handler_tests.rs"]
|
||||
@@ -46,6 +45,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHan
|
||||
COLLECTION_DETAILS_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -130,7 +133,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHan
|
||||
|
||||
fn handle_char_key_event(&mut self) {
|
||||
if self.active_radarr_block == ActiveRadarrBlock::CollectionDetails
|
||||
&& self.key == DEFAULT_KEYBINDINGS.edit.key
|
||||
&& matches_key!(edit, self.key)
|
||||
{
|
||||
self.app.push_navigation_stack(
|
||||
(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_str_eq;
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
@@ -278,6 +279,22 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_collection_details_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = CollectionDetailsHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collection_details_handler_not_ready_when_loading() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -589,6 +589,22 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_collections_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = CollectionsHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collections_handler_not_ready_when_loading() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||
@@ -7,7 +6,7 @@ use crate::models::servarr_data::radarr::modals::EditCollectionModal;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS};
|
||||
use crate::models::Scrollable;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::{handle_text_box_keys, handle_text_box_left_right_keys};
|
||||
use crate::{handle_text_box_keys, handle_text_box_left_right_keys, matches_key};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "edit_collection_handler_tests.rs"]
|
||||
@@ -69,6 +68,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
|
||||
EDIT_COLLECTION_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -354,7 +357,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
|
||||
ActiveRadarrBlock::EditCollectionPrompt => {
|
||||
if self.app.data.radarr_data.selected_block.get_active_block()
|
||||
== ActiveRadarrBlock::EditCollectionConfirmPrompt
|
||||
&& key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
&& matches_key!(confirm, key)
|
||||
{
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditCollection(
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
mod tests {
|
||||
use bimap::BiMap;
|
||||
use pretty_assertions::assert_str_eq;
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
@@ -926,7 +927,7 @@ mod tests {
|
||||
app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default());
|
||||
|
||||
EditCollectionHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveRadarrBlock::EditCollectionRootFolderPathInput,
|
||||
None,
|
||||
@@ -942,7 +943,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.path
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1019,6 +1020,22 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_edit_collection_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = EditCollectionHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_edit_collection_params() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handle_table_events;
|
||||
use crate::handlers::radarr_handlers::collections::collection_details_handler::CollectionDetailsHandler;
|
||||
use crate::handlers::radarr_handlers::collections::edit_collection_handler::EditCollectionHandler;
|
||||
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
|
||||
@@ -14,6 +12,7 @@ use crate::models::servarr_data::radarr::radarr_data::{
|
||||
use crate::models::stateful_table::SortOption;
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::{handle_table_events, matches_key};
|
||||
|
||||
mod collection_details_handler;
|
||||
mod edit_collection_handler;
|
||||
@@ -73,6 +72,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
|
||||
|| COLLECTIONS_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -145,7 +148,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
|
||||
let key = self.key;
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::Collections => match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.edit.key => {
|
||||
_ if matches_key!(edit, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into());
|
||||
@@ -154,18 +157,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
|
||||
self.app.data.radarr_data.selected_block =
|
||||
BlockSelectionState::new(EDIT_COLLECTION_SELECTION_BLOCKS);
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.update.key => {
|
||||
_ if matches_key!(update, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::UpdateAllCollectionsPrompt.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
_ if matches_key!(refresh, key) => {
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
ActiveRadarrBlock::UpdateAllCollectionsPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
if matches_key!(confirm, key) {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateCollections);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
@@ -387,6 +388,22 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_downloads_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = DownloadsHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_download_id() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handle_table_events;
|
||||
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::table_handler::TableHandlingConfig;
|
||||
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
|
||||
use crate::models::radarr_models::DownloadRecord;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::{handle_table_events, matches_key};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "downloads_handler_tests.rs"]
|
||||
@@ -47,6 +46,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
|
||||
DOWNLOADS_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -130,18 +133,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
|
||||
let key = self.key;
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::Downloads => match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.update.key => {
|
||||
_ if matches_key!(update, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::UpdateDownloadsPrompt.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
_ if matches_key!(refresh, key) => {
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
ActiveRadarrBlock::DeleteDownloadPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
if matches_key!(confirm, key) {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action =
|
||||
Some(RadarrEvent::DeleteDownload(self.extract_download_id()));
|
||||
@@ -150,7 +153,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
|
||||
}
|
||||
}
|
||||
ActiveRadarrBlock::UpdateDownloadsPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
if matches_key!(confirm, key) {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateDownloads);
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||
@@ -6,7 +5,9 @@ use crate::models::servarr_data::modals::EditIndexerModal;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS};
|
||||
use crate::models::servarr_models::EditIndexerParams;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::{handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys};
|
||||
use crate::{
|
||||
handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys, matches_key,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "edit_indexer_handler_tests.rs"]
|
||||
@@ -65,6 +66,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
|
||||
EDIT_INDEXER_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -504,7 +509,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
|
||||
ActiveRadarrBlock::EditIndexerPrompt => {
|
||||
if self.app.data.radarr_data.selected_block.get_active_block()
|
||||
== ActiveRadarrBlock::EditIndexerConfirmPrompt
|
||||
&& self.key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
&& matches_key!(confirm, self.key)
|
||||
{
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action =
|
||||
|
||||
@@ -10,6 +10,7 @@ mod tests {
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS};
|
||||
use crate::models::servarr_models::EditIndexerParams;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
mod test_handle_scroll_up_and_down {
|
||||
@@ -1597,7 +1598,7 @@ mod tests {
|
||||
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
|
||||
|
||||
EditIndexerHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveRadarrBlock::EditIndexerNameInput,
|
||||
None,
|
||||
@@ -1613,7 +1614,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.name
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1624,7 +1625,7 @@ mod tests {
|
||||
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
|
||||
|
||||
EditIndexerHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveRadarrBlock::EditIndexerUrlInput,
|
||||
None,
|
||||
@@ -1640,7 +1641,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.url
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1651,7 +1652,7 @@ mod tests {
|
||||
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
|
||||
|
||||
EditIndexerHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveRadarrBlock::EditIndexerApiKeyInput,
|
||||
None,
|
||||
@@ -1667,7 +1668,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.api_key
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1678,7 +1679,7 @@ mod tests {
|
||||
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
|
||||
|
||||
EditIndexerHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveRadarrBlock::EditIndexerSeedRatioInput,
|
||||
None,
|
||||
@@ -1694,7 +1695,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.seed_ratio
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1705,7 +1706,7 @@ mod tests {
|
||||
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
|
||||
|
||||
EditIndexerHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveRadarrBlock::EditIndexerTagsInput,
|
||||
None,
|
||||
@@ -1721,7 +1722,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.tags
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1793,6 +1794,22 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_edit_indexer_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = EditIndexerHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_edit_indexer_params() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||
@@ -7,7 +6,9 @@ use crate::models::servarr_data::radarr::radarr_data::{
|
||||
ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS,
|
||||
};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::{handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys};
|
||||
use crate::{
|
||||
handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys, matches_key,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "edit_indexer_settings_handler_tests.rs"]
|
||||
@@ -37,6 +38,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
|
||||
INDEXER_SETTINGS_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -269,7 +274,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
|
||||
ActiveRadarrBlock::AllIndexerSettingsPrompt => {
|
||||
if self.app.data.radarr_data.selected_block.get_active_block()
|
||||
== ActiveRadarrBlock::IndexerSettingsConfirmPrompt
|
||||
&& self.key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
&& matches_key!(confirm, self.key)
|
||||
{
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
@@ -907,7 +908,7 @@ mod tests {
|
||||
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default());
|
||||
|
||||
IndexerSettingsHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
|
||||
None,
|
||||
@@ -923,7 +924,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.whitelisted_hardcoded_subs
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -970,6 +971,22 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_indexer_settings_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = IndexerSettingsHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_edit_indexer_settings_body() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -633,6 +633,22 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_indexers_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = IndexersHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_indexer_id() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handle_table_events;
|
||||
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::radarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler;
|
||||
use crate::handlers::radarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler;
|
||||
@@ -15,6 +13,7 @@ use crate::models::servarr_data::radarr::radarr_data::{
|
||||
use crate::models::servarr_models::Indexer;
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::{handle_table_events, matches_key};
|
||||
|
||||
mod edit_indexer_handler;
|
||||
mod edit_indexer_settings_handler;
|
||||
@@ -70,6 +69,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
|
||||
|| INDEXERS_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -169,20 +172,20 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
|
||||
let key = self.key;
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::Indexers => match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
_ if matches_key!(refresh, key) => {
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.test.key => {
|
||||
_ if matches_key!(test, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::TestIndexer.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.test_all.key => {
|
||||
_ if matches_key!(test_all, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::TestAllIndexers.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.settings.key => {
|
||||
_ if matches_key!(settings, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into());
|
||||
@@ -192,7 +195,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
|
||||
_ => (),
|
||||
},
|
||||
ActiveRadarrBlock::DeleteIndexerPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
if matches_key!(confirm, key) {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action =
|
||||
Some(RadarrEvent::DeleteIndexer(self.extract_indexer_id()));
|
||||
|
||||
@@ -48,6 +48,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for TestAllIndexersHandl
|
||||
active_block == ActiveRadarrBlock::TestAllIndexers
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
|
||||
@@ -7,6 +7,7 @@ mod tests {
|
||||
use crate::models::servarr_data::modals::IndexerTestResultModalItem;
|
||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
mod test_handle_esc {
|
||||
@@ -48,6 +49,22 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_test_all_indexers_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = TestAllIndexersHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_test_all_indexers_handler_is_not_ready_when_loading() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::handlers::table_handler::TableHandlingConfig;
|
||||
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||
use crate::models::radarr_models::{
|
||||
@@ -11,7 +10,9 @@ use crate::models::servarr_data::radarr::radarr_data::{
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
use crate::models::{BlockSelectionState, Scrollable};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::{handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, App, Key};
|
||||
use crate::{
|
||||
handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, matches_key, App, Key,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "add_movie_handler_tests.rs"]
|
||||
@@ -132,6 +133,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
|
||||
ADD_MOVIE_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -542,7 +547,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
|
||||
ActiveRadarrBlock::AddMoviePrompt => {
|
||||
if self.app.data.radarr_data.selected_block.get_active_block()
|
||||
== ActiveRadarrBlock::AddMovieConfirmPrompt
|
||||
&& key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
&& matches_key!(confirm, key)
|
||||
{
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action =
|
||||
|
||||
@@ -1395,7 +1395,7 @@ mod tests {
|
||||
app.data.radarr_data.add_movie_search = Some(HorizontallyScrollableText::default());
|
||||
|
||||
AddMovieHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveRadarrBlock::AddMovieSearchInput,
|
||||
None,
|
||||
@@ -1404,7 +1404,7 @@ mod tests {
|
||||
|
||||
assert_str_eq!(
|
||||
app.data.radarr_data.add_movie_search.as_ref().unwrap().text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1414,7 +1414,7 @@ mod tests {
|
||||
app.data.radarr_data.add_movie_modal = Some(AddMovieModal::default());
|
||||
|
||||
AddMovieHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveRadarrBlock::AddMovieTagsInput,
|
||||
None,
|
||||
@@ -1430,7 +1430,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.tags
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1523,6 +1523,22 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_add_movie_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = AddMovieHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_movie_search_no_panic_on_none_search_result() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||
use crate::matches_key;
|
||||
use crate::models::radarr_models::DeleteMovieParams;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
@@ -37,6 +37,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<'
|
||||
DELETE_MOVIE_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -122,7 +126,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<'
|
||||
if self.active_radarr_block == ActiveRadarrBlock::DeleteMoviePrompt
|
||||
&& self.app.data.radarr_data.selected_block.get_active_block()
|
||||
== ActiveRadarrBlock::DeleteMovieConfirmPrompt
|
||||
&& self.key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
&& matches_key!(confirm, self.key)
|
||||
{
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action =
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
@@ -313,6 +314,22 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_delete_movie_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = DeleteMovieHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_delete_movie_params() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||
@@ -7,7 +6,7 @@ use crate::models::servarr_data::radarr::modals::EditMovieModal;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_MOVIE_BLOCKS};
|
||||
use crate::models::Scrollable;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::{handle_text_box_keys, handle_text_box_left_right_keys};
|
||||
use crate::{handle_text_box_keys, handle_text_box_left_right_keys, matches_key};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "edit_movie_handler_tests.rs"]
|
||||
@@ -68,6 +67,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a,
|
||||
EDIT_MOVIE_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -376,7 +379,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a,
|
||||
ActiveRadarrBlock::EditMoviePrompt => {
|
||||
if self.app.data.radarr_data.selected_block.get_active_block()
|
||||
== ActiveRadarrBlock::EditMovieConfirmPrompt
|
||||
&& key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
&& matches_key!(confirm, key)
|
||||
{
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action =
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
mod tests {
|
||||
use bimap::BiMap;
|
||||
use pretty_assertions::assert_str_eq;
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
@@ -1033,7 +1034,7 @@ mod tests {
|
||||
app.data.radarr_data.edit_movie_modal = Some(EditMovieModal::default());
|
||||
|
||||
EditMovieHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveRadarrBlock::EditMoviePathInput,
|
||||
None,
|
||||
@@ -1049,7 +1050,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.path
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1059,7 +1060,7 @@ mod tests {
|
||||
app.data.radarr_data.edit_movie_modal = Some(EditMovieModal::default());
|
||||
|
||||
EditMovieHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveRadarrBlock::EditMovieTagsInput,
|
||||
None,
|
||||
@@ -1075,7 +1076,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.tags
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1148,6 +1149,22 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_edit_movie_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = EditMovieHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_edit_movie_params() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -777,6 +777,22 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_library_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = LibraryHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_library_handler_not_ready_when_loading() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
|
||||
@@ -8,7 +7,6 @@ use crate::handlers::radarr_handlers::library::edit_movie_handler::EditMovieHand
|
||||
use crate::handlers::radarr_handlers::library::movie_details_handler::MovieDetailsHandler;
|
||||
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
|
||||
|
||||
use crate::handle_table_events;
|
||||
use crate::handlers::table_handler::TableHandlingConfig;
|
||||
use crate::models::radarr_models::Movie;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
@@ -17,6 +15,7 @@ use crate::models::servarr_data::radarr::radarr_data::{
|
||||
use crate::models::stateful_table::SortOption;
|
||||
use crate::models::{BlockSelectionState, HorizontallyScrollableText};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::{handle_table_events, matches_key};
|
||||
|
||||
mod add_movie_handler;
|
||||
mod delete_movie_handler;
|
||||
@@ -81,6 +80,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
|
||||
|| LIBRARY_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -161,7 +164,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
|
||||
let key = self.key;
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::Movies => match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.edit.key => {
|
||||
_ if matches_key!(edit, key) => {
|
||||
self.app.push_navigation_stack(
|
||||
(
|
||||
ActiveRadarrBlock::EditMoviePrompt,
|
||||
@@ -173,25 +176,25 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
|
||||
self.app.data.radarr_data.selected_block =
|
||||
BlockSelectionState::new(EDIT_MOVIE_SELECTION_BLOCKS);
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.add.key => {
|
||||
_ if matches_key!(add, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into());
|
||||
self.app.data.radarr_data.add_movie_search = Some(HorizontallyScrollableText::default());
|
||||
self.app.should_ignore_quit_key = true;
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.update.key => {
|
||||
_ if matches_key!(update, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::UpdateAllMoviesPrompt.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
_ if matches_key!(refresh, key) => {
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
ActiveRadarrBlock::UpdateAllMoviesPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
if matches_key!(confirm, key) {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateAllMovies);
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use serde_json::Number;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handle_table_events;
|
||||
use crate::handlers::table_handler::TableHandlingConfig;
|
||||
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||
use crate::models::radarr_models::{
|
||||
@@ -16,6 +14,7 @@ use crate::models::servarr_models::Language;
|
||||
use crate::models::stateful_table::SortOption;
|
||||
use crate::models::{BlockSelectionState, Scrollable};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::{handle_table_events, matches_key};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "movie_details_handler_tests.rs"]
|
||||
@@ -136,6 +135,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<
|
||||
MOVIE_DETAILS_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -245,13 +248,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<
|
||||
| ActiveRadarrBlock::Cast
|
||||
| ActiveRadarrBlock::Crew
|
||||
| ActiveRadarrBlock::ManualSearch => match self.key {
|
||||
_ if self.key == DEFAULT_KEYBINDINGS.left.key => {
|
||||
_ if matches_key!(left, self.key) => {
|
||||
self.app.data.radarr_data.movie_info_tabs.previous();
|
||||
self.app.pop_and_push_navigation_stack(
|
||||
self.app.data.radarr_data.movie_info_tabs.get_active_route(),
|
||||
);
|
||||
}
|
||||
_ if self.key == DEFAULT_KEYBINDINGS.right.key => {
|
||||
_ if matches_key!(right, self.key) => {
|
||||
self.app.data.radarr_data.movie_info_tabs.next();
|
||||
self.app.pop_and_push_navigation_stack(
|
||||
self.app.data.radarr_data.movie_info_tabs.get_active_route(),
|
||||
@@ -332,12 +335,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<
|
||||
| ActiveRadarrBlock::Cast
|
||||
| ActiveRadarrBlock::Crew
|
||||
| ActiveRadarrBlock::ManualSearch => match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.auto_search.key => {
|
||||
_ if matches_key!(auto_search, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::AutomaticallySearchMoviePrompt.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.edit.key => {
|
||||
_ if matches_key!(edit, key) => {
|
||||
self.app.push_navigation_stack(
|
||||
(
|
||||
ActiveRadarrBlock::EditMoviePrompt,
|
||||
@@ -349,35 +352,33 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<
|
||||
self.app.data.radarr_data.selected_block =
|
||||
BlockSelectionState::new(EDIT_MOVIE_SELECTION_BLOCKS);
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.update.key => {
|
||||
_ if matches_key!(update, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::UpdateAndScanPrompt.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
_ if matches_key!(refresh, key) => {
|
||||
self
|
||||
.app
|
||||
.pop_and_push_navigation_stack(self.active_radarr_block.into());
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
ActiveRadarrBlock::AutomaticallySearchMoviePrompt
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key =>
|
||||
{
|
||||
ActiveRadarrBlock::AutomaticallySearchMoviePrompt if matches_key!(confirm, key) => {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action =
|
||||
Some(RadarrEvent::TriggerAutomaticSearch(self.extract_movie_id()));
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
ActiveRadarrBlock::UpdateAndScanPrompt if key == DEFAULT_KEYBINDINGS.confirm.key => {
|
||||
ActiveRadarrBlock::UpdateAndScanPrompt if matches_key!(confirm, key) => {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action =
|
||||
Some(RadarrEvent::UpdateAndScan(self.extract_movie_id()));
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
ActiveRadarrBlock::ManualSearchConfirmPrompt if key == DEFAULT_KEYBINDINGS.confirm.key => {
|
||||
ActiveRadarrBlock::ManualSearchConfirmPrompt if matches_key!(confirm, key) => {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DownloadRelease(
|
||||
self.build_radarr_release_download_body(),
|
||||
|
||||
@@ -1042,6 +1042,22 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_movie_details_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = MovieDetailsHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_movie_details_handler_is_not_ready_when_loading(
|
||||
#[values(
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::handlers::radarr_handlers::blocklist::BlocklistHandler;
|
||||
use crate::handlers::radarr_handlers::collections::CollectionsHandler;
|
||||
use crate::handlers::radarr_handlers::downloads::DownloadsHandler;
|
||||
@@ -8,7 +7,7 @@ use crate::handlers::radarr_handlers::root_folders::RootFoldersHandler;
|
||||
use crate::handlers::radarr_handlers::system::SystemHandler;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||
use crate::{App, Key};
|
||||
use crate::{matches_key, App, Key};
|
||||
|
||||
mod blocklist;
|
||||
mod collections;
|
||||
@@ -65,6 +64,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b
|
||||
true
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -109,11 +112,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b
|
||||
pub fn handle_change_tab_left_right_keys(app: &mut App<'_>, key: Key) {
|
||||
let key_ref = key;
|
||||
match key_ref {
|
||||
_ if key == DEFAULT_KEYBINDINGS.left.key => {
|
||||
_ if matches_key!(left, key, app.should_ignore_quit_key) => {
|
||||
app.data.radarr_data.main_tabs.previous();
|
||||
app.pop_and_push_navigation_stack(app.data.radarr_data.main_tabs.get_active_route());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.right.key => {
|
||||
_ if matches_key!(right, key, app.should_ignore_quit_key) => {
|
||||
app.data.radarr_data.main_tabs.next();
|
||||
app.pop_and_push_navigation_stack(app.data.radarr_data.main_tabs.get_active_route());
|
||||
}
|
||||
|
||||
@@ -46,6 +46,76 @@ mod tests {
|
||||
assert_eq!(app.get_current_route(), right_block.into());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(0, ActiveRadarrBlock::System, ActiveRadarrBlock::Collections)]
|
||||
#[case(1, ActiveRadarrBlock::Movies, ActiveRadarrBlock::Downloads)]
|
||||
#[case(2, ActiveRadarrBlock::Collections, ActiveRadarrBlock::Blocklist)]
|
||||
#[case(3, ActiveRadarrBlock::Downloads, ActiveRadarrBlock::RootFolders)]
|
||||
#[case(4, ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Indexers)]
|
||||
#[case(5, ActiveRadarrBlock::RootFolders, ActiveRadarrBlock::System)]
|
||||
#[case(6, ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Movies)]
|
||||
fn test_radarr_handler_change_tab_left_right_keys_alt_navigation(
|
||||
#[case] index: usize,
|
||||
#[case] left_block: ActiveRadarrBlock,
|
||||
#[case] right_block: ActiveRadarrBlock,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = false;
|
||||
app.data.radarr_data.main_tabs.set_index(index);
|
||||
|
||||
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.left.alt.unwrap());
|
||||
|
||||
assert_eq!(
|
||||
app.data.radarr_data.main_tabs.get_active_route(),
|
||||
left_block.into()
|
||||
);
|
||||
assert_eq!(app.get_current_route(), left_block.into());
|
||||
|
||||
app.data.radarr_data.main_tabs.set_index(index);
|
||||
|
||||
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.right.alt.unwrap());
|
||||
|
||||
assert_eq!(
|
||||
app.data.radarr_data.main_tabs.get_active_route(),
|
||||
right_block.into()
|
||||
);
|
||||
assert_eq!(app.get_current_route(), right_block.into());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(0, ActiveRadarrBlock::Movies)]
|
||||
#[case(1, ActiveRadarrBlock::Collections)]
|
||||
#[case(2, ActiveRadarrBlock::Downloads)]
|
||||
#[case(3, ActiveRadarrBlock::Blocklist)]
|
||||
#[case(4, ActiveRadarrBlock::RootFolders)]
|
||||
#[case(5, ActiveRadarrBlock::Indexers)]
|
||||
#[case(6, ActiveRadarrBlock::System)]
|
||||
fn test_radarr_handler_change_tab_left_right_keys_alt_navigation_no_op_when_ignoring_quit_key(
|
||||
#[case] index: usize,
|
||||
#[case] block: ActiveRadarrBlock,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = true;
|
||||
app.push_navigation_stack(block.into());
|
||||
app.data.radarr_data.main_tabs.set_index(index);
|
||||
|
||||
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.left.alt.unwrap());
|
||||
|
||||
assert_eq!(
|
||||
app.data.radarr_data.main_tabs.get_active_route(),
|
||||
block.into()
|
||||
);
|
||||
assert_eq!(app.get_current_route(), block.into());
|
||||
|
||||
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.right.alt.unwrap());
|
||||
|
||||
assert_eq!(
|
||||
app.data.radarr_data.main_tabs.get_active_route(),
|
||||
block.into()
|
||||
);
|
||||
assert_eq!(app.get_current_route(), block.into());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_delegates_system_blocks_to_system_handler(
|
||||
#[values(
|
||||
@@ -217,6 +287,22 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_radarr_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = RadarrHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::Movies,
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_radarr_handler_is_ready() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
|
||||
@@ -8,7 +7,9 @@ use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, ROOT_F
|
||||
use crate::models::servarr_models::{AddRootFolderBody, RootFolder};
|
||||
use crate::models::HorizontallyScrollableText;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::{handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys};
|
||||
use crate::{
|
||||
handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, matches_key,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "root_folders_handler_tests.rs"]
|
||||
@@ -68,6 +69,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'
|
||||
ROOT_FOLDERS_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -195,10 +200,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'
|
||||
let key = self.key;
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::RootFolders => match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
_ if matches_key!(refresh, key) => {
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.add.key => {
|
||||
_ if matches_key!(add, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into());
|
||||
@@ -215,7 +220,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'
|
||||
)
|
||||
}
|
||||
ActiveRadarrBlock::DeleteRootFolderPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
if matches_key!(confirm, key) {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action =
|
||||
Some(RadarrEvent::DeleteRootFolder(self.extract_root_folder_id()));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
@@ -589,7 +590,7 @@ mod tests {
|
||||
app.data.radarr_data.edit_root_folder = Some(HorizontallyScrollableText::default());
|
||||
|
||||
RootFoldersHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveRadarrBlock::AddRootFolderPrompt,
|
||||
None,
|
||||
@@ -598,7 +599,7 @@ mod tests {
|
||||
|
||||
assert_str_eq!(
|
||||
app.data.radarr_data.edit_root_folder.as_ref().unwrap().text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -644,6 +645,22 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_root_folders_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = RootFoldersHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_add_root_folder_body() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::radarr_handlers::system::system_details_handler::SystemDetailsHandler;
|
||||
use crate::handlers::{handle_clear_errors, KeyEventHandler};
|
||||
use crate::matches_key;
|
||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||
use crate::models::Scrollable;
|
||||
|
||||
@@ -35,6 +35,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemHandler<'a, 'b
|
||||
SystemDetailsHandler::accepts(active_block) || active_block == ActiveRadarrBlock::System
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -86,15 +90,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemHandler<'a, 'b
|
||||
if self.active_radarr_block == ActiveRadarrBlock::System {
|
||||
let key = self.key;
|
||||
match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
_ if matches_key!(refresh, key) => {
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.events.key => {
|
||||
_ if matches_key!(events, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::SystemQueuedEvents.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.logs.key => {
|
||||
_ if matches_key!(logs, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::SystemLogs.into());
|
||||
@@ -106,12 +110,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemHandler<'a, 'b
|
||||
.set_items(self.app.data.radarr_data.logs.items.to_vec());
|
||||
self.app.data.radarr_data.log_details.scroll_to_bottom();
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.tasks.key => {
|
||||
_ if matches_key!(tasks, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::SystemTasks.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.update.key => {
|
||||
_ if matches_key!(update, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::SystemUpdates.into());
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||
use crate::matches_key;
|
||||
use crate::models::radarr_models::RadarrTaskName;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS};
|
||||
use crate::models::stateful_list::StatefulList;
|
||||
@@ -36,6 +36,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler
|
||||
SYSTEM_DETAILS_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -114,7 +118,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler
|
||||
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::SystemLogs => match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.left.key => {
|
||||
_ if matches_key!(left, key) => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
@@ -124,7 +128,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler
|
||||
.iter()
|
||||
.for_each(|log| log.scroll_right());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.right.key => {
|
||||
_ if matches_key!(right, key) => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
@@ -178,14 +182,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler
|
||||
}
|
||||
|
||||
fn handle_char_key_event(&mut self) {
|
||||
if SYSTEM_DETAILS_BLOCKS.contains(&self.active_radarr_block)
|
||||
&& self.key == DEFAULT_KEYBINDINGS.refresh.key
|
||||
if SYSTEM_DETAILS_BLOCKS.contains(&self.active_radarr_block) && matches_key!(refresh, self.key)
|
||||
{
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
|
||||
if self.active_radarr_block == ActiveRadarrBlock::SystemTaskStartConfirmPrompt
|
||||
&& self.key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
&& matches_key!(confirm, self.key)
|
||||
{
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action =
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
@@ -938,6 +939,22 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_system_details_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = SystemDetailsHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_task_name() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -450,6 +450,22 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_system_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = SystemHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_handler_is_not_ready_when_loading() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -4,6 +4,7 @@ mod tests {
|
||||
|
||||
use chrono::DateTime;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
@@ -513,6 +514,22 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_blocklist_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = BlocklistHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveSonarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_blocklist_item_id() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handle_table_events;
|
||||
use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::table_handler::TableHandlingConfig;
|
||||
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
|
||||
@@ -9,6 +7,7 @@ use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, BLOCKL
|
||||
use crate::models::sonarr_models::BlocklistItem;
|
||||
use crate::models::stateful_table::SortOption;
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
use crate::{handle_table_events, matches_key};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "blocklist_handler_tests.rs"]
|
||||
@@ -51,6 +50,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a,
|
||||
BLOCKLIST_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -143,10 +146,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a,
|
||||
let key = self.key;
|
||||
match self.active_sonarr_block {
|
||||
ActiveSonarrBlock::Blocklist => match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
_ if matches_key!(refresh, key) => {
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.clear.key => {
|
||||
_ if matches_key!(clear, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::BlocklistClearAllItemsPrompt.into());
|
||||
@@ -154,7 +157,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a,
|
||||
_ => (),
|
||||
},
|
||||
ActiveSonarrBlock::DeleteBlocklistItemPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
if matches_key!(confirm, key) {
|
||||
self.app.data.sonarr_data.prompt_confirm = true;
|
||||
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteBlocklistItem(
|
||||
self.extract_blocklist_item_id(),
|
||||
@@ -164,7 +167,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a,
|
||||
}
|
||||
}
|
||||
ActiveSonarrBlock::BlocklistClearAllItemsPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
if matches_key!(confirm, key) {
|
||||
self.app.data.sonarr_data.prompt_confirm = true;
|
||||
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::ClearBlocklist);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
@@ -389,6 +390,22 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_downloads_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = DownloadsHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveSonarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_download_id() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handle_table_events;
|
||||
use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::table_handler::TableHandlingConfig;
|
||||
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DOWNLOADS_BLOCKS};
|
||||
use crate::models::sonarr_models::DownloadRecord;
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
use crate::{handle_table_events, matches_key};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "downloads_handler_tests.rs"]
|
||||
@@ -47,6 +46,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DownloadsHandler<'a,
|
||||
DOWNLOADS_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -130,18 +133,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DownloadsHandler<'a,
|
||||
let key = self.key;
|
||||
match self.active_sonarr_block {
|
||||
ActiveSonarrBlock::Downloads => match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.update.key => {
|
||||
_ if matches_key!(update, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::UpdateDownloadsPrompt.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
_ if matches_key!(refresh, key) => {
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
ActiveSonarrBlock::DeleteDownloadPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
if matches_key!(confirm, key) {
|
||||
self.app.data.sonarr_data.prompt_confirm = true;
|
||||
self.app.data.sonarr_data.prompt_confirm_action =
|
||||
Some(SonarrEvent::DeleteDownload(self.extract_download_id()));
|
||||
@@ -150,7 +153,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DownloadsHandler<'a,
|
||||
}
|
||||
}
|
||||
ActiveSonarrBlock::UpdateDownloadsPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
if matches_key!(confirm, key) {
|
||||
self.app.data.sonarr_data.prompt_confirm = true;
|
||||
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::UpdateDownloads);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ mod tests {
|
||||
|
||||
use chrono::DateTime;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
@@ -306,6 +307,22 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_history_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = HistoryHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveSonarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_handler_not_ready_when_loading() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handle_table_events;
|
||||
use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::table_handler::TableHandlingConfig;
|
||||
use crate::handlers::{handle_clear_errors, KeyEventHandler};
|
||||
@@ -9,6 +7,7 @@ use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, HISTOR
|
||||
use crate::models::servarr_models::Language;
|
||||
use crate::models::sonarr_models::SonarrHistoryItem;
|
||||
use crate::models::stateful_table::SortOption;
|
||||
use crate::{handle_table_events, matches_key};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "history_handler_tests.rs"]
|
||||
@@ -52,6 +51,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for HistoryHandler<'a, '
|
||||
HISTORY_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -110,7 +113,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for HistoryHandler<'a, '
|
||||
let key = self.key;
|
||||
if self.active_sonarr_block == ActiveSonarrBlock::History {
|
||||
match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
_ if matches_key!(refresh, key) => {
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
_ => (),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||
@@ -6,7 +5,9 @@ use crate::models::servarr_data::modals::EditIndexerModal;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_INDEXER_BLOCKS};
|
||||
use crate::models::servarr_models::EditIndexerParams;
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
use crate::{handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys};
|
||||
use crate::{
|
||||
handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys, matches_key,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "edit_indexer_handler_tests.rs"]
|
||||
@@ -64,6 +65,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditIndexerHandler<'
|
||||
EDIT_INDEXER_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -503,7 +508,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditIndexerHandler<'
|
||||
ActiveSonarrBlock::EditIndexerPrompt => {
|
||||
if self.app.data.sonarr_data.selected_block.get_active_block()
|
||||
== ActiveSonarrBlock::EditIndexerConfirmPrompt
|
||||
&& self.key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
&& matches_key!(confirm, self.key)
|
||||
{
|
||||
self.app.data.sonarr_data.prompt_confirm = true;
|
||||
self.app.data.sonarr_data.prompt_confirm_action =
|
||||
|
||||
@@ -10,6 +10,7 @@ mod tests {
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_INDEXER_BLOCKS};
|
||||
use crate::models::servarr_models::EditIndexerParams;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
mod test_handle_scroll_up_and_down {
|
||||
@@ -1597,7 +1598,7 @@ mod tests {
|
||||
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
|
||||
|
||||
EditIndexerHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveSonarrBlock::EditIndexerNameInput,
|
||||
None,
|
||||
@@ -1613,7 +1614,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.name
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1624,7 +1625,7 @@ mod tests {
|
||||
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
|
||||
|
||||
EditIndexerHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveSonarrBlock::EditIndexerUrlInput,
|
||||
None,
|
||||
@@ -1640,7 +1641,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.url
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1651,7 +1652,7 @@ mod tests {
|
||||
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
|
||||
|
||||
EditIndexerHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveSonarrBlock::EditIndexerApiKeyInput,
|
||||
None,
|
||||
@@ -1667,7 +1668,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.api_key
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1678,7 +1679,7 @@ mod tests {
|
||||
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
|
||||
|
||||
EditIndexerHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveSonarrBlock::EditIndexerSeedRatioInput,
|
||||
None,
|
||||
@@ -1694,7 +1695,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.seed_ratio
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1705,7 +1706,7 @@ mod tests {
|
||||
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
|
||||
|
||||
EditIndexerHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveSonarrBlock::EditIndexerTagsInput,
|
||||
None,
|
||||
@@ -1721,7 +1722,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.tags
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1793,6 +1794,22 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_edit_indexer_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = EditIndexerHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveSonarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_edit_indexer_params() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handle_prompt_left_right_keys;
|
||||
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{
|
||||
ActiveSonarrBlock, INDEXER_SETTINGS_BLOCKS,
|
||||
};
|
||||
use crate::models::sonarr_models::IndexerSettings;
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
use crate::{handle_prompt_left_right_keys, matches_key};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "edit_indexer_settings_handler_tests.rs"]
|
||||
@@ -37,6 +36,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexerSettingsHandl
|
||||
INDEXER_SETTINGS_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -183,7 +186,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexerSettingsHandl
|
||||
if self.active_sonarr_block == ActiveSonarrBlock::AllIndexerSettingsPrompt
|
||||
&& self.app.data.sonarr_data.selected_block.get_active_block()
|
||||
== ActiveSonarrBlock::IndexerSettingsConfirmPrompt
|
||||
&& self.key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
&& matches_key!(confirm, self.key)
|
||||
{
|
||||
self.app.data.sonarr_data.prompt_confirm = true;
|
||||
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::EditAllIndexerSettings(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
@@ -521,6 +522,22 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_indexer_settings_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = IndexerSettingsHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveSonarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_edit_indexer_settings_params() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -643,6 +643,22 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_indexers_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = IndexersHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveSonarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_indexer_id() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handle_table_events;
|
||||
use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::sonarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler;
|
||||
use crate::handlers::sonarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler;
|
||||
@@ -15,6 +13,7 @@ use crate::models::servarr_data::sonarr::sonarr_data::{
|
||||
use crate::models::servarr_models::Indexer;
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
use crate::{handle_table_events, matches_key};
|
||||
|
||||
mod edit_indexer_handler;
|
||||
mod edit_indexer_settings_handler;
|
||||
@@ -70,6 +69,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexersHandler<'a,
|
||||
|| INDEXERS_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -168,20 +171,20 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexersHandler<'a,
|
||||
let key = self.key;
|
||||
match self.active_sonarr_block {
|
||||
ActiveSonarrBlock::Indexers => match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
_ if matches_key!(refresh, key) => {
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.test.key => {
|
||||
_ if matches_key!(test, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::TestIndexer.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.test_all.key => {
|
||||
_ if matches_key!(test_all, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::TestAllIndexers.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.settings.key => {
|
||||
_ if matches_key!(settings, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::AllIndexerSettingsPrompt.into());
|
||||
@@ -191,7 +194,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexersHandler<'a,
|
||||
_ => (),
|
||||
},
|
||||
ActiveSonarrBlock::DeleteIndexerPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
if matches_key!(confirm, key) {
|
||||
self.app.data.sonarr_data.prompt_confirm = true;
|
||||
self.app.data.sonarr_data.prompt_confirm_action =
|
||||
Some(SonarrEvent::DeleteIndexer(self.extract_indexer_id()));
|
||||
|
||||
@@ -48,6 +48,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for TestAllIndexersHandl
|
||||
active_block == ActiveSonarrBlock::TestAllIndexers
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
|
||||
@@ -7,6 +7,7 @@ mod tests {
|
||||
use crate::models::servarr_data::modals::IndexerTestResultModalItem;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
mod test_handle_esc {
|
||||
@@ -48,6 +49,22 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_test_all_indexers_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = TestAllIndexersHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveSonarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_test_all_indexers_handler_is_not_ready_when_loading() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::handlers::table_handler::TableHandlingConfig;
|
||||
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||
use crate::models::servarr_data::sonarr::modals::AddSeriesModal;
|
||||
@@ -9,7 +8,9 @@ use crate::models::sonarr_models::{AddSeriesBody, AddSeriesOptions, AddSeriesSea
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
use crate::models::{BlockSelectionState, Scrollable};
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
use crate::{handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, App, Key};
|
||||
use crate::{
|
||||
handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, matches_key, App, Key,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "add_series_handler_tests.rs"]
|
||||
@@ -126,6 +127,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
|
||||
ADD_SERIES_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -609,7 +614,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
|
||||
ActiveSonarrBlock::AddSeriesPrompt => {
|
||||
if self.app.data.sonarr_data.selected_block.get_active_block()
|
||||
== ActiveSonarrBlock::AddSeriesConfirmPrompt
|
||||
&& key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
&& matches_key!(confirm, key)
|
||||
{
|
||||
self.app.data.sonarr_data.prompt_confirm = true;
|
||||
self.app.data.sonarr_data.prompt_confirm_action =
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
mod tests {
|
||||
use bimap::BiMap;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
@@ -1570,7 +1571,7 @@ mod tests {
|
||||
app.data.sonarr_data.add_series_search = Some(HorizontallyScrollableText::default());
|
||||
|
||||
AddSeriesHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveSonarrBlock::AddSeriesSearchInput,
|
||||
None,
|
||||
@@ -1585,7 +1586,7 @@ mod tests {
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1596,7 +1597,7 @@ mod tests {
|
||||
app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default());
|
||||
|
||||
AddSeriesHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveSonarrBlock::AddSeriesTagsInput,
|
||||
None,
|
||||
@@ -1612,7 +1613,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.tags
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1714,6 +1715,22 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_add_series_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = AddSeriesHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveSonarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_series_search_no_panic_on_none_search_result() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use crate::models::sonarr_models::DeleteSeriesParams;
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
use crate::{
|
||||
app::{key_binding::DEFAULT_KEYBINDINGS, App},
|
||||
app::App,
|
||||
event::Key,
|
||||
handlers::{handle_prompt_toggle, KeyEventHandler},
|
||||
matches_key,
|
||||
models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DELETE_SERIES_BLOCKS},
|
||||
};
|
||||
|
||||
@@ -38,6 +39,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DeleteSeriesHandler<
|
||||
DELETE_SERIES_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -123,7 +128,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DeleteSeriesHandler<
|
||||
if self.active_sonarr_block == ActiveSonarrBlock::DeleteSeriesPrompt
|
||||
&& self.app.data.sonarr_data.selected_block.get_active_block()
|
||||
== ActiveSonarrBlock::DeleteSeriesConfirmPrompt
|
||||
&& self.key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
&& matches_key!(confirm, self.key)
|
||||
{
|
||||
self.app.data.sonarr_data.prompt_confirm = true;
|
||||
self.app.data.sonarr_data.prompt_confirm_action =
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
@@ -320,6 +321,22 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_delete_series_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = DeleteSeriesHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveSonarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_delete_series_params() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||
@@ -7,7 +6,7 @@ use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_S
|
||||
use crate::models::sonarr_models::EditSeriesParams;
|
||||
use crate::models::Scrollable;
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
use crate::{handle_text_box_keys, handle_text_box_left_right_keys};
|
||||
use crate::{handle_text_box_keys, handle_text_box_left_right_keys, matches_key};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "edit_series_handler_tests.rs"]
|
||||
@@ -83,6 +82,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditSeriesHandler<'a
|
||||
EDIT_SERIES_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -450,7 +453,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditSeriesHandler<'a
|
||||
ActiveSonarrBlock::EditSeriesPrompt => {
|
||||
if self.app.data.sonarr_data.selected_block.get_active_block()
|
||||
== ActiveSonarrBlock::EditSeriesConfirmPrompt
|
||||
&& key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
&& matches_key!(confirm, key)
|
||||
{
|
||||
self.app.data.sonarr_data.prompt_confirm = true;
|
||||
self.app.data.sonarr_data.prompt_confirm_action =
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
mod tests {
|
||||
use bimap::BiMap;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
@@ -1243,7 +1244,7 @@ mod tests {
|
||||
app.data.sonarr_data.edit_series_modal = Some(EditSeriesModal::default());
|
||||
|
||||
EditSeriesHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveSonarrBlock::EditSeriesPathInput,
|
||||
None,
|
||||
@@ -1259,7 +1260,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.path
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1270,7 +1271,7 @@ mod tests {
|
||||
app.data.sonarr_data.edit_series_modal = Some(EditSeriesModal::default());
|
||||
|
||||
EditSeriesHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveSonarrBlock::EditSeriesTagsInput,
|
||||
None,
|
||||
@@ -1286,7 +1287,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.tags
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1368,6 +1369,22 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_edit_series_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = EditSeriesHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveSonarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_edit_series_params() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handle_table_events;
|
||||
use crate::handlers::sonarr_handlers::library::season_details_handler::releases_sorting_options;
|
||||
use crate::handlers::table_handler::TableHandlingConfig;
|
||||
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EPISODE_DETAILS_BLOCKS};
|
||||
use crate::models::sonarr_models::{SonarrHistoryItem, SonarrRelease, SonarrReleaseDownloadBody};
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
use crate::{handle_table_events, matches_key};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "episode_details_handler_tests.rs"]
|
||||
@@ -88,6 +87,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle
|
||||
EPISODE_DETAILS_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -142,7 +145,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle
|
||||
| ActiveSonarrBlock::EpisodeHistory
|
||||
| ActiveSonarrBlock::EpisodeFile
|
||||
| ActiveSonarrBlock::ManualEpisodeSearch => match self.key {
|
||||
_ if self.key == DEFAULT_KEYBINDINGS.left.key => {
|
||||
_ if matches_key!(left, self.key) => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
@@ -170,7 +173,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle
|
||||
.get_active_route(),
|
||||
);
|
||||
}
|
||||
_ if self.key == DEFAULT_KEYBINDINGS.right.key => {
|
||||
_ if matches_key!(right, self.key) => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
@@ -306,21 +309,19 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle
|
||||
| ActiveSonarrBlock::EpisodeHistory
|
||||
| ActiveSonarrBlock::EpisodeFile
|
||||
| ActiveSonarrBlock::ManualEpisodeSearch => match self.key {
|
||||
_ if self.key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
_ if matches_key!(refresh, self.key) => {
|
||||
self
|
||||
.app
|
||||
.pop_and_push_navigation_stack(self.active_sonarr_block.into());
|
||||
}
|
||||
_ if self.key == DEFAULT_KEYBINDINGS.auto_search.key => {
|
||||
_ if matches_key!(auto_search, self.key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchEpisodePrompt.into());
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
ActiveSonarrBlock::AutomaticallySearchEpisodePrompt
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key =>
|
||||
{
|
||||
ActiveSonarrBlock::AutomaticallySearchEpisodePrompt if matches_key!(confirm, key) => {
|
||||
self.app.data.sonarr_data.prompt_confirm = true;
|
||||
self.app.data.sonarr_data.prompt_confirm_action = Some(
|
||||
SonarrEvent::TriggerAutomaticEpisodeSearch(self.extract_episode_id()),
|
||||
@@ -328,9 +329,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key =>
|
||||
{
|
||||
ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt if matches_key!(confirm, key) => {
|
||||
if self.app.data.sonarr_data.prompt_confirm {
|
||||
let SonarrRelease {
|
||||
guid, indexer_id, ..
|
||||
|
||||
@@ -625,6 +625,22 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_episode_details_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = EpisodeDetailsHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveSonarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_episode_id() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -827,6 +827,22 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_library_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = LibraryHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveSonarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_library_handler_not_ready_when_loading() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::{
|
||||
event::Key,
|
||||
handle_table_events,
|
||||
handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler},
|
||||
matches_key,
|
||||
models::{
|
||||
servarr_data::sonarr::sonarr_data::{
|
||||
ActiveSonarrBlock, DELETE_SERIES_SELECTION_BLOCKS, EDIT_SERIES_SELECTION_BLOCKS,
|
||||
@@ -21,7 +22,6 @@ use crate::{
|
||||
};
|
||||
|
||||
use super::handle_change_tab_left_right_keys;
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::handlers::sonarr_handlers::library::episode_details_handler::EpisodeDetailsHandler;
|
||||
use crate::handlers::sonarr_handlers::library::season_details_handler::SeasonDetailsHandler;
|
||||
use crate::handlers::sonarr_handlers::library::series_details_handler::SeriesDetailsHandler;
|
||||
@@ -102,6 +102,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, '
|
||||
|| LIBRARY_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -182,7 +186,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, '
|
||||
let key = self.key;
|
||||
match self.active_sonarr_block {
|
||||
ActiveSonarrBlock::Series => match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.edit.key => {
|
||||
_ if matches_key!(edit, key) => {
|
||||
self.app.push_navigation_stack(
|
||||
(
|
||||
ActiveSonarrBlock::EditSeriesPrompt,
|
||||
@@ -194,25 +198,25 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, '
|
||||
self.app.data.sonarr_data.selected_block =
|
||||
BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS);
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.add.key => {
|
||||
_ if matches_key!(add, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchInput.into());
|
||||
self.app.data.sonarr_data.add_series_search = Some(HorizontallyScrollableText::default());
|
||||
self.app.should_ignore_quit_key = true;
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.update.key => {
|
||||
_ if matches_key!(update, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::UpdateAllSeriesPrompt.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
_ if matches_key!(refresh, key) => {
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
ActiveSonarrBlock::UpdateAllSeriesPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
if matches_key!(confirm, key) {
|
||||
self.app.data.sonarr_data.prompt_confirm = true;
|
||||
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::UpdateAllSeries);
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handle_table_events;
|
||||
use crate::handlers::sonarr_handlers::history::history_sorting_options;
|
||||
use crate::handlers::table_handler::TableHandlingConfig;
|
||||
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||
@@ -12,6 +10,7 @@ use crate::models::sonarr_models::{
|
||||
};
|
||||
use crate::models::stateful_table::SortOption;
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
use crate::{handle_table_events, matches_key};
|
||||
use serde_json::Number;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -140,6 +139,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
|
||||
SEASON_DETAILS_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -193,7 +196,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
|
||||
ActiveSonarrBlock::SeasonDetails
|
||||
| ActiveSonarrBlock::SeasonHistory
|
||||
| ActiveSonarrBlock::ManualSeasonSearch => match self.key {
|
||||
_ if self.key == DEFAULT_KEYBINDINGS.left.key => {
|
||||
_ if matches_key!(left, self.key) => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
@@ -215,7 +218,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
|
||||
.get_active_route(),
|
||||
);
|
||||
}
|
||||
_ if self.key == DEFAULT_KEYBINDINGS.right.key => {
|
||||
_ if matches_key!(right, self.key) => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
@@ -378,7 +381,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
|
||||
fn handle_char_key_event(&mut self) {
|
||||
let key = self.key;
|
||||
match self.active_sonarr_block {
|
||||
ActiveSonarrBlock::SeasonDetails if self.key == DEFAULT_KEYBINDINGS.toggle_monitoring.key => {
|
||||
ActiveSonarrBlock::SeasonDetails if matches_key!(toggle_monitoring, self.key) => {
|
||||
self.app.data.sonarr_data.prompt_confirm = true;
|
||||
self.app.data.sonarr_data.prompt_confirm_action = Some(
|
||||
SonarrEvent::ToggleEpisodeMonitoring(self.extract_episode_id()),
|
||||
@@ -391,21 +394,19 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
|
||||
ActiveSonarrBlock::SeasonDetails
|
||||
| ActiveSonarrBlock::SeasonHistory
|
||||
| ActiveSonarrBlock::ManualSeasonSearch => match self.key {
|
||||
_ if self.key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
_ if matches_key!(refresh, self.key) => {
|
||||
self
|
||||
.app
|
||||
.pop_and_push_navigation_stack(self.active_sonarr_block.into());
|
||||
}
|
||||
_ if self.key == DEFAULT_KEYBINDINGS.auto_search.key => {
|
||||
_ if matches_key!(auto_search, self.key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchSeasonPrompt.into());
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key =>
|
||||
{
|
||||
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt if matches_key!(confirm, key) => {
|
||||
self.app.data.sonarr_data.prompt_confirm = true;
|
||||
self.app.data.sonarr_data.prompt_confirm_action = Some(
|
||||
SonarrEvent::TriggerAutomaticSeasonSearch(self.extract_series_id_season_number_tuple()),
|
||||
@@ -413,7 +414,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
ActiveSonarrBlock::DeleteEpisodeFilePrompt if key == DEFAULT_KEYBINDINGS.confirm.key => {
|
||||
ActiveSonarrBlock::DeleteEpisodeFilePrompt if matches_key!(confirm, key) => {
|
||||
self.app.data.sonarr_data.prompt_confirm = true;
|
||||
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteEpisodeFile(
|
||||
self.extract_episode_file_id(),
|
||||
@@ -421,9 +422,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key =>
|
||||
{
|
||||
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt if matches_key!(confirm, key) => {
|
||||
self.app.data.sonarr_data.prompt_confirm = true;
|
||||
let SonarrRelease {
|
||||
guid, indexer_id, ..
|
||||
|
||||
@@ -789,6 +789,22 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_season_details_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = SeasonDetailsHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveSonarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_episode_file_id() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handle_table_events;
|
||||
use crate::handlers::sonarr_handlers::history::history_sorting_options;
|
||||
use crate::handlers::table_handler::TableHandlingConfig;
|
||||
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||
@@ -11,6 +9,7 @@ use crate::models::servarr_data::sonarr::sonarr_data::{
|
||||
use crate::models::sonarr_models::{Season, SonarrHistoryItem};
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
use crate::{handle_table_events, matches_key};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "series_details_handler_tests.rs"]
|
||||
@@ -91,6 +90,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
|
||||
SERIES_DETAILS_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -130,7 +133,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
|
||||
fn handle_left_right_action(&mut self) {
|
||||
match self.active_sonarr_block {
|
||||
ActiveSonarrBlock::SeriesDetails | ActiveSonarrBlock::SeriesHistory => match self.key {
|
||||
_ if self.key == DEFAULT_KEYBINDINGS.left.key => {
|
||||
_ if matches_key!(left, self.key) => {
|
||||
self.app.data.sonarr_data.series_info_tabs.previous();
|
||||
self.app.pop_and_push_navigation_stack(
|
||||
self
|
||||
@@ -141,7 +144,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
|
||||
.get_active_route(),
|
||||
);
|
||||
}
|
||||
_ if self.key == DEFAULT_KEYBINDINGS.right.key => {
|
||||
_ if matches_key!(right, self.key) => {
|
||||
self.app.data.sonarr_data.series_info_tabs.next();
|
||||
self.app.pop_and_push_navigation_stack(
|
||||
self
|
||||
@@ -250,20 +253,20 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
|
||||
let key = self.key;
|
||||
match self.active_sonarr_block {
|
||||
ActiveSonarrBlock::SeriesDetails => match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => self
|
||||
_ if matches_key!(refresh, key) => self
|
||||
.app
|
||||
.pop_and_push_navigation_stack(self.active_sonarr_block.into()),
|
||||
_ if key == DEFAULT_KEYBINDINGS.auto_search.key => {
|
||||
_ if matches_key!(auto_search, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchSeriesPrompt.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.update.key => {
|
||||
_ if matches_key!(update, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::UpdateAndScanSeriesPrompt.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.edit.key => {
|
||||
_ if matches_key!(edit, key) => {
|
||||
self.app.push_navigation_stack(
|
||||
(
|
||||
ActiveSonarrBlock::EditSeriesPrompt,
|
||||
@@ -275,7 +278,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
|
||||
self.app.data.sonarr_data.selected_block =
|
||||
BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS);
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.toggle_monitoring.key => {
|
||||
_ if matches_key!(toggle_monitoring, key) => {
|
||||
self.app.data.sonarr_data.prompt_confirm = true;
|
||||
self.app.data.sonarr_data.prompt_confirm_action = Some(
|
||||
SonarrEvent::ToggleSeasonMonitoring(self.extract_series_id_season_number_tuple()),
|
||||
@@ -288,15 +291,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
|
||||
_ => (),
|
||||
},
|
||||
ActiveSonarrBlock::SeriesHistory => match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => self
|
||||
_ if matches_key!(refresh, key) => self
|
||||
.app
|
||||
.pop_and_push_navigation_stack(self.active_sonarr_block.into()),
|
||||
_ if key == DEFAULT_KEYBINDINGS.auto_search.key => {
|
||||
_ if matches_key!(auto_search, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchSeriesPrompt.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.edit.key => {
|
||||
_ if matches_key!(edit, key) => {
|
||||
self.app.push_navigation_stack(
|
||||
(
|
||||
ActiveSonarrBlock::EditSeriesPrompt,
|
||||
@@ -308,7 +311,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
|
||||
self.app.data.sonarr_data.selected_block =
|
||||
BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS);
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.update.key => {
|
||||
_ if matches_key!(update, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::UpdateAndScanSeriesPrompt.into());
|
||||
@@ -316,7 +319,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
|
||||
_ => (),
|
||||
},
|
||||
ActiveSonarrBlock::AutomaticallySearchSeriesPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
if matches_key!(confirm, key) {
|
||||
self.app.data.sonarr_data.prompt_confirm = true;
|
||||
self.app.data.sonarr_data.prompt_confirm_action = Some(
|
||||
SonarrEvent::TriggerAutomaticSeriesSearch(self.extract_series_id()),
|
||||
|
||||
@@ -611,6 +611,22 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_series_details_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = SeriesDetailsHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveSonarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_series_id_season_number_tuple() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -7,9 +7,7 @@ use root_folders::RootFoldersHandler;
|
||||
use system::SystemHandler;
|
||||
|
||||
use crate::{
|
||||
app::{key_binding::DEFAULT_KEYBINDINGS, App},
|
||||
event::Key,
|
||||
models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock,
|
||||
app::App, event::Key, matches_key, models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock,
|
||||
};
|
||||
|
||||
use super::KeyEventHandler;
|
||||
@@ -69,6 +67,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SonarrHandler<'a, 'b
|
||||
true
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -113,11 +115,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SonarrHandler<'a, 'b
|
||||
pub fn handle_change_tab_left_right_keys(app: &mut App<'_>, key: Key) {
|
||||
let key_ref = key;
|
||||
match key_ref {
|
||||
_ if key == DEFAULT_KEYBINDINGS.left.key => {
|
||||
_ if matches_key!(left, key, app.should_ignore_quit_key) => {
|
||||
app.data.sonarr_data.main_tabs.previous();
|
||||
app.pop_and_push_navigation_stack(app.data.sonarr_data.main_tabs.get_active_route());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.right.key => {
|
||||
_ if matches_key!(right, key, app.should_ignore_quit_key) => {
|
||||
app.data.sonarr_data.main_tabs.next();
|
||||
app.pop_and_push_navigation_stack(app.data.sonarr_data.main_tabs.get_active_route());
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
|
||||
@@ -8,7 +7,9 @@ use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, ROOT_F
|
||||
use crate::models::servarr_models::{AddRootFolderBody, RootFolder};
|
||||
use crate::models::HorizontallyScrollableText;
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
use crate::{handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys};
|
||||
use crate::{
|
||||
handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, matches_key,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "root_folders_handler_tests.rs"]
|
||||
@@ -66,6 +67,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<'
|
||||
ROOT_FOLDERS_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -193,10 +198,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<'
|
||||
let key = self.key;
|
||||
match self.active_sonarr_block {
|
||||
ActiveSonarrBlock::RootFolders => match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
_ if matches_key!(refresh, key) => {
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.add.key => {
|
||||
_ if matches_key!(add, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::AddRootFolderPrompt.into());
|
||||
@@ -213,7 +218,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<'
|
||||
)
|
||||
}
|
||||
ActiveSonarrBlock::DeleteRootFolderPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
if matches_key!(confirm, key) {
|
||||
self.app.data.sonarr_data.prompt_confirm = true;
|
||||
self.app.data.sonarr_data.prompt_confirm_action =
|
||||
Some(SonarrEvent::DeleteRootFolder(self.extract_root_folder_id()));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
@@ -600,7 +601,7 @@ mod tests {
|
||||
app.data.sonarr_data.edit_root_folder = Some(HorizontallyScrollableText::default());
|
||||
|
||||
RootFoldersHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveSonarrBlock::AddRootFolderPrompt,
|
||||
None,
|
||||
@@ -609,7 +610,7 @@ mod tests {
|
||||
|
||||
assert_str_eq!(
|
||||
app.data.sonarr_data.edit_root_folder.as_ref().unwrap().text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -655,6 +656,22 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_root_folders_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = RootFoldersHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveSonarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_root_folder_id() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -191,7 +191,7 @@ pub(in crate::handlers::sonarr_handlers) mod utils {
|
||||
title: "Test Download Title".to_owned(),
|
||||
status: DownloadStatus::Downloading,
|
||||
id: 1,
|
||||
episode_id: 1,
|
||||
episode_id: Some(Number::from(1i64)),
|
||||
size: 3543348019f64,
|
||||
sizeleft: 1771674009f64,
|
||||
output_path: Some(HorizontallyScrollableText::from(
|
||||
@@ -327,7 +327,7 @@ pub(in crate::handlers::sonarr_handlers) mod utils {
|
||||
title: None,
|
||||
season_number: 1,
|
||||
monitored: true,
|
||||
statistics: season_statistics(),
|
||||
statistics: Some(season_statistics()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ mod tests {
|
||||
use crate::test_handler_delegation;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
#[rstest]
|
||||
#[case(0, ActiveSonarrBlock::System, ActiveSonarrBlock::Downloads)]
|
||||
@@ -45,6 +46,77 @@ mod tests {
|
||||
assert_eq!(app.get_current_route(), right_block.into());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(0, ActiveSonarrBlock::System, ActiveSonarrBlock::Downloads)]
|
||||
#[case(1, ActiveSonarrBlock::Series, ActiveSonarrBlock::Blocklist)]
|
||||
#[case(2, ActiveSonarrBlock::Downloads, ActiveSonarrBlock::History)]
|
||||
#[case(3, ActiveSonarrBlock::Blocklist, ActiveSonarrBlock::RootFolders)]
|
||||
#[case(4, ActiveSonarrBlock::History, ActiveSonarrBlock::Indexers)]
|
||||
#[case(5, ActiveSonarrBlock::RootFolders, ActiveSonarrBlock::System)]
|
||||
#[case(6, ActiveSonarrBlock::Indexers, ActiveSonarrBlock::Series)]
|
||||
fn test_sonarr_handler_change_tab_left_right_keys_alt_navigation(
|
||||
#[case] index: usize,
|
||||
#[case] left_block: ActiveSonarrBlock,
|
||||
#[case] right_block: ActiveSonarrBlock,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.data.sonarr_data.main_tabs.set_index(index);
|
||||
|
||||
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.left.alt.unwrap());
|
||||
|
||||
assert_eq!(
|
||||
app.data.sonarr_data.main_tabs.get_active_route(),
|
||||
left_block.into()
|
||||
);
|
||||
assert_eq!(app.get_current_route(), left_block.into());
|
||||
|
||||
app.data.sonarr_data.main_tabs.set_index(index);
|
||||
|
||||
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.right.alt.unwrap());
|
||||
|
||||
assert_eq!(
|
||||
app.data.sonarr_data.main_tabs.get_active_route(),
|
||||
right_block.into()
|
||||
);
|
||||
assert_eq!(app.get_current_route(), right_block.into());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(0, ActiveSonarrBlock::Series)]
|
||||
#[case(1, ActiveSonarrBlock::Downloads)]
|
||||
#[case(2, ActiveSonarrBlock::Blocklist)]
|
||||
#[case(3, ActiveSonarrBlock::History)]
|
||||
#[case(4, ActiveSonarrBlock::RootFolders)]
|
||||
#[case(5, ActiveSonarrBlock::Indexers)]
|
||||
#[case(6, ActiveSonarrBlock::System)]
|
||||
fn test_sonarr_handler_change_tab_left_right_keys_alt_navigation_no_op_when_ignoring_quit_key(
|
||||
#[case] index: usize,
|
||||
#[case] block: ActiveSonarrBlock,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(block.into());
|
||||
app.should_ignore_quit_key = true;
|
||||
app.data.sonarr_data.main_tabs.set_index(index);
|
||||
|
||||
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.left.alt.unwrap());
|
||||
|
||||
assert_eq!(
|
||||
app.data.sonarr_data.main_tabs.get_active_route(),
|
||||
block.into()
|
||||
);
|
||||
assert_eq!(app.get_current_route(), block.into());
|
||||
|
||||
app.data.sonarr_data.main_tabs.set_index(index);
|
||||
|
||||
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.right.alt.unwrap());
|
||||
|
||||
assert_eq!(
|
||||
app.data.sonarr_data.main_tabs.get_active_route(),
|
||||
block.into()
|
||||
);
|
||||
assert_eq!(app.get_current_route(), block.into());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_delegates_library_blocks_to_library_handler(
|
||||
#[values(
|
||||
@@ -59,10 +131,10 @@ mod tests {
|
||||
ActiveSonarrBlock::AddSeriesSelectRootFolder,
|
||||
ActiveSonarrBlock::AddSeriesSelectSeriesType,
|
||||
ActiveSonarrBlock::AddSeriesTagsInput,
|
||||
// ActiveSonarrBlock::AutomaticallySearchEpisodePrompt,
|
||||
// ActiveSonarrBlock::AutomaticallySearchSeasonPrompt,
|
||||
// ActiveSonarrBlock::AutomaticallySearchSeriesPrompt,
|
||||
// ActiveSonarrBlock::DeleteEpisodeFilePrompt,
|
||||
ActiveSonarrBlock::AutomaticallySearchEpisodePrompt,
|
||||
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt,
|
||||
ActiveSonarrBlock::AutomaticallySearchSeriesPrompt,
|
||||
ActiveSonarrBlock::DeleteEpisodeFilePrompt,
|
||||
ActiveSonarrBlock::DeleteSeriesPrompt,
|
||||
ActiveSonarrBlock::EditSeriesPrompt,
|
||||
ActiveSonarrBlock::EditSeriesPathInput,
|
||||
@@ -70,39 +142,36 @@ mod tests {
|
||||
ActiveSonarrBlock::EditSeriesSelectQualityProfile,
|
||||
ActiveSonarrBlock::EditSeriesSelectLanguageProfile,
|
||||
ActiveSonarrBlock::EditSeriesTagsInput,
|
||||
// ActiveSonarrBlock::EpisodeDetails,
|
||||
// ActiveSonarrBlock::EpisodeFile,
|
||||
// ActiveSonarrBlock::EpisodeHistory,
|
||||
// ActiveSonarrBlock::EpisodesSortPrompt,
|
||||
// ActiveSonarrBlock::FilterEpisodes,
|
||||
// ActiveSonarrBlock::FilterEpisodesError,
|
||||
ActiveSonarrBlock::EpisodeDetails,
|
||||
ActiveSonarrBlock::EpisodeFile,
|
||||
ActiveSonarrBlock::EpisodeHistory,
|
||||
ActiveSonarrBlock::FilterSeries,
|
||||
ActiveSonarrBlock::FilterSeriesError,
|
||||
// ActiveSonarrBlock::FilterSeriesHistory,
|
||||
// ActiveSonarrBlock::FilterSeriesHistoryError,
|
||||
// ActiveSonarrBlock::ManualEpisodeSearch,
|
||||
// ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt,
|
||||
// ActiveSonarrBlock::ManualEpisodeSearchSortPrompt,
|
||||
// ActiveSonarrBlock::ManualSeasonSearch,
|
||||
// ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt,
|
||||
// ActiveSonarrBlock::ManualSeasonSearchSortPrompt,
|
||||
// ActiveSonarrBlock::SearchEpisodes,
|
||||
// ActiveSonarrBlock::SearchEpisodesError,
|
||||
// ActiveSonarrBlock::SearchSeason,
|
||||
// ActiveSonarrBlock::SearchSeasonError,
|
||||
ActiveSonarrBlock::FilterSeriesHistory,
|
||||
ActiveSonarrBlock::FilterSeriesHistoryError,
|
||||
ActiveSonarrBlock::ManualEpisodeSearch,
|
||||
ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt,
|
||||
ActiveSonarrBlock::ManualEpisodeSearchSortPrompt,
|
||||
ActiveSonarrBlock::ManualSeasonSearch,
|
||||
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt,
|
||||
ActiveSonarrBlock::ManualSeasonSearchSortPrompt,
|
||||
ActiveSonarrBlock::SearchEpisodes,
|
||||
ActiveSonarrBlock::SearchEpisodesError,
|
||||
ActiveSonarrBlock::SearchSeason,
|
||||
ActiveSonarrBlock::SearchSeasonError,
|
||||
ActiveSonarrBlock::SearchSeries,
|
||||
ActiveSonarrBlock::SearchSeriesError,
|
||||
// ActiveSonarrBlock::SearchSeriesHistory,
|
||||
// ActiveSonarrBlock::SearchSeriesHistoryError,
|
||||
// ActiveSonarrBlock::SeasonDetails,
|
||||
ActiveSonarrBlock::SearchSeriesHistory,
|
||||
ActiveSonarrBlock::SearchSeriesHistoryError,
|
||||
ActiveSonarrBlock::SeasonDetails,
|
||||
ActiveSonarrBlock::Series,
|
||||
// ActiveSonarrBlock::SeriesDetails,
|
||||
// ActiveSonarrBlock::SeriesHistory,
|
||||
// ActiveSonarrBlock::SeriesHistorySortPrompt,
|
||||
ActiveSonarrBlock::SeriesDetails,
|
||||
ActiveSonarrBlock::SeriesHistory,
|
||||
ActiveSonarrBlock::SeriesHistorySortPrompt,
|
||||
ActiveSonarrBlock::SeriesSortPrompt,
|
||||
ActiveSonarrBlock::UpdateAllSeriesPrompt,
|
||||
// ActiveSonarrBlock::UpdateAndScanSeriesPrompt
|
||||
// ActiveSonarrBlock::SeriesHistoryDetails,
|
||||
ActiveSonarrBlock::UpdateAndScanSeriesPrompt,
|
||||
ActiveSonarrBlock::SeriesHistoryDetails
|
||||
)]
|
||||
active_sonarr_block: ActiveSonarrBlock,
|
||||
) {
|
||||
@@ -222,4 +291,42 @@ mod tests {
|
||||
active_sonarr_block
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sonarr_handler_accepts() {
|
||||
ActiveSonarrBlock::iter().for_each(|active_sonarr_block| {
|
||||
assert!(SonarrHandler::accepts(active_sonarr_block));
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_sonarr_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = SonarrHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveSonarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sonarr_handler_is_ready() {
|
||||
let mut app = App::test_default();
|
||||
app.is_loading = true;
|
||||
|
||||
let handler = SonarrHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveSonarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(handler.is_ready());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::sonarr_handlers::system::system_details_handler::SystemDetailsHandler;
|
||||
use crate::handlers::{handle_clear_errors, KeyEventHandler};
|
||||
use crate::matches_key;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
|
||||
use crate::models::Scrollable;
|
||||
|
||||
@@ -35,6 +35,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemHandler<'a, 'b
|
||||
SystemDetailsHandler::accepts(active_block) || active_block == ActiveSonarrBlock::System
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -86,15 +90,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemHandler<'a, 'b
|
||||
if self.active_sonarr_block == ActiveSonarrBlock::System {
|
||||
let key = self.key;
|
||||
match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
_ if matches_key!(refresh, key) => {
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.events.key => {
|
||||
_ if matches_key!(events, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::SystemQueuedEvents.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.logs.key => {
|
||||
_ if matches_key!(logs, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::SystemLogs.into());
|
||||
@@ -106,12 +110,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemHandler<'a, 'b
|
||||
.set_items(self.app.data.sonarr_data.logs.items.to_vec());
|
||||
self.app.data.sonarr_data.log_details.scroll_to_bottom();
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.tasks.key => {
|
||||
_ if matches_key!(tasks, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::SystemTasks.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.update.key => {
|
||||
_ if matches_key!(update, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::SystemUpdates.into());
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||
use crate::matches_key;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SYSTEM_DETAILS_BLOCKS};
|
||||
use crate::models::sonarr_models::SonarrTaskName;
|
||||
use crate::models::stateful_list::StatefulList;
|
||||
@@ -36,6 +36,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler
|
||||
SYSTEM_DETAILS_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -114,7 +118,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler
|
||||
|
||||
match self.active_sonarr_block {
|
||||
ActiveSonarrBlock::SystemLogs => match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.left.key => {
|
||||
_ if matches_key!(left, key) => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
@@ -124,7 +128,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler
|
||||
.iter()
|
||||
.for_each(|log| log.scroll_right());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.right.key => {
|
||||
_ if matches_key!(right, key) => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
@@ -178,14 +182,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler
|
||||
}
|
||||
|
||||
fn handle_char_key_event(&mut self) {
|
||||
if SYSTEM_DETAILS_BLOCKS.contains(&self.active_sonarr_block)
|
||||
&& self.key == DEFAULT_KEYBINDINGS.refresh.key
|
||||
if SYSTEM_DETAILS_BLOCKS.contains(&self.active_sonarr_block) && matches_key!(refresh, self.key)
|
||||
{
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
|
||||
if self.active_sonarr_block == ActiveSonarrBlock::SystemTaskStartConfirmPrompt
|
||||
&& self.key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
&& matches_key!(confirm, self.key)
|
||||
{
|
||||
self.app.data.sonarr_data.prompt_confirm = true;
|
||||
self.app.data.sonarr_data.prompt_confirm_action =
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
@@ -960,6 +961,22 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_system_details_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = SystemDetailsHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveSonarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_task_name() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -456,6 +456,22 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_system_handler_ignore_alt_navigation(
|
||||
#[values(true, false)] should_ignore_quit_key: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = should_ignore_quit_key;
|
||||
let handler = SystemHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveSonarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(handler.ignore_alt_navigation(), should_ignore_quit_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_handler_is_not_ready_when_loading() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -42,17 +42,17 @@ macro_rules! handle_table_events {
|
||||
fn [<handle_ $name _table_events>](&mut $self, config: $crate::handlers::table_handler::TableHandlingConfig<$row>) -> bool {
|
||||
if $self.is_ready() {
|
||||
match $self.key {
|
||||
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.up.key => $self.[<handle_ $name _table_scroll_up>](config),
|
||||
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.down.key => $self.[<handle_ $name _table_scroll_down>](config),
|
||||
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.home.key => $self.[<handle_ $name _table_home>](config),
|
||||
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.end.key => $self.[<handle_ $name _table_end>](config),
|
||||
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.left.key
|
||||
|| $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.right.key =>
|
||||
_ if $crate::matches_key!(up, $self.key, $self.ignore_alt_navigation()) => $self.[<handle_ $name _table_scroll_up>](config),
|
||||
_ if $crate::matches_key!(down, $self.key, $self.ignore_alt_navigation()) => $self.[<handle_ $name _table_scroll_down>](config),
|
||||
_ if $crate::matches_key!(home, $self.key) => $self.[<handle_ $name _table_home>](config),
|
||||
_ if $crate::matches_key!(end, $self.key) => $self.[<handle_ $name _table_end>](config),
|
||||
_ if $crate::matches_key!(left, $self.key, $self.ignore_alt_navigation())
|
||||
|| $crate::matches_key!(right, $self.key, $self.ignore_alt_navigation()) =>
|
||||
{
|
||||
$self.[<handle_ $name _table_left_right>](config)
|
||||
}
|
||||
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.submit.key => $self.[<handle_ $name _table_submit>](config),
|
||||
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.esc.key => $self.[<handle_ $name _table_esc>](config),
|
||||
_ if $crate::matches_key!(submit, $self.key) => $self.[<handle_ $name _table_submit>](config),
|
||||
_ if $crate::matches_key!(esc, $self.key) => $self.[<handle_ $name _table_esc>](config),
|
||||
_ if config.searching_block.is_some()
|
||||
&& $self.app.get_current_route() == *config.searching_block.as_ref().unwrap() =>
|
||||
{
|
||||
@@ -63,11 +63,11 @@ macro_rules! handle_table_events {
|
||||
{
|
||||
$self.[<handle_ $name _table_filter_box_input>]()
|
||||
}
|
||||
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.filter.key
|
||||
_ if $crate::matches_key!(filter, $self.key)
|
||||
&& config.filtering_block.is_some() => $self.[<handle_ $name _table_filter_key>](config),
|
||||
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.search.key
|
||||
_ if $crate::matches_key!(search, $self.key)
|
||||
&& config.searching_block.is_some() => $self.[<handle_ $name _table_search_key>](config),
|
||||
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.sort.key
|
||||
_ if $crate::matches_key!(sort, $self.key)
|
||||
&& config.sorting_block.is_some() => $self.[<handle_ $name _table_sort_key>](config),
|
||||
_ => false,
|
||||
}
|
||||
|
||||
@@ -48,6 +48,10 @@ mod tests {
|
||||
true
|
||||
}
|
||||
|
||||
fn ignore_alt_navigation(&self) -> bool {
|
||||
self.app.should_ignore_quit_key
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -1040,7 +1044,7 @@ mod tests {
|
||||
app.data.radarr_data.movies.search = Some(HorizontallyScrollableText::default());
|
||||
|
||||
TableHandlerUnit::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveRadarrBlock::SearchMovie,
|
||||
None,
|
||||
@@ -1049,7 +1053,7 @@ mod tests {
|
||||
|
||||
assert_str_eq!(
|
||||
app.data.radarr_data.movies.search.as_ref().unwrap().text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1065,7 +1069,7 @@ mod tests {
|
||||
app.data.radarr_data.movies.filter = Some(HorizontallyScrollableText::default());
|
||||
|
||||
TableHandlerUnit::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveRadarrBlock::FilterMovies,
|
||||
None,
|
||||
@@ -1074,7 +1078,7 @@ mod tests {
|
||||
|
||||
assert_str_eq!(
|
||||
app.data.radarr_data.movies.filter.as_ref().unwrap().text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+63
-10
@@ -1,10 +1,4 @@
|
||||
use anyhow::Result;
|
||||
use std::panic::PanicHookInfo;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::{io, panic, process};
|
||||
|
||||
use clap::{crate_authors, crate_description, crate_name, crate_version, CommandFactory, Parser};
|
||||
use clap_complete::generate;
|
||||
use crossterm::execute;
|
||||
@@ -16,6 +10,11 @@ use network::NetworkTrait;
|
||||
use ratatui::backend::CrosstermBackend;
|
||||
use ratatui::Terminal;
|
||||
use reqwest::Client;
|
||||
use std::panic::PanicHookInfo;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::{io, panic, process};
|
||||
use tokio::select;
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
@@ -24,12 +23,14 @@ use utils::{
|
||||
build_network_client, load_config, start_cli_no_spinner, start_cli_with_spinner, tail_logs,
|
||||
};
|
||||
|
||||
use crate::app::App;
|
||||
use crate::app::{log_and_print_error, App};
|
||||
use crate::cli::Command;
|
||||
use crate::event::input_event::{Events, InputEvent};
|
||||
use crate::event::Key;
|
||||
use crate::network::{Network, NetworkEvent};
|
||||
use crate::ui::ui;
|
||||
use crate::ui::theme::{Theme, ThemeDefinitionsWrapper};
|
||||
use crate::ui::{ui, THEME};
|
||||
use crate::utils::load_theme_config;
|
||||
|
||||
mod app;
|
||||
mod cli;
|
||||
@@ -74,6 +75,22 @@ struct Cli {
|
||||
help = "The Managarr configuration file to use"
|
||||
)]
|
||||
config_file: Option<PathBuf>,
|
||||
#[arg(
|
||||
long,
|
||||
global = true,
|
||||
value_parser,
|
||||
env = "MANAGARR_THEMES_FILE",
|
||||
help = "The Managarr themes file to use"
|
||||
)]
|
||||
themes_file: Option<PathBuf>,
|
||||
#[arg(
|
||||
long,
|
||||
global = true,
|
||||
value_parser,
|
||||
env = "MANAGARR_THEME",
|
||||
help = "The name of the Managarr theme to use"
|
||||
)]
|
||||
theme: Option<String>,
|
||||
#[arg(
|
||||
long,
|
||||
global = true,
|
||||
@@ -98,10 +115,12 @@ async fn main() -> Result<()> {
|
||||
} else {
|
||||
confy::load("managarr", "config")?
|
||||
};
|
||||
let theme_name = config.theme.clone();
|
||||
let spinner_disabled = args.disable_spinner;
|
||||
debug!("Managarr loaded using config: {config:?}");
|
||||
config.validate();
|
||||
config.post_process_initialization();
|
||||
|
||||
let reqwest_client = build_network_client(&config);
|
||||
let (sync_network_tx, sync_network_rx) = mpsc::channel(500);
|
||||
let cancellation_token = CancellationToken::new();
|
||||
@@ -140,7 +159,12 @@ async fn main() -> Result<()> {
|
||||
std::thread::spawn(move || {
|
||||
start_networking(sync_network_rx, &app_nw, cancellation_token, reqwest_client)
|
||||
});
|
||||
start_ui(&app).await?;
|
||||
start_ui(
|
||||
&app,
|
||||
&args.themes_file,
|
||||
args.theme.unwrap_or(theme_name.unwrap_or_default()),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +198,36 @@ async fn start_networking(
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_ui(app: &Arc<Mutex<App<'_>>>) -> Result<()> {
|
||||
async fn start_ui(
|
||||
app: &Arc<Mutex<App<'_>>>,
|
||||
themes_file_arg: &Option<PathBuf>,
|
||||
theme_name: String,
|
||||
) -> Result<()> {
|
||||
let theme_definitions_wrapper = if let Some(ref theme_file) = themes_file_arg {
|
||||
load_theme_config(theme_file.to_str().expect("Invalid theme file specified"))?
|
||||
} else {
|
||||
confy::load("managarr", "themes").unwrap_or_else(|_| ThemeDefinitionsWrapper::default())
|
||||
};
|
||||
let theme = if !theme_name.is_empty() {
|
||||
let theme_definition = theme_definitions_wrapper
|
||||
.theme_definitions
|
||||
.iter()
|
||||
.find(|t| t.name == theme_name);
|
||||
|
||||
if theme_definition.is_none() {
|
||||
log_and_print_error(format!("The specified theme was not found: {theme_name}"));
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
theme_definition.unwrap().theme
|
||||
} else {
|
||||
debug!("No theme specified, using default theme");
|
||||
Theme::default()
|
||||
};
|
||||
debug!("Managarr loaded using theme: {theme:?}");
|
||||
theme.validate();
|
||||
THEME.set(theme);
|
||||
|
||||
let mut stdout = io::stdout();
|
||||
enable_raw_mode()?;
|
||||
|
||||
|
||||
+15
-4
@@ -42,10 +42,6 @@ pub enum Serdeable {
|
||||
Sonarr(SonarrSerdeable),
|
||||
}
|
||||
|
||||
pub trait EnumDisplayStyle<'a> {
|
||||
fn to_display_str(self) -> &'a str;
|
||||
}
|
||||
|
||||
pub trait Scrollable {
|
||||
fn scroll_down(&mut self);
|
||||
fn scroll_up(&mut self);
|
||||
@@ -445,6 +441,21 @@ pub fn strip_non_search_characters(input: &str) -> String {
|
||||
#[macro_export]
|
||||
macro_rules! serde_enum_from {
|
||||
($enum_name:ident { $($variant:ident($ty:ty),)* }) => {
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug)]
|
||||
#[serde(untagged)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum $enum_name {
|
||||
$(
|
||||
$variant($ty),
|
||||
)*
|
||||
}
|
||||
|
||||
impl From<()> for $enum_name {
|
||||
fn from(_: ()) -> Self {
|
||||
$enum_name::Value(serde_json::json!({}))
|
||||
}
|
||||
}
|
||||
|
||||
$(
|
||||
impl From<$ty> for $enum_name {
|
||||
fn from(value: $ty) -> Self {
|
||||
|
||||
+26
-88
@@ -1,19 +1,19 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use crate::{models::HorizontallyScrollableText, serde_enum_from};
|
||||
use chrono::{DateTime, Utc};
|
||||
use clap::ValueEnum;
|
||||
use derivative::Derivative;
|
||||
use enum_display_style_derive::EnumDisplayStyle;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Number, Value};
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
use crate::{models::HorizontallyScrollableText, serde_enum_from};
|
||||
use serde_json::{Number, Value};
|
||||
use strum_macros::{Display, EnumIter};
|
||||
|
||||
use super::servarr_models::{
|
||||
DiskSpace, HostConfig, Indexer, Language, LogResponse, QualityProfile, QualityWrapper,
|
||||
QueueEvent, RootFolder, SecurityConfig, Tag, Update,
|
||||
};
|
||||
use super::{EnumDisplayStyle, Serdeable};
|
||||
use super::Serdeable;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "radarr_models_tests.rs"]
|
||||
@@ -258,69 +258,44 @@ pub struct MediaInfo {
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, EnumIter, ValueEnum,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
EnumIter,
|
||||
ValueEnum,
|
||||
Display,
|
||||
EnumDisplayStyle,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[strum(serialize_all = "camelCase")]
|
||||
pub enum MinimumAvailability {
|
||||
#[default]
|
||||
Announced,
|
||||
#[display_style(name = "In Cinemas")]
|
||||
InCinemas,
|
||||
Released,
|
||||
#[display_style(name = "TBA")]
|
||||
Tba,
|
||||
}
|
||||
|
||||
impl Display for MinimumAvailability {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let minimum_availability = match self {
|
||||
MinimumAvailability::Tba => "tba",
|
||||
MinimumAvailability::Announced => "announced",
|
||||
MinimumAvailability::InCinemas => "inCinemas",
|
||||
MinimumAvailability::Released => "released",
|
||||
};
|
||||
write!(f, "{minimum_availability}")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EnumDisplayStyle<'a> for MinimumAvailability {
|
||||
fn to_display_str(self) -> &'a str {
|
||||
match self {
|
||||
MinimumAvailability::Tba => "TBA",
|
||||
MinimumAvailability::Announced => "Announced",
|
||||
MinimumAvailability::InCinemas => "In Cinemas",
|
||||
MinimumAvailability::Released => "Released",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, EnumIter, ValueEnum)]
|
||||
#[derive(
|
||||
Default, PartialEq, Eq, Clone, Copy, Debug, EnumIter, ValueEnum, Display, EnumDisplayStyle,
|
||||
)]
|
||||
#[strum(serialize_all = "camelCase")]
|
||||
pub enum MovieMonitor {
|
||||
#[default]
|
||||
#[display_style(name = "Movie only")]
|
||||
MovieOnly,
|
||||
#[display_style(name = "Movie and Collection")]
|
||||
MovieAndCollection,
|
||||
None,
|
||||
}
|
||||
|
||||
impl Display for MovieMonitor {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let monitor = match self {
|
||||
MovieMonitor::MovieOnly => "movieOnly",
|
||||
MovieMonitor::MovieAndCollection => "movieAndCollection",
|
||||
MovieMonitor::None => "none",
|
||||
};
|
||||
write!(f, "{monitor}")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EnumDisplayStyle<'a> for MovieMonitor {
|
||||
fn to_display_str(self) -> &'a str {
|
||||
match self {
|
||||
MovieMonitor::MovieOnly => "Movie only",
|
||||
MovieMonitor::MovieAndCollection => "Movie and Collection",
|
||||
MovieMonitor::None => "None",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Movie {
|
||||
@@ -475,49 +450,12 @@ impl Display for RadarrTaskName {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(untagged)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum RadarrSerdeable {
|
||||
Value(Value),
|
||||
Tag(Tag),
|
||||
BlocklistResponse(BlocklistResponse),
|
||||
Collections(Vec<Collection>),
|
||||
Credits(Vec<Credit>),
|
||||
DiskSpaces(Vec<DiskSpace>),
|
||||
DownloadsResponse(DownloadsResponse),
|
||||
HostConfig(HostConfig),
|
||||
Indexers(Vec<Indexer>),
|
||||
IndexerSettings(IndexerSettings),
|
||||
LogResponse(LogResponse),
|
||||
Movie(Movie),
|
||||
MovieHistoryItems(Vec<MovieHistoryItem>),
|
||||
Movies(Vec<Movie>),
|
||||
QualityProfiles(Vec<QualityProfile>),
|
||||
QueueEvents(Vec<QueueEvent>),
|
||||
Releases(Vec<RadarrRelease>),
|
||||
RootFolders(Vec<RootFolder>),
|
||||
SecurityConfig(SecurityConfig),
|
||||
SystemStatus(SystemStatus),
|
||||
Tags(Vec<Tag>),
|
||||
Tasks(Vec<RadarrTask>),
|
||||
Updates(Vec<Update>),
|
||||
AddMovieSearchResults(Vec<AddMovieSearchResult>),
|
||||
IndexerTestResults(Vec<IndexerTestResult>),
|
||||
}
|
||||
|
||||
impl From<RadarrSerdeable> for Serdeable {
|
||||
fn from(value: RadarrSerdeable) -> Serdeable {
|
||||
Serdeable::Radarr(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<()> for RadarrSerdeable {
|
||||
fn from(_: ()) -> Self {
|
||||
RadarrSerdeable::Value(json!({}))
|
||||
}
|
||||
}
|
||||
|
||||
serde_enum_from!(
|
||||
RadarrSerdeable {
|
||||
Value(Value),
|
||||
|
||||
@@ -11,7 +11,7 @@ mod tests {
|
||||
RadarrSerdeable, RadarrTask, RadarrTaskName, SystemStatus, Tag, Update,
|
||||
},
|
||||
servarr_models::{HostConfig, Log, LogResponse, QueueEvent, RootFolder, SecurityConfig},
|
||||
EnumDisplayStyle, Serdeable,
|
||||
Serdeable,
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
||||
+83
-205
@@ -1,13 +1,14 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use crate::serde_enum_from;
|
||||
use chrono::{DateTime, Utc};
|
||||
use clap::ValueEnum;
|
||||
use derivative::Derivative;
|
||||
use enum_display_style_derive::EnumDisplayStyle;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Number, Value};
|
||||
use serde_json::{Number, Value};
|
||||
use strum::EnumIter;
|
||||
|
||||
use crate::serde_enum_from;
|
||||
use strum_macros::Display;
|
||||
|
||||
use super::{
|
||||
radarr_models::IndexerTestResult,
|
||||
@@ -15,7 +16,7 @@ use super::{
|
||||
DiskSpace, HostConfig, Indexer, Language, LogResponse, QualityProfile, QualityWrapper,
|
||||
QueueEvent, RootFolder, SecurityConfig, Tag, Update,
|
||||
},
|
||||
EnumDisplayStyle, HorizontallyScrollableText, Serdeable,
|
||||
HorizontallyScrollableText, Serdeable,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -112,8 +113,7 @@ pub struct DownloadRecord {
|
||||
pub status: DownloadStatus,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub id: i64,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub episode_id: i64,
|
||||
pub episode_id: Option<Number>,
|
||||
#[serde(deserialize_with = "super::from_f64")]
|
||||
pub size: f64,
|
||||
#[serde(deserialize_with = "super::from_f64")]
|
||||
@@ -126,8 +126,21 @@ pub struct DownloadRecord {
|
||||
|
||||
impl Eq for DownloadRecord {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, EnumIter)]
|
||||
#[derive(
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
EnumIter,
|
||||
Display,
|
||||
EnumDisplayStyle,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[strum(serialize_all = "camelCase")]
|
||||
pub enum DownloadStatus {
|
||||
#[default]
|
||||
Unknown,
|
||||
@@ -138,45 +151,11 @@ pub enum DownloadStatus {
|
||||
Failed,
|
||||
Warning,
|
||||
Delay,
|
||||
#[display_style(name = "Download Client Unavailable")]
|
||||
DownloadClientUnavailable,
|
||||
Fallback,
|
||||
}
|
||||
|
||||
impl Display for DownloadStatus {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let download_status = match self {
|
||||
DownloadStatus::Unknown => "unknown",
|
||||
DownloadStatus::Queued => "queued",
|
||||
DownloadStatus::Paused => "paused",
|
||||
DownloadStatus::Downloading => "downloading",
|
||||
DownloadStatus::Completed => "completed",
|
||||
DownloadStatus::Failed => "failed",
|
||||
DownloadStatus::Warning => "warning",
|
||||
DownloadStatus::Delay => "delay",
|
||||
DownloadStatus::DownloadClientUnavailable => "downloadClientUnavailable",
|
||||
DownloadStatus::Fallback => "fallback",
|
||||
};
|
||||
write!(f, "{download_status}")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EnumDisplayStyle<'a> for DownloadStatus {
|
||||
fn to_display_str(self) -> &'a str {
|
||||
match self {
|
||||
DownloadStatus::Unknown => "Unknown",
|
||||
DownloadStatus::Queued => "Queued",
|
||||
DownloadStatus::Paused => "Paused",
|
||||
DownloadStatus::Downloading => "Downloading",
|
||||
DownloadStatus::Completed => "Completed",
|
||||
DownloadStatus::Failed => "Failed",
|
||||
DownloadStatus::Warning => "Warning",
|
||||
DownloadStatus::Delay => "Delay",
|
||||
DownloadStatus::DownloadClientUnavailable => "Download Client Unavailable",
|
||||
DownloadStatus::Fallback => "Fallback",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DownloadsResponse {
|
||||
@@ -309,7 +288,7 @@ pub struct Season {
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub season_number: i64,
|
||||
pub monitored: bool,
|
||||
pub statistics: SeasonStatistics,
|
||||
pub statistics: Option<SeasonStatistics>,
|
||||
}
|
||||
|
||||
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
|
||||
@@ -364,74 +343,66 @@ pub struct Series {
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, EnumIter, ValueEnum,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
EnumIter,
|
||||
ValueEnum,
|
||||
Display,
|
||||
EnumDisplayStyle,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[strum(serialize_all = "camelCase")]
|
||||
pub enum SeriesMonitor {
|
||||
#[default]
|
||||
#[display_style(name = "All Episodes")]
|
||||
All,
|
||||
Unknown,
|
||||
#[display_style(name = "Future Episodes")]
|
||||
Future,
|
||||
#[display_style(name = "Missing Episodes")]
|
||||
Missing,
|
||||
#[display_style(name = "Existing Episodes")]
|
||||
Existing,
|
||||
#[display_style(name = "Only First Season")]
|
||||
FirstSeason,
|
||||
#[display_style(name = "Only Last Season")]
|
||||
LastSeason,
|
||||
#[display_style(name = "Only Latest Season")]
|
||||
LatestSeason,
|
||||
#[display_style(name = "Pilot Episode")]
|
||||
Pilot,
|
||||
#[display_style(name = "Recent Episodes")]
|
||||
Recent,
|
||||
#[display_style(name = "Only Specials")]
|
||||
MonitorSpecials,
|
||||
#[display_style(name = "Not Specials")]
|
||||
UnmonitorSpecials,
|
||||
None,
|
||||
Skip,
|
||||
}
|
||||
|
||||
impl Display for SeriesMonitor {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let series_monitor = match self {
|
||||
SeriesMonitor::Unknown => "unknown",
|
||||
SeriesMonitor::All => "all",
|
||||
SeriesMonitor::Future => "future",
|
||||
SeriesMonitor::Missing => "missing",
|
||||
SeriesMonitor::Existing => "existing",
|
||||
SeriesMonitor::FirstSeason => "firstSeason",
|
||||
SeriesMonitor::LastSeason => "lastSeason",
|
||||
SeriesMonitor::LatestSeason => "latestSeason",
|
||||
SeriesMonitor::Pilot => "pilot",
|
||||
SeriesMonitor::Recent => "recent",
|
||||
SeriesMonitor::MonitorSpecials => "monitorSpecials",
|
||||
SeriesMonitor::UnmonitorSpecials => "unmonitorSpecials",
|
||||
SeriesMonitor::None => "none",
|
||||
SeriesMonitor::Skip => "skip",
|
||||
};
|
||||
write!(f, "{series_monitor}")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EnumDisplayStyle<'a> for SeriesMonitor {
|
||||
fn to_display_str(self) -> &'a str {
|
||||
match self {
|
||||
SeriesMonitor::Unknown => "Unknown",
|
||||
SeriesMonitor::All => "All Episodes",
|
||||
SeriesMonitor::Future => "Future Episodes",
|
||||
SeriesMonitor::Missing => "Missing Episodes",
|
||||
SeriesMonitor::Existing => "Existing Episodes",
|
||||
SeriesMonitor::FirstSeason => "Only First Season",
|
||||
SeriesMonitor::LastSeason => "Only Last Season",
|
||||
SeriesMonitor::LatestSeason => "Only Latest Season",
|
||||
SeriesMonitor::Pilot => "Pilot Episode",
|
||||
SeriesMonitor::Recent => "Recent Episodes",
|
||||
SeriesMonitor::MonitorSpecials => "Only Specials",
|
||||
SeriesMonitor::UnmonitorSpecials => "Not Specials",
|
||||
SeriesMonitor::None => "None",
|
||||
SeriesMonitor::Skip => "Skip",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, EnumIter, ValueEnum,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
EnumIter,
|
||||
ValueEnum,
|
||||
Display,
|
||||
EnumDisplayStyle,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[strum(serialize_all = "camelCase")]
|
||||
pub enum SeriesType {
|
||||
#[default]
|
||||
Standard,
|
||||
@@ -439,27 +410,6 @@ pub enum SeriesType {
|
||||
Anime,
|
||||
}
|
||||
|
||||
impl Display for SeriesType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let series_type = match self {
|
||||
SeriesType::Standard => "standard",
|
||||
SeriesType::Daily => "daily",
|
||||
SeriesType::Anime => "anime",
|
||||
};
|
||||
write!(f, "{series_type}")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EnumDisplayStyle<'a> for SeriesType {
|
||||
fn to_display_str(self) -> &'a str {
|
||||
match self {
|
||||
SeriesType::Standard => "Standard",
|
||||
SeriesType::Daily => "Daily",
|
||||
SeriesType::Anime => "Anime",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SeriesStatistics {
|
||||
@@ -479,8 +429,21 @@ pub struct SeriesStatistics {
|
||||
|
||||
impl Eq for SeriesStatistics {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, EnumIter)]
|
||||
#[derive(
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
EnumIter,
|
||||
Display,
|
||||
EnumDisplayStyle,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[strum(serialize_all = "camelCase")]
|
||||
pub enum SeriesStatus {
|
||||
#[default]
|
||||
Continuing,
|
||||
@@ -489,29 +452,6 @@ pub enum SeriesStatus {
|
||||
Deleted,
|
||||
}
|
||||
|
||||
impl Display for SeriesStatus {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let series_status = match self {
|
||||
SeriesStatus::Continuing => "continuing",
|
||||
SeriesStatus::Ended => "ended",
|
||||
SeriesStatus::Upcoming => "upcoming",
|
||||
SeriesStatus::Deleted => "deleted",
|
||||
};
|
||||
write!(f, "{series_status}")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EnumDisplayStyle<'a> for SeriesStatus {
|
||||
fn to_display_str(self) -> &'a str {
|
||||
match self {
|
||||
SeriesStatus::Continuing => "Continuing",
|
||||
SeriesStatus::Ended => "Ended",
|
||||
SeriesStatus::Upcoming => "Upcoming",
|
||||
SeriesStatus::Deleted => "Deleted",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SonarrHistoryWrapper {
|
||||
@@ -538,51 +478,29 @@ pub struct SonarrHistoryData {
|
||||
pub relative_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq, Display, EnumDisplayStyle,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[strum(serialize_all = "camelCase")]
|
||||
pub enum SonarrHistoryEventType {
|
||||
#[default]
|
||||
Unknown,
|
||||
Grabbed,
|
||||
#[display_style(name = "Series Folder Imported")]
|
||||
SeriesFolderImported,
|
||||
#[display_style(name = "Download Folder Imported")]
|
||||
DownloadFolderImported,
|
||||
#[display_style(name = "Download Failed")]
|
||||
DownloadFailed,
|
||||
#[display_style(name = "Episode File Deleted")]
|
||||
EpisodeFileDeleted,
|
||||
#[display_style(name = "Episode File Renamed")]
|
||||
EpisodeFileRenamed,
|
||||
#[display_style(name = "Download Ignored")]
|
||||
DownloadIgnored,
|
||||
}
|
||||
|
||||
impl Display for SonarrHistoryEventType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let event_type = match self {
|
||||
SonarrHistoryEventType::Unknown => "unknown",
|
||||
SonarrHistoryEventType::Grabbed => "grabbed",
|
||||
SonarrHistoryEventType::SeriesFolderImported => "seriesFolderImported",
|
||||
SonarrHistoryEventType::DownloadFolderImported => "downloadFolderImported",
|
||||
SonarrHistoryEventType::DownloadFailed => "downloadFailed",
|
||||
SonarrHistoryEventType::EpisodeFileDeleted => "episodeFileDeleted",
|
||||
SonarrHistoryEventType::EpisodeFileRenamed => "episodeFileRenamed",
|
||||
SonarrHistoryEventType::DownloadIgnored => "downloadIgnored",
|
||||
};
|
||||
write!(f, "{event_type}")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EnumDisplayStyle<'a> for SonarrHistoryEventType {
|
||||
fn to_display_str(self) -> &'a str {
|
||||
match self {
|
||||
SonarrHistoryEventType::Unknown => "Unknown",
|
||||
SonarrHistoryEventType::Grabbed => "Grabbed",
|
||||
SonarrHistoryEventType::SeriesFolderImported => "Series Folder Imported",
|
||||
SonarrHistoryEventType::DownloadFolderImported => "Download Folder Imported",
|
||||
SonarrHistoryEventType::DownloadFailed => "Download Failed",
|
||||
SonarrHistoryEventType::EpisodeFileDeleted => "Episode File Deleted",
|
||||
SonarrHistoryEventType::EpisodeFileRenamed => "Episode File Renamed",
|
||||
SonarrHistoryEventType::DownloadIgnored => "Download Ignored",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SonarrHistoryItem {
|
||||
@@ -682,52 +600,12 @@ impl Display for SonarrTaskName {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(untagged)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum SonarrSerdeable {
|
||||
AddSeriesSearchResults(Vec<AddSeriesSearchResult>),
|
||||
BlocklistResponse(BlocklistResponse),
|
||||
DownloadsResponse(DownloadsResponse),
|
||||
DiskSpaces(Vec<DiskSpace>),
|
||||
Episode(Episode),
|
||||
Episodes(Vec<Episode>),
|
||||
EpisodeFiles(Vec<EpisodeFile>),
|
||||
HostConfig(HostConfig),
|
||||
IndexerSettings(IndexerSettings),
|
||||
Indexers(Vec<Indexer>),
|
||||
IndexerTestResults(Vec<IndexerTestResult>),
|
||||
LanguageProfiles(Vec<Language>),
|
||||
LogResponse(LogResponse),
|
||||
QualityProfiles(Vec<QualityProfile>),
|
||||
QueueEvents(Vec<QueueEvent>),
|
||||
Releases(Vec<SonarrRelease>),
|
||||
RootFolders(Vec<RootFolder>),
|
||||
SecurityConfig(SecurityConfig),
|
||||
SeriesVec(Vec<Series>),
|
||||
Series(Series),
|
||||
SonarrHistoryItems(Vec<SonarrHistoryItem>),
|
||||
SonarrHistoryWrapper(SonarrHistoryWrapper),
|
||||
SystemStatus(SystemStatus),
|
||||
Tag(Tag),
|
||||
Tags(Vec<Tag>),
|
||||
Tasks(Vec<SonarrTask>),
|
||||
Updates(Vec<Update>),
|
||||
Value(Value),
|
||||
}
|
||||
|
||||
impl From<SonarrSerdeable> for Serdeable {
|
||||
fn from(value: SonarrSerdeable) -> Serdeable {
|
||||
Serdeable::Sonarr(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<()> for SonarrSerdeable {
|
||||
fn from(_: ()) -> Self {
|
||||
SonarrSerdeable::Value(json!({}))
|
||||
}
|
||||
}
|
||||
|
||||
serde_enum_from!(
|
||||
SonarrSerdeable {
|
||||
AddSeriesSearchResults(Vec<AddSeriesSearchResult>),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user