Compare commits

...

35 Commits

Author SHA1 Message Date
Dark-Alex-17 0048d71b74 feat: Support alternative keymappings for all keys, featuring hjkl movements 2025-03-17 22:02:15 -06:00
Dark-Alex-17 c633347ecc Merge remote-tracking branch 'refs/remotes/origin/develop' 2025-03-17 20:49:52 -06:00
Alex Clarke ecd6a0ec32 Merge pull request #37 from Dark-Alex-17/custom-themes
Support Themes
2025-03-17 14:23:18 -06:00
Dark-Alex-17 30507d9d01 docs: Updated the README to include the new flags 2025-03-17 13:26:46 -06:00
Dark-Alex-17 6245a794d5 docs: Update all screenshots to not have any auto-generated usernames in the tags columns 2025-03-10 16:22:24 -06:00
Dark-Alex-17 5c822e4890 Merge branch 'develop' 2025-03-10 16:13:48 -06:00
Dark-Alex-17 cab06fe43f fix: Marked the Season.statistics field as Option so that a panic does not happen for outdated Sonarr data. This resolves #35 2025-03-10 16:13:04 -06:00
Dark-Alex-17 b4ff5f3351 feat: Added the Eldritch theme and updated documentation 2025-03-10 15:49:40 -06:00
Dark-Alex-17 0834802481 fix: When adding a film from the Collection Details modal, the render order was wrong: Radarr Library -> Collection Table -> Add Movie Prompt (missing the Collection details prompt too). Correct order is: Collection Table -> Collection Details Modal -> Add Movie Modal 2025-03-10 15:08:02 -06:00
Dark-Alex-17 3afd74dcbf fix: Fixed a bug that was rendering encompassing blocks after other widgets were rendered, thus overwriting the custom styles on each previously rendered widget 2025-03-10 15:01:58 -06:00
Dark-Alex-17 b1a0bdfbb6 Merge branch 'develop' 2025-03-07 12:02:47 -07:00
Dark-Alex-17 6d38bc5e1d Merge branch 'main' 2025-03-07 12:02:19 -07:00
Dark-Alex-17 5ba1ba15c9 ci: Update to the most recent Rust version 2025-03-07 11:55:32 -07:00
Dark-Alex-17 db05d2abfb Merge branch 'develop' into custom-themes 2025-03-07 10:37:48 -07:00
Dark-Alex-17 1840c4e39a Merge branch 'main' into develop
# Conflicts:
#	proc_macros/enum_display_style_derive/src/lib.rs
2025-03-07 10:37:23 -07:00
Dark-Alex-17 c5a3f424d6 refactor: Reformatted code to make the format checks pass 2025-03-07 10:36:40 -07:00
Dark-Alex-17 04aa6b81b5 Merge branch 'develop' into custom-themes 2025-03-07 10:35:07 -07:00
Dark-Alex-17 5ff3b9b996 Merge branch 'main' into develop 2025-03-07 10:34:16 -07:00
Dark-Alex-17 228e4a61a4 fix: Updated ring dependency to mitigate CWE-770 2025-03-07 10:33:57 -07:00
Dark-Alex-17 df38ea5413 feat: Write built in themes to the themes file on first run so users can define custom themes 2025-03-06 17:44:52 -07:00
Dark-Alex-17 709f6ca6ca test: Added integration tests for the ValidateTheme macro 2025-03-06 16:00:50 -07:00
Dark-Alex-17 b012fc29e4 Merge branch 'develop' into custom-themes
# Conflicts:
#	Cargo.toml
2025-03-06 15:35:05 -07:00
Dark-Alex-17 bdad723aef refactor: Formatted files using rustfmt 2025-03-06 15:32:59 -07:00
Dark-Alex-17 f97d46cec3 refactor: Created a derive macro for defining the display style of Enum models and removed the use of the EnumDisplayStyle trait 2025-03-06 15:29:30 -07:00
Dark-Alex-17 7381eaef57 refactor: Expanded the serde_enum_from macro to further reduce code duplication 2025-03-05 15:09:51 -07:00
Dark-Alex-17 72c922b311 feat: Created a theme validation macro to verify theme configurations before allowing the TUI to start 2025-03-05 14:37:34 -07:00
Alex Clarke fd14a8152c fix: change the name of the theme configuration file to 'themes' 2025-03-04 18:29:21 -07:00
Dark-Alex-17 5cb60c317d feat: Initial support for custom user-defined themes 2025-03-04 18:09:09 -07:00
Dark-Alex-17 847de75713 fix: Modified the Sonarr DownloadRecord so that the episode_id is optional to prevent crashes for weird downloads 2025-03-01 14:50:20 -07:00
Dark-Alex-17 58723cf3e8 ci: Ensure the docker release is fully up-to-date 2025-02-28 21:45:05 -07:00
Dark-Alex-17 c613168bfb docs: Updated the CHANGELOG accordingly 2025-02-28 21:26:13 -07:00
github-actions[bot] 6f83de77f2 chore: Bump the version in Cargo.lock 2025-03-01 03:30:05 +00:00
github-actions[bot] 3f6ef3beb4 bump: version 0.5.0 → 0.5.1 [skip ci] 2025-03-01 03:30:04 +00:00
Dark-Alex-17 14e50c1465 Merge remote-tracking branch 'refs/remotes/origin/main' 2025-02-28 20:29:29 -07:00
Dark-Alex-17 0aa9fdca14 ci: Overwrite previous artifact uploads for proper releases 2025-02-28 20:29:15 -07:00
161 changed files with 3454 additions and 892 deletions
+4 -4
View File
@@ -76,15 +76,15 @@ jobs:
RUSTDOCFLAGS: --cfg docsrs RUSTDOCFLAGS: --cfg docsrs
msrv: msrv:
# check that we can build using the minimal rust version that is specified by this crate # 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 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install 1.82.0 - name: Install 1.85.0
uses: dtolnay/rust-toolchain@master uses: dtolnay/rust-toolchain@master
with: with:
toolchain: 1.82.0 toolchain: 1.85.0
- name: cargo +1.82.0 check - name: cargo +1.85.0 check
run: cargo check run: cargo check
+6
View File
@@ -309,6 +309,7 @@ jobs:
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
path: artifacts path: artifacts
overwrite: true
publish-chocolatey-package: publish-chocolatey-package:
needs: [publish-github-release] needs: [publish-github-release]
@@ -429,6 +430,11 @@ jobs:
path: artifacts path: artifacts
merge-multiple: true merge-multiple: true
- name: Ensure repository is up-to-date
run: |
git fetch --all
git pull
- name: Set version variable - name: Set version variable
run: | run: |
version="$(cat artifacts/release-version)" version="$(cat artifacts/release-version)"
+1 -1
View File
@@ -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/), 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). 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 ### Feat
Generated
+198 -107
View File
@@ -99,9 +99,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.96" version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
[[package]] [[package]]
name = "arc-swap" name = "arc-swap"
@@ -137,13 +137,13 @@ dependencies = [
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.86" version = "0.1.87"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.98", "syn 2.0.99",
] ]
[[package]] [[package]]
@@ -190,9 +190,9 @@ dependencies = [
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.8.0" version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]] [[package]]
name = "bstr" name = "bstr"
@@ -219,9 +219,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.10.0" version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]] [[package]]
name = "cargo-husky" name = "cargo-husky"
@@ -321,7 +321,7 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.98", "syn 2.0.99",
] ]
[[package]] [[package]]
@@ -338,11 +338,10 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]] [[package]]
name = "colored" name = "colored"
version = "2.2.0" version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
dependencies = [ dependencies = [
"lazy_static",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@@ -374,9 +373,9 @@ dependencies = [
[[package]] [[package]]
name = "console" name = "console"
version = "0.15.10" version = "0.15.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
dependencies = [ dependencies = [
"encode_unicode", "encode_unicode",
"libc", "libc",
@@ -411,7 +410,7 @@ dependencies = [
"crossterm_winapi", "crossterm_winapi",
"mio", "mio",
"parking_lot", "parking_lot",
"rustix", "rustix 0.38.44",
"signal-hook", "signal-hook",
"signal-hook-mio", "signal-hook-mio",
"winapi", "winapi",
@@ -457,7 +456,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 2.0.98", "syn 2.0.99",
] ]
[[package]] [[package]]
@@ -468,7 +467,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 2.0.98", "syn 2.0.99",
] ]
[[package]] [[package]]
@@ -500,7 +499,7 @@ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.98", "syn 2.0.99",
] ]
[[package]] [[package]]
@@ -577,7 +576,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.98", "syn 2.0.99",
] ]
[[package]] [[package]]
@@ -594,9 +593,9 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1"
[[package]] [[package]]
name = "either" name = "either"
version = "1.14.0" version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]] [[package]]
name = "encode_unicode" name = "encode_unicode"
@@ -613,6 +612,15 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "enum_display_style_derive"
version = "0.1.0"
dependencies = [
"darling",
"quote",
"syn 2.0.99",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@@ -733,7 +741,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.98", "syn 2.0.99",
] ]
[[package]] [[package]]
@@ -879,9 +887,9 @@ dependencies = [
[[package]] [[package]]
name = "httparse" name = "httparse"
version = "1.10.0" version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]] [[package]]
name = "httpdate" name = "httpdate"
@@ -1122,7 +1130,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.98", "syn 2.0.99",
] ]
[[package]] [[package]]
@@ -1177,9 +1185,9 @@ dependencies = [
[[package]] [[package]]
name = "indoc" name = "indoc"
version = "2.0.5" version = "2.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
[[package]] [[package]]
name = "instability" name = "instability"
@@ -1191,7 +1199,7 @@ dependencies = [
"indoc", "indoc",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.98", "syn 2.0.99",
] ]
[[package]] [[package]]
@@ -1216,10 +1224,19 @@ dependencies = [
] ]
[[package]] [[package]]
name = "itoa" name = "itertools"
version = "1.0.14" version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "js-sys" name = "js-sys"
@@ -1231,12 +1248,6 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.170" version = "0.2.170"
@@ -1259,6 +1270,12 @@ version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "linux-raw-sys"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9"
[[package]] [[package]]
name = "litemap" name = "litemap"
version = "0.7.5" version = "0.7.5"
@@ -1307,7 +1324,7 @@ dependencies = [
"log-mdc", "log-mdc",
"once_cell", "once_cell",
"parking_lot", "parking_lot",
"rand", "rand 0.8.5",
"serde", "serde",
"serde-value", "serde-value",
"serde_json", "serde_json",
@@ -1329,7 +1346,7 @@ dependencies = [
[[package]] [[package]]
name = "managarr" name = "managarr"
version = "0.5.0" version = "0.5.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"assert_cmd", "assert_cmd",
@@ -1348,10 +1365,11 @@ dependencies = [
"derive_setters", "derive_setters",
"deunicode", "deunicode",
"dirs-next", "dirs-next",
"enum_display_style_derive",
"human-panic", "human-panic",
"indicatif", "indicatif",
"indoc", "indoc",
"itertools", "itertools 0.14.0",
"log", "log",
"log4rs", "log4rs",
"managarr-tree-widget", "managarr-tree-widget",
@@ -1373,6 +1391,7 @@ dependencies = [
"tokio", "tokio",
"tokio-util", "tokio-util",
"urlencoding", "urlencoding",
"validate_theme_derive",
"veil", "veil",
] ]
@@ -1442,14 +1461,14 @@ dependencies = [
"cfg-if", "cfg-if",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.98", "syn 2.0.99",
] ]
[[package]] [[package]]
name = "mockito" name = "mockito"
version = "1.6.1" version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "652cd6d169a36eaf9d1e6bce1a221130439a966d7f27858af66a33a66e9c4ee2" checksum = "7760e0e418d9b7e5777c0374009ca4c93861b9066f18cb334a20ce50ab63aa48"
dependencies = [ dependencies = [
"assert-json-diff", "assert-json-diff",
"bytes", "bytes",
@@ -1461,7 +1480,7 @@ dependencies = [
"hyper", "hyper",
"hyper-util", "hyper-util",
"log", "log",
"rand", "rand 0.9.0",
"regex", "regex",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
@@ -1566,7 +1585,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.98", "syn 2.0.99",
] ]
[[package]] [[package]]
@@ -1672,9 +1691,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]] [[package]]
name = "pkg-config" name = "pkg-config"
version = "0.3.31" version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]] [[package]]
name = "portable-atomic" name = "portable-atomic"
@@ -1694,7 +1713,7 @@ version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [ dependencies = [
"zerocopy", "zerocopy 0.7.35",
] ]
[[package]] [[package]]
@@ -1736,27 +1755,27 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "3.2.0" version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
dependencies = [ dependencies = [
"toml_edit", "toml_edit",
] ]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.93" version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.38" version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@@ -1768,8 +1787,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [ dependencies = [
"libc", "libc",
"rand_chacha", "rand_chacha 0.3.1",
"rand_core", "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]] [[package]]
@@ -1779,7 +1809,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [ dependencies = [
"ppv-lite86", "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]] [[package]]
@@ -1791,6 +1831,15 @@ dependencies = [
"getrandom 0.2.15", "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]] [[package]]
name = "ratatui" name = "ratatui"
version = "0.29.0" version = "0.29.0"
@@ -1803,7 +1852,7 @@ dependencies = [
"crossterm", "crossterm",
"indoc", "indoc",
"instability", "instability",
"itertools", "itertools 0.13.0",
"lru", "lru",
"paste", "paste",
"strum", "strum",
@@ -1815,9 +1864,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.9" version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
dependencies = [ dependencies = [
"bitflags", "bitflags",
] ]
@@ -1914,9 +1963,9 @@ dependencies = [
[[package]] [[package]]
name = "ring" name = "ring"
version = "0.17.11" version = "0.17.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73" checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee"
dependencies = [ dependencies = [
"cc", "cc",
"cfg-if", "cfg-if",
@@ -1928,21 +1977,21 @@ dependencies = [
[[package]] [[package]]
name = "rstest" name = "rstest"
version = "0.23.0" version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a2c585be59b6b5dd66a9d2084aa1d8bd52fbdb806eafdeffb52791147862035" checksum = "6fc39292f8613e913f7df8fa892b8944ceb47c247b78e1b1ae2f09e019be789d"
dependencies = [ dependencies = [
"futures",
"futures-timer", "futures-timer",
"futures-util",
"rstest_macros", "rstest_macros",
"rustc_version", "rustc_version",
] ]
[[package]] [[package]]
name = "rstest_macros" name = "rstest_macros"
version = "0.23.0" version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "825ea780781b15345a146be27eaefb05085e337e869bff01b4306a4fd4a9ad5a" checksum = "1f168d99749d307be9de54d23fd226628d99768225ef08f6ffb52e0182a27746"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"glob", "glob",
@@ -1952,7 +2001,7 @@ dependencies = [
"regex", "regex",
"relative-path", "relative-path",
"rustc_version", "rustc_version",
"syn 2.0.98", "syn 2.0.99",
"unicode-ident", "unicode-ident",
] ]
@@ -1980,7 +2029,20 @@ dependencies = [
"bitflags", "bitflags",
"errno", "errno",
"libc", "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", "windows-sys 0.59.0",
] ]
@@ -2025,15 +2087,15 @@ dependencies = [
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.19" version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.19" version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]] [[package]]
name = "scc" name = "scc"
@@ -2090,9 +2152,9 @@ dependencies = [
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.25" version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
[[package]] [[package]]
name = "serde" name = "serde"
@@ -2121,14 +2183,14 @@ checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.98", "syn 2.0.99",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.139" version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@@ -2192,7 +2254,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.98", "syn 2.0.99",
] ]
[[package]] [[package]]
@@ -2299,7 +2361,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustversion", "rustversion",
"syn 2.0.98", "syn 2.0.99",
] ]
[[package]] [[package]]
@@ -2321,9 +2383,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.98" version = "2.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2347,7 +2409,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.98", "syn 2.0.99",
] ]
[[package]] [[package]]
@@ -2373,15 +2435,15 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.17.1" version = "3.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
"getrandom 0.3.1", "getrandom 0.3.1",
"once_cell", "once_cell",
"rustix", "rustix 1.0.1",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@@ -2391,7 +2453,7 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9"
dependencies = [ dependencies = [
"rustix", "rustix 0.38.44",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@@ -2418,7 +2480,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.98", "syn 2.0.99",
] ]
[[package]] [[package]]
@@ -2433,9 +2495,9 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.37" version = "0.3.39"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
dependencies = [ dependencies = [
"deranged", "deranged",
"libc", "libc",
@@ -2448,9 +2510,9 @@ dependencies = [
[[package]] [[package]]
name = "time-core" name = "time-core"
version = "0.1.2" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
[[package]] [[package]]
name = "tinystr" name = "tinystr"
@@ -2488,7 +2550,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.98", "syn 2.0.99",
] ]
[[package]] [[package]]
@@ -2503,9 +2565,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-rustls" name = "tokio-rustls"
version = "0.26.1" version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
dependencies = [ dependencies = [
"rustls", "rustls",
"tokio", "tokio",
@@ -2621,9 +2683,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.17" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
@@ -2637,7 +2699,7 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
dependencies = [ dependencies = [
"itertools", "itertools 0.13.0",
"unicode-segmentation", "unicode-segmentation",
"unicode-width 0.1.14", "unicode-width 0.1.14",
] ]
@@ -2719,6 +2781,15 @@ dependencies = [
"getrandom 0.3.1", "getrandom 0.3.1",
] ]
[[package]]
name = "validate_theme_derive"
version = "0.1.0"
dependencies = [
"log",
"quote",
"syn 2.0.99",
]
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"
@@ -2743,7 +2814,7 @@ checksum = "5b2d5567b6fbd34e8f0488d56b648e67c0d999535f4af2060d14f9074b43e833"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.98", "syn 2.0.99",
] ]
[[package]] [[package]]
@@ -2801,7 +2872,7 @@ dependencies = [
"log", "log",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.98", "syn 2.0.99",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -2836,7 +2907,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.98", "syn 2.0.99",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -3141,7 +3212,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.98", "syn 2.0.99",
"synstructure", "synstructure",
] ]
@@ -3152,7 +3223,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [ dependencies = [
"byteorder", "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]] [[package]]
@@ -3163,7 +3243,18 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "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]] [[package]]
@@ -3183,7 +3274,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.98", "syn 2.0.99",
"synstructure", "synstructure",
] ]
@@ -3212,5 +3303,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.98", "syn 2.0.99",
] ]
+16 -6
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "managarr" name = "managarr"
version = "0.5.0" version = "0.5.1"
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"] authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
description = "A TUI and CLI to manage your Servarrs" description = "A TUI and CLI to manage your Servarrs"
keywords = ["managarr", "ratatui", "dashboard", "servarr", "tui"] keywords = ["managarr", "ratatui", "dashboard", "servarr", "tui"]
@@ -10,9 +10,12 @@ homepage = "https://github.com/Dark-Alex-17/managarr"
readme = "README.md" readme = "README.md"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
rust-version = "1.82.0" rust-version = "1.85.0"
exclude = [".github", "CONTRIBUTING.md", "*.log", "tags"] exclude = [".github", "CONTRIBUTING.md", "*.log", "tags"]
[workspace]
members = ["proc_macros/enum_display_style_derive", "proc_macros/validate_theme_derive"]
[dependencies] [dependencies]
anyhow = "1.0.68" anyhow = "1.0.68"
backtrace = "0.3.74" backtrace = "0.3.74"
@@ -41,11 +44,16 @@ ratatui = { version = "0.29.0", features = [
"unstable-widget-ref", "unstable-widget-ref",
] } ] }
urlencoding = "2.1.2" 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" clap_complete = "4.5.33"
itertools = "0.13.0" itertools = "0.14.0"
ctrlc = "3.4.5" ctrlc = "3.4.5"
colored = "2.1.0" colored = "3.0.0"
async-trait = "0.1.83" async-trait = "0.1.83"
dirs-next = "2.0.0" dirs-next = "2.0.0"
managarr-tree-widget = "0.24.0" managarr-tree-widget = "0.24.0"
@@ -55,13 +63,15 @@ deunicode = "1.6.0"
paste = "1.0.15" paste = "1.0.15"
openssl = { version = "0.10.70", features = ["vendored"] } openssl = { version = "0.10.70", features = ["vendored"] }
veil = "0.2.0" 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] [dev-dependencies]
assert_cmd = "2.0.16" assert_cmd = "2.0.16"
mockall = "0.13.0" mockall = "0.13.0"
mockito = "1.0.0" mockito = "1.0.0"
pretty_assertions = "1.3.0" pretty_assertions = "1.3.0"
rstest = "0.23.0" rstest = "0.25.0"
serial_test = "3.2.0" serial_test = "3.2.0"
[dev-dependencies.cargo-husky] [dev-dependencies.cargo-husky]
+1 -1
View File
@@ -8,7 +8,7 @@ default: run
.PHONY: test test-cov build run lint lint-fix fmt analyze sonar release delete-tag .PHONY: test test-cov build run lint lint-fix fmt analyze sonar release delete-tag
test: test:
@cargo test @cargo test --all
## Run all tests with coverage - `cargo install cargo-tarpaulin` ## Run all tests with coverage - `cargo install cargo-tarpaulin`
test-cov: test-cov:
+15 -1
View File
@@ -206,6 +206,16 @@ Key:
- [ ] Support for Tautulli - [ ] Support for Tautulli
### Themes
Managarr ships with a few themes out of the box. Here's a few examples:
![default](themes/default/manual_episode_search.png)
![dracula](themes/dracula/manual_episode_search.png)
![watermelon-dark](themes/watermelon-dark/manual_episode_search.png)
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 ### The Managarr CLI
Managarr can be used in one of two ways: As a TUI, or as a CLI for managing your Servarrs. 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 ```shell
$ managarr --help $ managarr --help
managarr 0.5.0 managarr 0.5.1
Alex Clarke <alex.j.tusa@gmail.com> Alex Clarke <alex.j.tusa@gmail.com>
A TUI and CLI to manage your Servarrs A TUI and CLI to manage your Servarrs
@@ -235,6 +245,8 @@ Commands:
Options: Options:
--disable-spinner Disable the spinner (can sometimes make parsing output challenging) [env: MANAGARR_DISABLE_SPINNER=] --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=] --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. --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. 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. 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: ### Example Configuration:
```yaml ```yaml
theme: default
radarr: radarr:
- host: 192.168.0.78 - host: 192.168.0.78
port: 7878 port: 7878
@@ -357,6 +370,7 @@ tautulli:
### Example Multi-Instance Configuration: ### Example Multi-Instance Configuration:
```yaml ```yaml
theme: default
radarr: radarr:
- host: 192.168.0.78 # No name specified, so this instance's name will default to 'Radarr 1' - host: 192.168.0.78 # No name specified, so this instance's name will default to 'Radarr 1'
port: 7878 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

+1
View File
@@ -31,6 +31,7 @@ mod tests {
}; };
let sonarr_config_2 = ServarrConfig::default(); let sonarr_config_2 = ServarrConfig::default();
let config = AppConfig { let config = AppConfig {
theme: None,
radarr: Some(vec![radarr_config_1.clone(), radarr_config_2.clone()]), radarr: Some(vec![radarr_config_1.clone(), radarr_config_2.clone()]),
sonarr: Some(vec![sonarr_config_1.clone(), sonarr_config_2.clone()]), sonarr: Some(vec![sonarr_config_1.clone(), sonarr_config_2.clone()]),
}; };
+61 -1
View File
@@ -44,128 +44,188 @@ generate_keybindings! {
#[derive(Clone, Copy, Eq, PartialEq, Debug)] #[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct KeyBinding { pub struct KeyBinding {
pub key: Key, pub key: Key,
pub alt: Option<Key>,
pub desc: &'static str, pub desc: &'static str,
} }
pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings { pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings {
add: KeyBinding { add: KeyBinding {
key: Key::Char('a'), key: Key::Char('a'),
alt: None,
desc: "add", desc: "add",
}, },
up: KeyBinding { up: KeyBinding {
key: Key::Up, key: Key::Up,
alt: Some(Key::Char('k')),
desc: "up", desc: "up",
}, },
down: KeyBinding { down: KeyBinding {
key: Key::Down, key: Key::Down,
alt: Some(Key::Char('j')),
desc: "down", desc: "down",
}, },
left: KeyBinding { left: KeyBinding {
key: Key::Left, key: Key::Left,
alt: Some(Key::Char('h')),
desc: "left", desc: "left",
}, },
right: KeyBinding { right: KeyBinding {
key: Key::Right, key: Key::Right,
alt: Some(Key::Char('l')),
desc: "right", desc: "right",
}, },
backspace: KeyBinding { backspace: KeyBinding {
key: Key::Backspace, key: Key::Backspace,
alt: None,
desc: "backspace", desc: "backspace",
}, },
next_servarr: KeyBinding { next_servarr: KeyBinding {
key: Key::Tab, key: Key::Tab,
alt: None,
desc: "next servarr", desc: "next servarr",
}, },
previous_servarr: KeyBinding { previous_servarr: KeyBinding {
key: Key::BackTab, key: Key::BackTab,
alt: None,
desc: "previous servarr", desc: "previous servarr",
}, },
clear: KeyBinding { clear: KeyBinding {
key: Key::Char('c'), key: Key::Char('c'),
alt: None,
desc: "clear", desc: "clear",
}, },
auto_search: KeyBinding { auto_search: KeyBinding {
key: Key::Char('S'), key: Key::Char('S'),
alt: None,
desc: "auto search", desc: "auto search",
}, },
search: KeyBinding { search: KeyBinding {
key: Key::Char('s'), key: Key::Char('s'),
alt: None,
desc: "search", desc: "search",
}, },
settings: KeyBinding { settings: KeyBinding {
key: Key::Char('S'), key: Key::Char('S'),
alt: None,
desc: "settings", desc: "settings",
}, },
filter: KeyBinding { filter: KeyBinding {
key: Key::Char('f'), key: Key::Char('f'),
alt: None,
desc: "filter", desc: "filter",
}, },
sort: KeyBinding { sort: KeyBinding {
key: Key::Char('o'), key: Key::Char('o'),
alt: None,
desc: "sort", desc: "sort",
}, },
edit: KeyBinding { edit: KeyBinding {
key: Key::Char('e'), key: Key::Char('e'),
alt: None,
desc: "edit", desc: "edit",
}, },
events: KeyBinding { events: KeyBinding {
key: Key::Char('e'), key: Key::Char('e'),
alt: None,
desc: "events", desc: "events",
}, },
logs: KeyBinding { logs: KeyBinding {
key: Key::Char('l'), key: Key::Char('L'),
alt: None,
desc: "logs", desc: "logs",
}, },
tasks: KeyBinding { tasks: KeyBinding {
key: Key::Char('t'), key: Key::Char('t'),
alt: None,
desc: "tasks", desc: "tasks",
}, },
test: KeyBinding { test: KeyBinding {
key: Key::Char('t'), key: Key::Char('t'),
alt: None,
desc: "test", desc: "test",
}, },
test_all: KeyBinding { test_all: KeyBinding {
key: Key::Char('T'), key: Key::Char('T'),
alt: None,
desc: "test all", desc: "test all",
}, },
toggle_monitoring: KeyBinding { toggle_monitoring: KeyBinding {
key: Key::Char('m'), key: Key::Char('m'),
alt: None,
desc: "toggle monitoring", desc: "toggle monitoring",
}, },
refresh: KeyBinding { refresh: KeyBinding {
key: Key::Ctrl('r'), key: Key::Ctrl('r'),
alt: None,
desc: "refresh", desc: "refresh",
}, },
update: KeyBinding { update: KeyBinding {
key: Key::Char('u'), key: Key::Char('u'),
alt: None,
desc: "update", desc: "update",
}, },
home: KeyBinding { home: KeyBinding {
key: Key::Home, key: Key::Home,
alt: None,
desc: "home", desc: "home",
}, },
end: KeyBinding { end: KeyBinding {
key: Key::End, key: Key::End,
alt: None,
desc: "end", desc: "end",
}, },
delete: KeyBinding { delete: KeyBinding {
key: Key::Delete, key: Key::Delete,
alt: None,
desc: "delete", desc: "delete",
}, },
submit: KeyBinding { submit: KeyBinding {
key: Key::Enter, key: Key::Enter,
alt: None,
desc: "submit", desc: "submit",
}, },
confirm: KeyBinding { confirm: KeyBinding {
key: Key::Ctrl('s'), key: Key::Ctrl('s'),
alt: None,
desc: "submit", desc: "submit",
}, },
quit: KeyBinding { quit: KeyBinding {
key: Key::Char('q'), key: Key::Char('q'),
alt: None,
desc: "quit", desc: "quit",
}, },
esc: KeyBinding { esc: KeyBinding {
key: Key::Esc, key: Key::Esc,
alt: None,
desc: "close", 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)
};
}
+1 -1
View File
@@ -23,7 +23,7 @@ mod test {
#[case(DEFAULT_KEYBINDINGS.sort, Key::Char('o'), "sort")] #[case(DEFAULT_KEYBINDINGS.sort, Key::Char('o'), "sort")]
#[case(DEFAULT_KEYBINDINGS.edit, Key::Char('e'), "edit")] #[case(DEFAULT_KEYBINDINGS.edit, Key::Char('e'), "edit")]
#[case(DEFAULT_KEYBINDINGS.events, Key::Char('e'), "events")] #[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.tasks, Key::Char('t'), "tasks")]
#[case(DEFAULT_KEYBINDINGS.test, Key::Char('t'), "test")] #[case(DEFAULT_KEYBINDINGS.test, Key::Char('t'), "test")]
#[case(DEFAULT_KEYBINDINGS.test_all, Key::Char('T'), "test all")] #[case(DEFAULT_KEYBINDINGS.test_all, Key::Char('T'), "test all")]
+1
View File
@@ -270,6 +270,7 @@ pub struct Data<'a> {
#[derive(Debug, Deserialize, Serialize, Default, Clone)] #[derive(Debug, Deserialize, Serialize, Default, Clone)]
pub struct AppConfig { pub struct AppConfig {
pub theme: Option<String>,
pub radarr: Option<Vec<ServarrConfig>>, pub radarr: Option<Vec<ServarrConfig>>,
pub sonarr: Option<Vec<ServarrConfig>>, pub sonarr: Option<Vec<ServarrConfig>>,
} }
+18 -17
View File
@@ -1,9 +1,9 @@
use radarr_handlers::RadarrHandler; use radarr_handlers::RadarrHandler;
use sonarr_handlers::SonarrHandler; use sonarr_handlers::SonarrHandler;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::matches_key;
use crate::models::{HorizontallyScrollableText, Route}; use crate::models::{HorizontallyScrollableText, Route};
mod radarr_handlers; mod radarr_handlers;
@@ -22,40 +22,42 @@ pub trait KeyEventHandler<'a, 'b, T: Into<Route> + Copy> {
fn handle_key_event(&mut self) { fn handle_key_event(&mut self) {
let key = self.get_key(); let key = self.get_key();
match key { match key {
_ if key == DEFAULT_KEYBINDINGS.up.key => { _ if matches_key!(up, key, self.ignore_alt_navigation()) => {
if self.is_ready() { if self.is_ready() {
self.handle_scroll_up(); self.handle_scroll_up();
} }
} }
_ if key == DEFAULT_KEYBINDINGS.down.key => { _ if matches_key!(down, key, self.ignore_alt_navigation()) => {
if self.is_ready() { if self.is_ready() {
self.handle_scroll_down(); self.handle_scroll_down();
} }
} }
_ if key == DEFAULT_KEYBINDINGS.home.key => { _ if matches_key!(home, key) => {
if self.is_ready() { if self.is_ready() {
self.handle_home(); self.handle_home();
} }
} }
_ if key == DEFAULT_KEYBINDINGS.end.key => { _ if matches_key!(end, key) => {
if self.is_ready() { if self.is_ready() {
self.handle_end(); self.handle_end();
} }
} }
_ if key == DEFAULT_KEYBINDINGS.delete.key => { _ if matches_key!(delete, key) => {
if self.is_ready() { if self.is_ready() {
self.handle_delete(); 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() self.handle_left_right_action()
} }
_ if key == DEFAULT_KEYBINDINGS.submit.key => { _ if matches_key!(submit, key) => {
if self.is_ready() { if self.is_ready() {
self.handle_submit(); self.handle_submit();
} }
} }
_ if key == DEFAULT_KEYBINDINGS.esc.key => self.handle_esc(), _ if matches_key!(esc, key) => self.handle_esc(),
_ => { _ => {
if self.is_ready() { if self.is_ready() {
self.handle_char_key_event(); 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 accepts(active_block: T) -> bool;
fn new(key: Key, app: &'a mut App<'b>, active_block: T, context: Option<T>) -> Self; fn new(key: Key, app: &'a mut App<'b>, active_block: T, context: Option<T>) -> Self;
fn get_key(&self) -> Key; fn get_key(&self) -> Key;
fn ignore_alt_navigation(&self) -> bool;
fn is_ready(&self) -> bool; fn is_ready(&self) -> bool;
fn handle_scroll_up(&mut self); fn handle_scroll_up(&mut self);
fn handle_scroll_down(&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<'_>) { 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.reset();
app.server_tabs.next(); app.server_tabs.next();
app.pop_and_push_navigation_stack(app.server_tabs.get_active_route()); app.pop_and_push_navigation_stack(app.server_tabs.get_active_route());
app.cancellation_token.cancel(); app.cancellation_token.cancel();
} else if key == DEFAULT_KEYBINDINGS.previous_servarr.key { } else if matches_key!(previous_servarr, key) {
app.reset(); app.reset();
app.server_tabs.previous(); app.server_tabs.previous();
app.pop_and_push_navigation_stack(app.server_tabs.get_active_route()); 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) { fn handle_prompt_toggle(app: &mut App<'_>, key: Key) {
match key { match key {
_ if key == DEFAULT_KEYBINDINGS.left.key || key == DEFAULT_KEYBINDINGS.right.key => { _ if matches_key!(left, key) || matches_key!(right, key) => match app.get_current_route() {
match app.get_current_route() {
Route::Radarr(_, _) => { Route::Radarr(_, _) => {
app.data.radarr_data.prompt_confirm = !app.data.radarr_data.prompt_confirm 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 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 { macro_rules! handle_text_box_keys {
($self:expr, $key:expr, $input:expr) => { ($self:expr, $key:expr, $input:expr) => {
match $self.key { match $self.key {
_ if $key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.backspace.key => { _ if $crate::matches_key!(backspace, $key) => {
$input.pop(); $input.pop();
} }
Key::Char(character) => { Key::Char(character) => {
@@ -165,7 +166,7 @@ macro_rules! handle_prompt_left_right_keys {
($self:expr, $confirm_prompt:expr, $data:ident) => { ($self:expr, $confirm_prompt:expr, $data:ident) => {
if $self.app.data.$data.selected_block.get_active_block() == $confirm_prompt { if $self.app.data.$data.selected_block.get_active_block() == $confirm_prompt {
handle_prompt_toggle($self.app, $self.key); 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(); $self.app.data.$data.selected_block.left();
} else { } else {
$self.app.data.$data.selected_block.right(); $self.app.data.$data.selected_block.right();
@@ -4,6 +4,7 @@ mod tests {
use chrono::DateTime; use chrono::DateTime;
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; 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] #[test]
fn test_extract_blocklist_item_id() { fn test_extract_blocklist_item_id() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; 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::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; 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::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, BLOCKLIST_BLOCKS};
use crate::models::stateful_table::SortOption; use crate::models::stateful_table::SortOption;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_table_events, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "blocklist_handler_tests.rs"] #[path = "blocklist_handler_tests.rs"]
@@ -51,6 +50,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
BLOCKLIST_BLOCKS.contains(&active_block) BLOCKLIST_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -143,10 +146,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
let key = self.key; let key = self.key;
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::Blocklist => match self.key { ActiveRadarrBlock::Blocklist => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ if key == DEFAULT_KEYBINDINGS.clear.key => { _ if matches_key!(clear, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::BlocklistClearAllItemsPrompt.into()); .push_navigation_stack(ActiveRadarrBlock::BlocklistClearAllItemsPrompt.into());
@@ -154,7 +157,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
_ => (), _ => (),
}, },
ActiveRadarrBlock::DeleteBlocklistItemPrompt => { 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 = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteBlocklistItem( self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteBlocklistItem(
self.extract_blocklist_item_id(), self.extract_blocklist_item_id(),
@@ -164,7 +167,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
} }
} }
ActiveRadarrBlock::BlocklistClearAllItemsPrompt => { 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 = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::ClearBlocklist); 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::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::KeyEventHandler; use crate::handlers::KeyEventHandler;
use crate::models::radarr_models::CollectionMovie; 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::stateful_table::StatefulTable;
use crate::models::BlockSelectionState; use crate::models::BlockSelectionState;
use crate::{handle_table_events, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "collection_details_handler_tests.rs"] #[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) COLLECTION_DETAILS_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, 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) { fn handle_char_key_event(&mut self) {
if self.active_radarr_block == ActiveRadarrBlock::CollectionDetails if self.active_radarr_block == ActiveRadarrBlock::CollectionDetails
&& self.key == DEFAULT_KEYBINDINGS.edit.key && matches_key!(edit, self.key)
{ {
self.app.push_navigation_stack( self.app.push_navigation_stack(
( (
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_str_eq; use pretty_assertions::assert_str_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; 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] #[test]
fn test_collection_details_handler_not_ready_when_loading() { fn test_collection_details_handler_not_ready_when_loading() {
let mut app = App::test_default(); 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] #[test]
fn test_collections_handler_not_ready_when_loading() { fn test_collections_handler_not_ready_when_loading() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; 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::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS};
use crate::models::Scrollable; use crate::models::Scrollable;
use crate::network::radarr_network::RadarrEvent; 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)] #[cfg(test)]
#[path = "edit_collection_handler_tests.rs"] #[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) EDIT_COLLECTION_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -354,7 +357,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
ActiveRadarrBlock::EditCollectionPrompt => { ActiveRadarrBlock::EditCollectionPrompt => {
if self.app.data.radarr_data.selected_block.get_active_block() if self.app.data.radarr_data.selected_block.get_active_block()
== ActiveRadarrBlock::EditCollectionConfirmPrompt == 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 = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditCollection( self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditCollection(
@@ -2,6 +2,7 @@
mod tests { mod tests {
use bimap::BiMap; use bimap::BiMap;
use pretty_assertions::assert_str_eq; use pretty_assertions::assert_str_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -926,7 +927,7 @@ mod tests {
app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default()); app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default());
EditCollectionHandler::new( EditCollectionHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::EditCollectionRootFolderPathInput, ActiveRadarrBlock::EditCollectionRootFolderPathInput,
None, None,
@@ -942,7 +943,7 @@ mod tests {
.unwrap() .unwrap()
.path .path
.text, .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] #[test]
fn test_build_edit_collection_params() { fn test_build_edit_collection_params() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; 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::collection_details_handler::CollectionDetailsHandler;
use crate::handlers::radarr_handlers::collections::edit_collection_handler::EditCollectionHandler; use crate::handlers::radarr_handlers::collections::edit_collection_handler::EditCollectionHandler;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; 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::stateful_table::SortOption;
use crate::models::BlockSelectionState; use crate::models::BlockSelectionState;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_table_events, matches_key};
mod collection_details_handler; mod collection_details_handler;
mod edit_collection_handler; mod edit_collection_handler;
@@ -73,6 +72,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
|| COLLECTIONS_BLOCKS.contains(&active_block) || COLLECTIONS_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -145,7 +148,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
let key = self.key; let key = self.key;
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::Collections => match self.key { ActiveRadarrBlock::Collections => match self.key {
_ if key == DEFAULT_KEYBINDINGS.edit.key => { _ if matches_key!(edit, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); .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 = self.app.data.radarr_data.selected_block =
BlockSelectionState::new(EDIT_COLLECTION_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_COLLECTION_SELECTION_BLOCKS);
} }
_ if key == DEFAULT_KEYBINDINGS.update.key => { _ if matches_key!(update, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::UpdateAllCollectionsPrompt.into()); .push_navigation_stack(ActiveRadarrBlock::UpdateAllCollectionsPrompt.into());
} }
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ => (), _ => (),
}, },
ActiveRadarrBlock::UpdateAllCollectionsPrompt => { 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 = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateCollections); self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateCollections);
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; 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] #[test]
fn test_extract_download_id() { fn test_extract_download_id() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,13 +1,12 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; 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::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
use crate::models::radarr_models::DownloadRecord; use crate::models::radarr_models::DownloadRecord;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS};
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_table_events, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "downloads_handler_tests.rs"] #[path = "downloads_handler_tests.rs"]
@@ -47,6 +46,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
DOWNLOADS_BLOCKS.contains(&active_block) DOWNLOADS_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -130,18 +133,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
let key = self.key; let key = self.key;
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::Downloads => match self.key { ActiveRadarrBlock::Downloads => match self.key {
_ if key == DEFAULT_KEYBINDINGS.update.key => { _ if matches_key!(update, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::UpdateDownloadsPrompt.into()); .push_navigation_stack(ActiveRadarrBlock::UpdateDownloadsPrompt.into());
} }
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ => (), _ => (),
}, },
ActiveRadarrBlock::DeleteDownloadPrompt => { 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 = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::DeleteDownload(self.extract_download_id())); Some(RadarrEvent::DeleteDownload(self.extract_download_id()));
@@ -150,7 +153,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
} }
} }
ActiveRadarrBlock::UpdateDownloadsPrompt => { 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 = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateDownloads); 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::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; 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_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS};
use crate::models::servarr_models::EditIndexerParams; use crate::models::servarr_models::EditIndexerParams;
use crate::network::radarr_network::RadarrEvent; 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)] #[cfg(test)]
#[path = "edit_indexer_handler_tests.rs"] #[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) EDIT_INDEXER_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -504,7 +509,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
ActiveRadarrBlock::EditIndexerPrompt => { ActiveRadarrBlock::EditIndexerPrompt => {
if self.app.data.radarr_data.selected_block.get_active_block() if self.app.data.radarr_data.selected_block.get_active_block()
== ActiveRadarrBlock::EditIndexerConfirmPrompt == 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 = true;
self.app.data.radarr_data.prompt_confirm_action = 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_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS};
use crate::models::servarr_models::EditIndexerParams; use crate::models::servarr_models::EditIndexerParams;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
mod test_handle_scroll_up_and_down { mod test_handle_scroll_up_and_down {
@@ -1597,7 +1598,7 @@ mod tests {
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new( EditIndexerHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::EditIndexerNameInput, ActiveRadarrBlock::EditIndexerNameInput,
None, None,
@@ -1613,7 +1614,7 @@ mod tests {
.unwrap() .unwrap()
.name .name
.text, .text,
"h" "a"
); );
} }
@@ -1624,7 +1625,7 @@ mod tests {
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new( EditIndexerHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::EditIndexerUrlInput, ActiveRadarrBlock::EditIndexerUrlInput,
None, None,
@@ -1640,7 +1641,7 @@ mod tests {
.unwrap() .unwrap()
.url .url
.text, .text,
"h" "a"
); );
} }
@@ -1651,7 +1652,7 @@ mod tests {
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new( EditIndexerHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::EditIndexerApiKeyInput, ActiveRadarrBlock::EditIndexerApiKeyInput,
None, None,
@@ -1667,7 +1668,7 @@ mod tests {
.unwrap() .unwrap()
.api_key .api_key
.text, .text,
"h" "a"
); );
} }
@@ -1678,7 +1679,7 @@ mod tests {
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new( EditIndexerHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::EditIndexerSeedRatioInput, ActiveRadarrBlock::EditIndexerSeedRatioInput,
None, None,
@@ -1694,7 +1695,7 @@ mod tests {
.unwrap() .unwrap()
.seed_ratio .seed_ratio
.text, .text,
"h" "a"
); );
} }
@@ -1705,7 +1706,7 @@ mod tests {
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new( EditIndexerHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::EditIndexerTagsInput, ActiveRadarrBlock::EditIndexerTagsInput,
None, None,
@@ -1721,7 +1722,7 @@ mod tests {
.unwrap() .unwrap()
.tags .tags
.text, .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] #[test]
fn test_build_edit_indexer_params() { fn test_build_edit_indexer_params() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -7,7 +6,9 @@ use crate::models::servarr_data::radarr::radarr_data::{
ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS, ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS,
}; };
use crate::network::radarr_network::RadarrEvent; 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)] #[cfg(test)]
#[path = "edit_indexer_settings_handler_tests.rs"] #[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) INDEXER_SETTINGS_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -269,7 +274,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
ActiveRadarrBlock::AllIndexerSettingsPrompt => { ActiveRadarrBlock::AllIndexerSettingsPrompt => {
if self.app.data.radarr_data.selected_block.get_active_block() if self.app.data.radarr_data.selected_block.get_active_block()
== ActiveRadarrBlock::IndexerSettingsConfirmPrompt == 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 = true;
self.app.data.radarr_data.prompt_confirm_action = Some( self.app.data.radarr_data.prompt_confirm_action = Some(
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -907,7 +908,7 @@ mod tests {
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); app.data.radarr_data.indexer_settings = Some(IndexerSettings::default());
IndexerSettingsHandler::new( IndexerSettingsHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
None, None,
@@ -923,7 +924,7 @@ mod tests {
.unwrap() .unwrap()
.whitelisted_hardcoded_subs .whitelisted_hardcoded_subs
.text, .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] #[test]
fn test_build_edit_indexer_settings_body() { fn test_build_edit_indexer_settings_body() {
let mut app = App::test_default(); 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] #[test]
fn test_extract_indexer_id() { fn test_extract_indexer_id() {
let mut app = App::test_default(); let mut app = App::test_default();
+10 -7
View File
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; 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::handle_change_tab_left_right_keys;
use crate::handlers::radarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler; use crate::handlers::radarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler;
use crate::handlers::radarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler; 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::servarr_models::Indexer;
use crate::models::BlockSelectionState; use crate::models::BlockSelectionState;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_table_events, matches_key};
mod edit_indexer_handler; mod edit_indexer_handler;
mod edit_indexer_settings_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) || INDEXERS_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -169,20 +172,20 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
let key = self.key; let key = self.key;
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::Indexers => match self.key { ActiveRadarrBlock::Indexers => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ if key == DEFAULT_KEYBINDINGS.test.key => { _ if matches_key!(test, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::TestIndexer.into()); .push_navigation_stack(ActiveRadarrBlock::TestIndexer.into());
} }
_ if key == DEFAULT_KEYBINDINGS.test_all.key => { _ if matches_key!(test_all, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::TestAllIndexers.into()); .push_navigation_stack(ActiveRadarrBlock::TestAllIndexers.into());
} }
_ if key == DEFAULT_KEYBINDINGS.settings.key => { _ if matches_key!(settings, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into()); .push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into());
@@ -192,7 +195,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
_ => (), _ => (),
}, },
ActiveRadarrBlock::DeleteIndexerPrompt => { 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 = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::DeleteIndexer(self.extract_indexer_id())); Some(RadarrEvent::DeleteIndexer(self.extract_indexer_id()));
@@ -48,6 +48,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for TestAllIndexersHandl
active_block == ActiveRadarrBlock::TestAllIndexers active_block == ActiveRadarrBlock::TestAllIndexers
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -7,6 +7,7 @@ mod tests {
use crate::models::servarr_data::modals::IndexerTestResultModalItem; use crate::models::servarr_data::modals::IndexerTestResultModalItem;
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::models::stateful_table::StatefulTable; use crate::models::stateful_table::StatefulTable;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
mod test_handle_esc { 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] #[test]
fn test_test_all_indexers_handler_is_not_ready_when_loading() { fn test_test_all_indexers_handler_is_not_ready_when_loading() {
let mut app = App::test_default(); 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::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::radarr_models::{ 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::stateful_table::StatefulTable;
use crate::models::{BlockSelectionState, Scrollable}; use crate::models::{BlockSelectionState, Scrollable};
use crate::network::radarr_network::RadarrEvent; 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)] #[cfg(test)]
#[path = "add_movie_handler_tests.rs"] #[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) ADD_MOVIE_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -542,7 +547,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
ActiveRadarrBlock::AddMoviePrompt => { ActiveRadarrBlock::AddMoviePrompt => {
if self.app.data.radarr_data.selected_block.get_active_block() if self.app.data.radarr_data.selected_block.get_active_block()
== ActiveRadarrBlock::AddMovieConfirmPrompt == 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 = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action =
@@ -1395,7 +1395,7 @@ mod tests {
app.data.radarr_data.add_movie_search = Some(HorizontallyScrollableText::default()); app.data.radarr_data.add_movie_search = Some(HorizontallyScrollableText::default());
AddMovieHandler::new( AddMovieHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::AddMovieSearchInput, ActiveRadarrBlock::AddMovieSearchInput,
None, None,
@@ -1404,7 +1404,7 @@ mod tests {
assert_str_eq!( assert_str_eq!(
app.data.radarr_data.add_movie_search.as_ref().unwrap().text, 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()); app.data.radarr_data.add_movie_modal = Some(AddMovieModal::default());
AddMovieHandler::new( AddMovieHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::AddMovieTagsInput, ActiveRadarrBlock::AddMovieTagsInput,
None, None,
@@ -1430,7 +1430,7 @@ mod tests {
.unwrap() .unwrap()
.tags .tags
.text, .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] #[test]
fn test_add_movie_search_no_panic_on_none_search_result() { fn test_add_movie_search_no_panic_on_none_search_result() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,7 +1,7 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::matches_key;
use crate::models::radarr_models::DeleteMovieParams; use crate::models::radarr_models::DeleteMovieParams;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS};
use crate::network::radarr_network::RadarrEvent; 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) DELETE_MOVIE_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, 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 if self.active_radarr_block == ActiveRadarrBlock::DeleteMoviePrompt
&& self.app.data.radarr_data.selected_block.get_active_block() && self.app.data.radarr_data.selected_block.get_active_block()
== ActiveRadarrBlock::DeleteMovieConfirmPrompt == 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 = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action =
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; 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] #[test]
fn test_build_delete_movie_params() { fn test_build_delete_movie_params() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; 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::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_MOVIE_BLOCKS};
use crate::models::Scrollable; use crate::models::Scrollable;
use crate::network::radarr_network::RadarrEvent; 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)] #[cfg(test)]
#[path = "edit_movie_handler_tests.rs"] #[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) EDIT_MOVIE_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -376,7 +379,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a,
ActiveRadarrBlock::EditMoviePrompt => { ActiveRadarrBlock::EditMoviePrompt => {
if self.app.data.radarr_data.selected_block.get_active_block() if self.app.data.radarr_data.selected_block.get_active_block()
== ActiveRadarrBlock::EditMovieConfirmPrompt == 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 = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action =
@@ -2,6 +2,7 @@
mod tests { mod tests {
use bimap::BiMap; use bimap::BiMap;
use pretty_assertions::assert_str_eq; use pretty_assertions::assert_str_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -1033,7 +1034,7 @@ mod tests {
app.data.radarr_data.edit_movie_modal = Some(EditMovieModal::default()); app.data.radarr_data.edit_movie_modal = Some(EditMovieModal::default());
EditMovieHandler::new( EditMovieHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::EditMoviePathInput, ActiveRadarrBlock::EditMoviePathInput,
None, None,
@@ -1049,7 +1050,7 @@ mod tests {
.unwrap() .unwrap()
.path .path
.text, .text,
"h" "a"
); );
} }
@@ -1059,7 +1060,7 @@ mod tests {
app.data.radarr_data.edit_movie_modal = Some(EditMovieModal::default()); app.data.radarr_data.edit_movie_modal = Some(EditMovieModal::default());
EditMovieHandler::new( EditMovieHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::EditMovieTagsInput, ActiveRadarrBlock::EditMovieTagsInput,
None, None,
@@ -1075,7 +1076,7 @@ mod tests {
.unwrap() .unwrap()
.tags .tags
.text, .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] #[test]
fn test_build_edit_movie_params() { fn test_build_edit_movie_params() {
let mut app = App::test_default(); 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] #[test]
fn test_library_handler_not_ready_when_loading() { fn test_library_handler_not_ready_when_loading() {
let mut app = App::test_default(); let mut app = App::test_default();
+10 -7
View File
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; 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::radarr_handlers::library::movie_details_handler::MovieDetailsHandler;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
use crate::handle_table_events;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::models::radarr_models::Movie; use crate::models::radarr_models::Movie;
use crate::models::servarr_data::radarr::radarr_data::{ 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::stateful_table::SortOption;
use crate::models::{BlockSelectionState, HorizontallyScrollableText}; use crate::models::{BlockSelectionState, HorizontallyScrollableText};
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_table_events, matches_key};
mod add_movie_handler; mod add_movie_handler;
mod delete_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) || LIBRARY_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -161,7 +164,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
let key = self.key; let key = self.key;
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::Movies => match self.key { ActiveRadarrBlock::Movies => match self.key {
_ if key == DEFAULT_KEYBINDINGS.edit.key => { _ if matches_key!(edit, key) => {
self.app.push_navigation_stack( self.app.push_navigation_stack(
( (
ActiveRadarrBlock::EditMoviePrompt, ActiveRadarrBlock::EditMoviePrompt,
@@ -173,25 +176,25 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
self.app.data.radarr_data.selected_block = self.app.data.radarr_data.selected_block =
BlockSelectionState::new(EDIT_MOVIE_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_MOVIE_SELECTION_BLOCKS);
} }
_ if key == DEFAULT_KEYBINDINGS.add.key => { _ if matches_key!(add, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into()); .push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into());
self.app.data.radarr_data.add_movie_search = Some(HorizontallyScrollableText::default()); self.app.data.radarr_data.add_movie_search = Some(HorizontallyScrollableText::default());
self.app.should_ignore_quit_key = true; self.app.should_ignore_quit_key = true;
} }
_ if key == DEFAULT_KEYBINDINGS.update.key => { _ if matches_key!(update, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::UpdateAllMoviesPrompt.into()); .push_navigation_stack(ActiveRadarrBlock::UpdateAllMoviesPrompt.into());
} }
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ => (), _ => (),
}, },
ActiveRadarrBlock::UpdateAllMoviesPrompt => { 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 = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateAllMovies); self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateAllMovies);
@@ -1,9 +1,7 @@
use serde_json::Number; use serde_json::Number;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::radarr_models::{ use crate::models::radarr_models::{
@@ -16,6 +14,7 @@ use crate::models::servarr_models::Language;
use crate::models::stateful_table::SortOption; use crate::models::stateful_table::SortOption;
use crate::models::{BlockSelectionState, Scrollable}; use crate::models::{BlockSelectionState, Scrollable};
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_table_events, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "movie_details_handler_tests.rs"] #[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) MOVIE_DETAILS_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -245,13 +248,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<
| ActiveRadarrBlock::Cast | ActiveRadarrBlock::Cast
| ActiveRadarrBlock::Crew | ActiveRadarrBlock::Crew
| ActiveRadarrBlock::ManualSearch => match self.key { | 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.data.radarr_data.movie_info_tabs.previous();
self.app.pop_and_push_navigation_stack( self.app.pop_and_push_navigation_stack(
self.app.data.radarr_data.movie_info_tabs.get_active_route(), 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.data.radarr_data.movie_info_tabs.next();
self.app.pop_and_push_navigation_stack( self.app.pop_and_push_navigation_stack(
self.app.data.radarr_data.movie_info_tabs.get_active_route(), 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::Cast
| ActiveRadarrBlock::Crew | ActiveRadarrBlock::Crew
| ActiveRadarrBlock::ManualSearch => match self.key { | ActiveRadarrBlock::ManualSearch => match self.key {
_ if key == DEFAULT_KEYBINDINGS.auto_search.key => { _ if matches_key!(auto_search, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::AutomaticallySearchMoviePrompt.into()); .push_navigation_stack(ActiveRadarrBlock::AutomaticallySearchMoviePrompt.into());
} }
_ if key == DEFAULT_KEYBINDINGS.edit.key => { _ if matches_key!(edit, key) => {
self.app.push_navigation_stack( self.app.push_navigation_stack(
( (
ActiveRadarrBlock::EditMoviePrompt, ActiveRadarrBlock::EditMoviePrompt,
@@ -349,35 +352,33 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<
self.app.data.radarr_data.selected_block = self.app.data.radarr_data.selected_block =
BlockSelectionState::new(EDIT_MOVIE_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_MOVIE_SELECTION_BLOCKS);
} }
_ if key == DEFAULT_KEYBINDINGS.update.key => { _ if matches_key!(update, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::UpdateAndScanPrompt.into()); .push_navigation_stack(ActiveRadarrBlock::UpdateAndScanPrompt.into());
} }
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self self
.app .app
.pop_and_push_navigation_stack(self.active_radarr_block.into()); .pop_and_push_navigation_stack(self.active_radarr_block.into());
} }
_ => (), _ => (),
}, },
ActiveRadarrBlock::AutomaticallySearchMoviePrompt ActiveRadarrBlock::AutomaticallySearchMoviePrompt if matches_key!(confirm, key) => {
if key == DEFAULT_KEYBINDINGS.confirm.key =>
{
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::TriggerAutomaticSearch(self.extract_movie_id())); Some(RadarrEvent::TriggerAutomaticSearch(self.extract_movie_id()));
self.app.pop_navigation_stack(); 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 = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::UpdateAndScan(self.extract_movie_id())); Some(RadarrEvent::UpdateAndScan(self.extract_movie_id()));
self.app.pop_navigation_stack(); 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 = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DownloadRelease( self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DownloadRelease(
self.build_radarr_release_download_body(), 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] #[rstest]
fn test_movie_details_handler_is_not_ready_when_loading( fn test_movie_details_handler_is_not_ready_when_loading(
#[values( #[values(
+7 -4
View File
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::handlers::radarr_handlers::blocklist::BlocklistHandler; use crate::handlers::radarr_handlers::blocklist::BlocklistHandler;
use crate::handlers::radarr_handlers::collections::CollectionsHandler; use crate::handlers::radarr_handlers::collections::CollectionsHandler;
use crate::handlers::radarr_handlers::downloads::DownloadsHandler; 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::radarr_handlers::system::SystemHandler;
use crate::handlers::KeyEventHandler; use crate::handlers::KeyEventHandler;
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::{App, Key}; use crate::{matches_key, App, Key};
mod blocklist; mod blocklist;
mod collections; mod collections;
@@ -65,6 +64,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b
true true
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, 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) { pub fn handle_change_tab_left_right_keys(app: &mut App<'_>, key: Key) {
let key_ref = key; let key_ref = key;
match key_ref { 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.data.radarr_data.main_tabs.previous();
app.pop_and_push_navigation_stack(app.data.radarr_data.main_tabs.get_active_route()); 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.data.radarr_data.main_tabs.next();
app.pop_and_push_navigation_stack(app.data.radarr_data.main_tabs.get_active_route()); 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()); 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] #[rstest]
fn test_delegates_system_blocks_to_system_handler( fn test_delegates_system_blocks_to_system_handler(
#[values( #[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] #[test]
fn test_radarr_handler_is_ready() { fn test_radarr_handler_is_ready() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; 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::servarr_models::{AddRootFolderBody, RootFolder};
use crate::models::HorizontallyScrollableText; use crate::models::HorizontallyScrollableText;
use crate::network::radarr_network::RadarrEvent; 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)] #[cfg(test)]
#[path = "root_folders_handler_tests.rs"] #[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) ROOT_FOLDERS_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -195,10 +200,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'
let key = self.key; let key = self.key;
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::RootFolders => match self.key { ActiveRadarrBlock::RootFolders => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ if key == DEFAULT_KEYBINDINGS.add.key => { _ if matches_key!(add, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into()); .push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into());
@@ -215,7 +220,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'
) )
} }
ActiveRadarrBlock::DeleteRootFolderPrompt => { 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 = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::DeleteRootFolder(self.extract_root_folder_id())); Some(RadarrEvent::DeleteRootFolder(self.extract_root_folder_id()));
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -589,7 +590,7 @@ mod tests {
app.data.radarr_data.edit_root_folder = Some(HorizontallyScrollableText::default()); app.data.radarr_data.edit_root_folder = Some(HorizontallyScrollableText::default());
RootFoldersHandler::new( RootFoldersHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::AddRootFolderPrompt, ActiveRadarrBlock::AddRootFolderPrompt,
None, None,
@@ -598,7 +599,7 @@ mod tests {
assert_str_eq!( assert_str_eq!(
app.data.radarr_data.edit_root_folder.as_ref().unwrap().text, 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] #[test]
fn test_build_add_root_folder_body() { fn test_build_add_root_folder_body() {
let mut app = App::test_default(); let mut app = App::test_default();
+10 -6
View File
@@ -1,9 +1,9 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::radarr_handlers::system::system_details_handler::SystemDetailsHandler; use crate::handlers::radarr_handlers::system::system_details_handler::SystemDetailsHandler;
use crate::handlers::{handle_clear_errors, KeyEventHandler}; use crate::handlers::{handle_clear_errors, KeyEventHandler};
use crate::matches_key;
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::models::Scrollable; 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 SystemDetailsHandler::accepts(active_block) || active_block == ActiveRadarrBlock::System
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, 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 { if self.active_radarr_block == ActiveRadarrBlock::System {
let key = self.key; let key = self.key;
match self.key { match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ if key == DEFAULT_KEYBINDINGS.events.key => { _ if matches_key!(events, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::SystemQueuedEvents.into()); .push_navigation_stack(ActiveRadarrBlock::SystemQueuedEvents.into());
} }
_ if key == DEFAULT_KEYBINDINGS.logs.key => { _ if matches_key!(logs, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::SystemLogs.into()); .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()); .set_items(self.app.data.radarr_data.logs.items.to_vec());
self.app.data.radarr_data.log_details.scroll_to_bottom(); self.app.data.radarr_data.log_details.scroll_to_bottom();
} }
_ if key == DEFAULT_KEYBINDINGS.tasks.key => { _ if matches_key!(tasks, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::SystemTasks.into()); .push_navigation_stack(ActiveRadarrBlock::SystemTasks.into());
} }
_ if key == DEFAULT_KEYBINDINGS.update.key => { _ if matches_key!(update, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::SystemUpdates.into()); .push_navigation_stack(ActiveRadarrBlock::SystemUpdates.into());
@@ -1,7 +1,7 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::matches_key;
use crate::models::radarr_models::RadarrTaskName; use crate::models::radarr_models::RadarrTaskName;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS};
use crate::models::stateful_list::StatefulList; 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) SYSTEM_DETAILS_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -114,7 +118,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::SystemLogs => match self.key { ActiveRadarrBlock::SystemLogs => match self.key {
_ if key == DEFAULT_KEYBINDINGS.left.key => { _ if matches_key!(left, key) => {
self self
.app .app
.data .data
@@ -124,7 +128,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler
.iter() .iter()
.for_each(|log| log.scroll_right()); .for_each(|log| log.scroll_right());
} }
_ if key == DEFAULT_KEYBINDINGS.right.key => { _ if matches_key!(right, key) => {
self self
.app .app
.data .data
@@ -178,14 +182,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler
} }
fn handle_char_key_event(&mut self) { fn handle_char_key_event(&mut self) {
if SYSTEM_DETAILS_BLOCKS.contains(&self.active_radarr_block) if SYSTEM_DETAILS_BLOCKS.contains(&self.active_radarr_block) && matches_key!(refresh, self.key)
&& self.key == DEFAULT_KEYBINDINGS.refresh.key
{ {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
if self.active_radarr_block == ActiveRadarrBlock::SystemTaskStartConfirmPrompt 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 = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action =
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; 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] #[test]
fn test_extract_task_name() { fn test_extract_task_name() {
let mut app = App::test_default(); 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] #[test]
fn test_system_handler_is_not_ready_when_loading() { fn test_system_handler_is_not_ready_when_loading() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -4,6 +4,7 @@ mod tests {
use chrono::DateTime; use chrono::DateTime;
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; 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] #[test]
fn test_extract_blocklist_item_id() { fn test_extract_blocklist_item_id() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; 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::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; 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::sonarr_models::BlocklistItem;
use crate::models::stateful_table::SortOption; use crate::models::stateful_table::SortOption;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_table_events, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "blocklist_handler_tests.rs"] #[path = "blocklist_handler_tests.rs"]
@@ -51,6 +50,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a,
BLOCKLIST_BLOCKS.contains(&active_block) BLOCKLIST_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -143,10 +146,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a,
let key = self.key; let key = self.key;
match self.active_sonarr_block { match self.active_sonarr_block {
ActiveSonarrBlock::Blocklist => match self.key { ActiveSonarrBlock::Blocklist => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ if key == DEFAULT_KEYBINDINGS.clear.key => { _ if matches_key!(clear, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::BlocklistClearAllItemsPrompt.into()); .push_navigation_stack(ActiveSonarrBlock::BlocklistClearAllItemsPrompt.into());
@@ -154,7 +157,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a,
_ => (), _ => (),
}, },
ActiveSonarrBlock::DeleteBlocklistItemPrompt => { 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 = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteBlocklistItem( self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteBlocklistItem(
self.extract_blocklist_item_id(), self.extract_blocklist_item_id(),
@@ -164,7 +167,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a,
} }
} }
ActiveSonarrBlock::BlocklistClearAllItemsPrompt => { 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 = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::ClearBlocklist); self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::ClearBlocklist);
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; 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] #[test]
fn test_extract_download_id() { fn test_extract_download_id() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,13 +1,12 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; 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::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DOWNLOADS_BLOCKS}; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DOWNLOADS_BLOCKS};
use crate::models::sonarr_models::DownloadRecord; use crate::models::sonarr_models::DownloadRecord;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_table_events, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "downloads_handler_tests.rs"] #[path = "downloads_handler_tests.rs"]
@@ -47,6 +46,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DownloadsHandler<'a,
DOWNLOADS_BLOCKS.contains(&active_block) DOWNLOADS_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -130,18 +133,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DownloadsHandler<'a,
let key = self.key; let key = self.key;
match self.active_sonarr_block { match self.active_sonarr_block {
ActiveSonarrBlock::Downloads => match self.key { ActiveSonarrBlock::Downloads => match self.key {
_ if key == DEFAULT_KEYBINDINGS.update.key => { _ if matches_key!(update, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::UpdateDownloadsPrompt.into()); .push_navigation_stack(ActiveSonarrBlock::UpdateDownloadsPrompt.into());
} }
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ => (), _ => (),
}, },
ActiveSonarrBlock::DeleteDownloadPrompt => { 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 = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action =
Some(SonarrEvent::DeleteDownload(self.extract_download_id())); Some(SonarrEvent::DeleteDownload(self.extract_download_id()));
@@ -150,7 +153,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DownloadsHandler<'a,
} }
} }
ActiveSonarrBlock::UpdateDownloadsPrompt => { 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 = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::UpdateDownloads); self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::UpdateDownloads);
@@ -4,6 +4,7 @@ mod tests {
use chrono::DateTime; use chrono::DateTime;
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; 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] #[test]
fn test_history_handler_not_ready_when_loading() { fn test_history_handler_not_ready_when_loading() {
let mut app = App::test_default(); let mut app = App::test_default();
+6 -3
View File
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; 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::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_clear_errors, KeyEventHandler}; 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::servarr_models::Language;
use crate::models::sonarr_models::SonarrHistoryItem; use crate::models::sonarr_models::SonarrHistoryItem;
use crate::models::stateful_table::SortOption; use crate::models::stateful_table::SortOption;
use crate::{handle_table_events, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "history_handler_tests.rs"] #[path = "history_handler_tests.rs"]
@@ -52,6 +51,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for HistoryHandler<'a, '
HISTORY_BLOCKS.contains(&active_block) HISTORY_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -110,7 +113,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for HistoryHandler<'a, '
let key = self.key; let key = self.key;
if self.active_sonarr_block == ActiveSonarrBlock::History { if self.active_sonarr_block == ActiveSonarrBlock::History {
match self.key { match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ => (), _ => (),
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; 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_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_INDEXER_BLOCKS};
use crate::models::servarr_models::EditIndexerParams; use crate::models::servarr_models::EditIndexerParams;
use crate::network::sonarr_network::SonarrEvent; 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)] #[cfg(test)]
#[path = "edit_indexer_handler_tests.rs"] #[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) EDIT_INDEXER_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -503,7 +508,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditIndexerHandler<'
ActiveSonarrBlock::EditIndexerPrompt => { ActiveSonarrBlock::EditIndexerPrompt => {
if self.app.data.sonarr_data.selected_block.get_active_block() if self.app.data.sonarr_data.selected_block.get_active_block()
== ActiveSonarrBlock::EditIndexerConfirmPrompt == 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 = true;
self.app.data.sonarr_data.prompt_confirm_action = 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_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_INDEXER_BLOCKS};
use crate::models::servarr_models::EditIndexerParams; use crate::models::servarr_models::EditIndexerParams;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
mod test_handle_scroll_up_and_down { mod test_handle_scroll_up_and_down {
@@ -1597,7 +1598,7 @@ mod tests {
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new( EditIndexerHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveSonarrBlock::EditIndexerNameInput, ActiveSonarrBlock::EditIndexerNameInput,
None, None,
@@ -1613,7 +1614,7 @@ mod tests {
.unwrap() .unwrap()
.name .name
.text, .text,
"h" "a"
); );
} }
@@ -1624,7 +1625,7 @@ mod tests {
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new( EditIndexerHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveSonarrBlock::EditIndexerUrlInput, ActiveSonarrBlock::EditIndexerUrlInput,
None, None,
@@ -1640,7 +1641,7 @@ mod tests {
.unwrap() .unwrap()
.url .url
.text, .text,
"h" "a"
); );
} }
@@ -1651,7 +1652,7 @@ mod tests {
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new( EditIndexerHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveSonarrBlock::EditIndexerApiKeyInput, ActiveSonarrBlock::EditIndexerApiKeyInput,
None, None,
@@ -1667,7 +1668,7 @@ mod tests {
.unwrap() .unwrap()
.api_key .api_key
.text, .text,
"h" "a"
); );
} }
@@ -1678,7 +1679,7 @@ mod tests {
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new( EditIndexerHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveSonarrBlock::EditIndexerSeedRatioInput, ActiveSonarrBlock::EditIndexerSeedRatioInput,
None, None,
@@ -1694,7 +1695,7 @@ mod tests {
.unwrap() .unwrap()
.seed_ratio .seed_ratio
.text, .text,
"h" "a"
); );
} }
@@ -1705,7 +1706,7 @@ mod tests {
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new( EditIndexerHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveSonarrBlock::EditIndexerTagsInput, ActiveSonarrBlock::EditIndexerTagsInput,
None, None,
@@ -1721,7 +1722,7 @@ mod tests {
.unwrap() .unwrap()
.tags .tags
.text, .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] #[test]
fn test_build_edit_indexer_params() { fn test_build_edit_indexer_params() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,13 +1,12 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_prompt_left_right_keys;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::servarr_data::sonarr::sonarr_data::{ use crate::models::servarr_data::sonarr::sonarr_data::{
ActiveSonarrBlock, INDEXER_SETTINGS_BLOCKS, ActiveSonarrBlock, INDEXER_SETTINGS_BLOCKS,
}; };
use crate::models::sonarr_models::IndexerSettings; use crate::models::sonarr_models::IndexerSettings;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_prompt_left_right_keys, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "edit_indexer_settings_handler_tests.rs"] #[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) INDEXER_SETTINGS_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, 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 if self.active_sonarr_block == ActiveSonarrBlock::AllIndexerSettingsPrompt
&& self.app.data.sonarr_data.selected_block.get_active_block() && self.app.data.sonarr_data.selected_block.get_active_block()
== ActiveSonarrBlock::IndexerSettingsConfirmPrompt == 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 = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::EditAllIndexerSettings( self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::EditAllIndexerSettings(
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; 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] #[test]
fn test_build_edit_indexer_settings_params() { fn test_build_edit_indexer_settings_params() {
let mut app = App::test_default(); 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] #[test]
fn test_extract_indexer_id() { fn test_extract_indexer_id() {
let mut app = App::test_default(); let mut app = App::test_default();
+10 -7
View File
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; 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::handle_change_tab_left_right_keys;
use crate::handlers::sonarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler; use crate::handlers::sonarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler;
use crate::handlers::sonarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler; 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::servarr_models::Indexer;
use crate::models::BlockSelectionState; use crate::models::BlockSelectionState;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_table_events, matches_key};
mod edit_indexer_handler; mod edit_indexer_handler;
mod edit_indexer_settings_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) || INDEXERS_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -168,20 +171,20 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexersHandler<'a,
let key = self.key; let key = self.key;
match self.active_sonarr_block { match self.active_sonarr_block {
ActiveSonarrBlock::Indexers => match self.key { ActiveSonarrBlock::Indexers => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ if key == DEFAULT_KEYBINDINGS.test.key => { _ if matches_key!(test, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::TestIndexer.into()); .push_navigation_stack(ActiveSonarrBlock::TestIndexer.into());
} }
_ if key == DEFAULT_KEYBINDINGS.test_all.key => { _ if matches_key!(test_all, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::TestAllIndexers.into()); .push_navigation_stack(ActiveSonarrBlock::TestAllIndexers.into());
} }
_ if key == DEFAULT_KEYBINDINGS.settings.key => { _ if matches_key!(settings, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::AllIndexerSettingsPrompt.into()); .push_navigation_stack(ActiveSonarrBlock::AllIndexerSettingsPrompt.into());
@@ -191,7 +194,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexersHandler<'a,
_ => (), _ => (),
}, },
ActiveSonarrBlock::DeleteIndexerPrompt => { 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 = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action =
Some(SonarrEvent::DeleteIndexer(self.extract_indexer_id())); Some(SonarrEvent::DeleteIndexer(self.extract_indexer_id()));
@@ -48,6 +48,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for TestAllIndexersHandl
active_block == ActiveSonarrBlock::TestAllIndexers active_block == ActiveSonarrBlock::TestAllIndexers
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -7,6 +7,7 @@ mod tests {
use crate::models::servarr_data::modals::IndexerTestResultModalItem; use crate::models::servarr_data::modals::IndexerTestResultModalItem;
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
use crate::models::stateful_table::StatefulTable; use crate::models::stateful_table::StatefulTable;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
mod test_handle_esc { 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] #[test]
fn test_test_all_indexers_handler_is_not_ready_when_loading() { fn test_test_all_indexers_handler_is_not_ready_when_loading() {
let mut app = App::test_default(); 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::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::servarr_data::sonarr::modals::AddSeriesModal; 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::stateful_table::StatefulTable;
use crate::models::{BlockSelectionState, Scrollable}; use crate::models::{BlockSelectionState, Scrollable};
use crate::network::sonarr_network::SonarrEvent; 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)] #[cfg(test)]
#[path = "add_series_handler_tests.rs"] #[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) ADD_SERIES_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -609,7 +614,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
ActiveSonarrBlock::AddSeriesPrompt => { ActiveSonarrBlock::AddSeriesPrompt => {
if self.app.data.sonarr_data.selected_block.get_active_block() if self.app.data.sonarr_data.selected_block.get_active_block()
== ActiveSonarrBlock::AddSeriesConfirmPrompt == 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 = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action =
@@ -2,6 +2,7 @@
mod tests { mod tests {
use bimap::BiMap; use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -1570,7 +1571,7 @@ mod tests {
app.data.sonarr_data.add_series_search = Some(HorizontallyScrollableText::default()); app.data.sonarr_data.add_series_search = Some(HorizontallyScrollableText::default());
AddSeriesHandler::new( AddSeriesHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveSonarrBlock::AddSeriesSearchInput, ActiveSonarrBlock::AddSeriesSearchInput,
None, None,
@@ -1585,7 +1586,7 @@ mod tests {
.as_ref() .as_ref()
.unwrap() .unwrap()
.text, .text,
"h" "a"
); );
} }
@@ -1596,7 +1597,7 @@ mod tests {
app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default()); app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default());
AddSeriesHandler::new( AddSeriesHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveSonarrBlock::AddSeriesTagsInput, ActiveSonarrBlock::AddSeriesTagsInput,
None, None,
@@ -1612,7 +1613,7 @@ mod tests {
.unwrap() .unwrap()
.tags .tags
.text, .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] #[test]
fn test_add_series_search_no_panic_on_none_search_result() { fn test_add_series_search_no_panic_on_none_search_result() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,9 +1,10 @@
use crate::models::sonarr_models::DeleteSeriesParams; use crate::models::sonarr_models::DeleteSeriesParams;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{ use crate::{
app::{key_binding::DEFAULT_KEYBINDINGS, App}, app::App,
event::Key, event::Key,
handlers::{handle_prompt_toggle, KeyEventHandler}, handlers::{handle_prompt_toggle, KeyEventHandler},
matches_key,
models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DELETE_SERIES_BLOCKS}, 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) DELETE_SERIES_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, 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 if self.active_sonarr_block == ActiveSonarrBlock::DeleteSeriesPrompt
&& self.app.data.sonarr_data.selected_block.get_active_block() && self.app.data.sonarr_data.selected_block.get_active_block()
== ActiveSonarrBlock::DeleteSeriesConfirmPrompt == 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 = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action =
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; 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] #[test]
fn test_build_delete_series_params() { fn test_build_delete_series_params() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; 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::sonarr_models::EditSeriesParams;
use crate::models::Scrollable; use crate::models::Scrollable;
use crate::network::sonarr_network::SonarrEvent; 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)] #[cfg(test)]
#[path = "edit_series_handler_tests.rs"] #[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) EDIT_SERIES_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -450,7 +453,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditSeriesHandler<'a
ActiveSonarrBlock::EditSeriesPrompt => { ActiveSonarrBlock::EditSeriesPrompt => {
if self.app.data.sonarr_data.selected_block.get_active_block() if self.app.data.sonarr_data.selected_block.get_active_block()
== ActiveSonarrBlock::EditSeriesConfirmPrompt == 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 = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action =
@@ -2,6 +2,7 @@
mod tests { mod tests {
use bimap::BiMap; use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -1243,7 +1244,7 @@ mod tests {
app.data.sonarr_data.edit_series_modal = Some(EditSeriesModal::default()); app.data.sonarr_data.edit_series_modal = Some(EditSeriesModal::default());
EditSeriesHandler::new( EditSeriesHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveSonarrBlock::EditSeriesPathInput, ActiveSonarrBlock::EditSeriesPathInput,
None, None,
@@ -1259,7 +1260,7 @@ mod tests {
.unwrap() .unwrap()
.path .path
.text, .text,
"h" "a"
); );
} }
@@ -1270,7 +1271,7 @@ mod tests {
app.data.sonarr_data.edit_series_modal = Some(EditSeriesModal::default()); app.data.sonarr_data.edit_series_modal = Some(EditSeriesModal::default());
EditSeriesHandler::new( EditSeriesHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveSonarrBlock::EditSeriesTagsInput, ActiveSonarrBlock::EditSeriesTagsInput,
None, None,
@@ -1286,7 +1287,7 @@ mod tests {
.unwrap() .unwrap()
.tags .tags
.text, .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] #[test]
fn test_build_edit_series_params() { fn test_build_edit_series_params() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,13 +1,12 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::sonarr_handlers::library::season_details_handler::releases_sorting_options; use crate::handlers::sonarr_handlers::library::season_details_handler::releases_sorting_options;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EPISODE_DETAILS_BLOCKS}; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EPISODE_DETAILS_BLOCKS};
use crate::models::sonarr_models::{SonarrHistoryItem, SonarrRelease, SonarrReleaseDownloadBody}; use crate::models::sonarr_models::{SonarrHistoryItem, SonarrRelease, SonarrReleaseDownloadBody};
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_table_events, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "episode_details_handler_tests.rs"] #[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) EPISODE_DETAILS_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -142,7 +145,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle
| ActiveSonarrBlock::EpisodeHistory | ActiveSonarrBlock::EpisodeHistory
| ActiveSonarrBlock::EpisodeFile | ActiveSonarrBlock::EpisodeFile
| ActiveSonarrBlock::ManualEpisodeSearch => match self.key { | ActiveSonarrBlock::ManualEpisodeSearch => match self.key {
_ if self.key == DEFAULT_KEYBINDINGS.left.key => { _ if matches_key!(left, self.key) => {
self self
.app .app
.data .data
@@ -170,7 +173,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle
.get_active_route(), .get_active_route(),
); );
} }
_ if self.key == DEFAULT_KEYBINDINGS.right.key => { _ if matches_key!(right, self.key) => {
self self
.app .app
.data .data
@@ -306,21 +309,19 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle
| ActiveSonarrBlock::EpisodeHistory | ActiveSonarrBlock::EpisodeHistory
| ActiveSonarrBlock::EpisodeFile | ActiveSonarrBlock::EpisodeFile
| ActiveSonarrBlock::ManualEpisodeSearch => match self.key { | ActiveSonarrBlock::ManualEpisodeSearch => match self.key {
_ if self.key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, self.key) => {
self self
.app .app
.pop_and_push_navigation_stack(self.active_sonarr_block.into()); .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 self
.app .app
.push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchEpisodePrompt.into()); .push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchEpisodePrompt.into());
} }
_ => (), _ => (),
}, },
ActiveSonarrBlock::AutomaticallySearchEpisodePrompt ActiveSonarrBlock::AutomaticallySearchEpisodePrompt if matches_key!(confirm, key) => {
if key == DEFAULT_KEYBINDINGS.confirm.key =>
{
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some( self.app.data.sonarr_data.prompt_confirm_action = Some(
SonarrEvent::TriggerAutomaticEpisodeSearch(self.extract_episode_id()), SonarrEvent::TriggerAutomaticEpisodeSearch(self.extract_episode_id()),
@@ -328,9 +329,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt if matches_key!(confirm, key) => {
if key == DEFAULT_KEYBINDINGS.confirm.key =>
{
if self.app.data.sonarr_data.prompt_confirm { if self.app.data.sonarr_data.prompt_confirm {
let SonarrRelease { let SonarrRelease {
guid, indexer_id, .. 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] #[test]
fn test_extract_episode_id() { fn test_extract_episode_id() {
let mut app = App::test_default(); 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] #[test]
fn test_library_handler_not_ready_when_loading() { fn test_library_handler_not_ready_when_loading() {
let mut app = App::test_default(); let mut app = App::test_default();
+10 -6
View File
@@ -8,6 +8,7 @@ use crate::{
event::Key, event::Key,
handle_table_events, handle_table_events,
handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}, handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler},
matches_key,
models::{ models::{
servarr_data::sonarr::sonarr_data::{ servarr_data::sonarr::sonarr_data::{
ActiveSonarrBlock, DELETE_SERIES_SELECTION_BLOCKS, EDIT_SERIES_SELECTION_BLOCKS, ActiveSonarrBlock, DELETE_SERIES_SELECTION_BLOCKS, EDIT_SERIES_SELECTION_BLOCKS,
@@ -21,7 +22,6 @@ use crate::{
}; };
use super::handle_change_tab_left_right_keys; 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::episode_details_handler::EpisodeDetailsHandler;
use crate::handlers::sonarr_handlers::library::season_details_handler::SeasonDetailsHandler; use crate::handlers::sonarr_handlers::library::season_details_handler::SeasonDetailsHandler;
use crate::handlers::sonarr_handlers::library::series_details_handler::SeriesDetailsHandler; 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) || LIBRARY_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -182,7 +186,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, '
let key = self.key; let key = self.key;
match self.active_sonarr_block { match self.active_sonarr_block {
ActiveSonarrBlock::Series => match self.key { ActiveSonarrBlock::Series => match self.key {
_ if key == DEFAULT_KEYBINDINGS.edit.key => { _ if matches_key!(edit, key) => {
self.app.push_navigation_stack( self.app.push_navigation_stack(
( (
ActiveSonarrBlock::EditSeriesPrompt, ActiveSonarrBlock::EditSeriesPrompt,
@@ -194,25 +198,25 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, '
self.app.data.sonarr_data.selected_block = self.app.data.sonarr_data.selected_block =
BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS);
} }
_ if key == DEFAULT_KEYBINDINGS.add.key => { _ if matches_key!(add, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchInput.into()); .push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchInput.into());
self.app.data.sonarr_data.add_series_search = Some(HorizontallyScrollableText::default()); self.app.data.sonarr_data.add_series_search = Some(HorizontallyScrollableText::default());
self.app.should_ignore_quit_key = true; self.app.should_ignore_quit_key = true;
} }
_ if key == DEFAULT_KEYBINDINGS.update.key => { _ if matches_key!(update, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::UpdateAllSeriesPrompt.into()); .push_navigation_stack(ActiveSonarrBlock::UpdateAllSeriesPrompt.into());
} }
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ => (), _ => (),
}, },
ActiveSonarrBlock::UpdateAllSeriesPrompt => { 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 = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::UpdateAllSeries); 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::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::sonarr_handlers::history::history_sorting_options; use crate::handlers::sonarr_handlers::history::history_sorting_options;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -12,6 +10,7 @@ use crate::models::sonarr_models::{
}; };
use crate::models::stateful_table::SortOption; use crate::models::stateful_table::SortOption;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_table_events, matches_key};
use serde_json::Number; use serde_json::Number;
#[cfg(test)] #[cfg(test)]
@@ -140,6 +139,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
SEASON_DETAILS_BLOCKS.contains(&active_block) SEASON_DETAILS_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -193,7 +196,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
ActiveSonarrBlock::SeasonDetails ActiveSonarrBlock::SeasonDetails
| ActiveSonarrBlock::SeasonHistory | ActiveSonarrBlock::SeasonHistory
| ActiveSonarrBlock::ManualSeasonSearch => match self.key { | ActiveSonarrBlock::ManualSeasonSearch => match self.key {
_ if self.key == DEFAULT_KEYBINDINGS.left.key => { _ if matches_key!(left, self.key) => {
self self
.app .app
.data .data
@@ -215,7 +218,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
.get_active_route(), .get_active_route(),
); );
} }
_ if self.key == DEFAULT_KEYBINDINGS.right.key => { _ if matches_key!(right, self.key) => {
self self
.app .app
.data .data
@@ -378,7 +381,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
fn handle_char_key_event(&mut self) { fn handle_char_key_event(&mut self) {
let key = self.key; let key = self.key;
match self.active_sonarr_block { 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 = true;
self.app.data.sonarr_data.prompt_confirm_action = Some( self.app.data.sonarr_data.prompt_confirm_action = Some(
SonarrEvent::ToggleEpisodeMonitoring(self.extract_episode_id()), SonarrEvent::ToggleEpisodeMonitoring(self.extract_episode_id()),
@@ -391,21 +394,19 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
ActiveSonarrBlock::SeasonDetails ActiveSonarrBlock::SeasonDetails
| ActiveSonarrBlock::SeasonHistory | ActiveSonarrBlock::SeasonHistory
| ActiveSonarrBlock::ManualSeasonSearch => match self.key { | ActiveSonarrBlock::ManualSeasonSearch => match self.key {
_ if self.key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, self.key) => {
self self
.app .app
.pop_and_push_navigation_stack(self.active_sonarr_block.into()); .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 self
.app .app
.push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchSeasonPrompt.into()); .push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchSeasonPrompt.into());
} }
_ => (), _ => (),
}, },
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt ActiveSonarrBlock::AutomaticallySearchSeasonPrompt if matches_key!(confirm, key) => {
if key == DEFAULT_KEYBINDINGS.confirm.key =>
{
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some( self.app.data.sonarr_data.prompt_confirm_action = Some(
SonarrEvent::TriggerAutomaticSeasonSearch(self.extract_series_id_season_number_tuple()), 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(); 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 = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteEpisodeFile( self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteEpisodeFile(
self.extract_episode_file_id(), self.extract_episode_file_id(),
@@ -421,9 +422,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt if matches_key!(confirm, key) => {
if key == DEFAULT_KEYBINDINGS.confirm.key =>
{
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
let SonarrRelease { let SonarrRelease {
guid, indexer_id, .. 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] #[test]
fn test_extract_episode_file_id() { fn test_extract_episode_file_id() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::sonarr_handlers::history::history_sorting_options; use crate::handlers::sonarr_handlers::history::history_sorting_options;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; 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::sonarr_models::{Season, SonarrHistoryItem};
use crate::models::BlockSelectionState; use crate::models::BlockSelectionState;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_table_events, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "series_details_handler_tests.rs"] #[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) SERIES_DETAILS_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, 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) { fn handle_left_right_action(&mut self) {
match self.active_sonarr_block { match self.active_sonarr_block {
ActiveSonarrBlock::SeriesDetails | ActiveSonarrBlock::SeriesHistory => match self.key { 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.data.sonarr_data.series_info_tabs.previous();
self.app.pop_and_push_navigation_stack( self.app.pop_and_push_navigation_stack(
self self
@@ -141,7 +144,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
.get_active_route(), .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.data.sonarr_data.series_info_tabs.next();
self.app.pop_and_push_navigation_stack( self.app.pop_and_push_navigation_stack(
self self
@@ -250,20 +253,20 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
let key = self.key; let key = self.key;
match self.active_sonarr_block { match self.active_sonarr_block {
ActiveSonarrBlock::SeriesDetails => match self.key { ActiveSonarrBlock::SeriesDetails => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => self _ if matches_key!(refresh, key) => self
.app .app
.pop_and_push_navigation_stack(self.active_sonarr_block.into()), .pop_and_push_navigation_stack(self.active_sonarr_block.into()),
_ if key == DEFAULT_KEYBINDINGS.auto_search.key => { _ if matches_key!(auto_search, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchSeriesPrompt.into()); .push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchSeriesPrompt.into());
} }
_ if key == DEFAULT_KEYBINDINGS.update.key => { _ if matches_key!(update, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::UpdateAndScanSeriesPrompt.into()); .push_navigation_stack(ActiveSonarrBlock::UpdateAndScanSeriesPrompt.into());
} }
_ if key == DEFAULT_KEYBINDINGS.edit.key => { _ if matches_key!(edit, key) => {
self.app.push_navigation_stack( self.app.push_navigation_stack(
( (
ActiveSonarrBlock::EditSeriesPrompt, ActiveSonarrBlock::EditSeriesPrompt,
@@ -275,7 +278,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
self.app.data.sonarr_data.selected_block = self.app.data.sonarr_data.selected_block =
BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS); 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 = true;
self.app.data.sonarr_data.prompt_confirm_action = Some( self.app.data.sonarr_data.prompt_confirm_action = Some(
SonarrEvent::ToggleSeasonMonitoring(self.extract_series_id_season_number_tuple()), 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 { ActiveSonarrBlock::SeriesHistory => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => self _ if matches_key!(refresh, key) => self
.app .app
.pop_and_push_navigation_stack(self.active_sonarr_block.into()), .pop_and_push_navigation_stack(self.active_sonarr_block.into()),
_ if key == DEFAULT_KEYBINDINGS.auto_search.key => { _ if matches_key!(auto_search, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchSeriesPrompt.into()); .push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchSeriesPrompt.into());
} }
_ if key == DEFAULT_KEYBINDINGS.edit.key => { _ if matches_key!(edit, key) => {
self.app.push_navigation_stack( self.app.push_navigation_stack(
( (
ActiveSonarrBlock::EditSeriesPrompt, ActiveSonarrBlock::EditSeriesPrompt,
@@ -308,7 +311,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
self.app.data.sonarr_data.selected_block = self.app.data.sonarr_data.selected_block =
BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS);
} }
_ if key == DEFAULT_KEYBINDINGS.update.key => { _ if matches_key!(update, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::UpdateAndScanSeriesPrompt.into()); .push_navigation_stack(ActiveSonarrBlock::UpdateAndScanSeriesPrompt.into());
@@ -316,7 +319,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
_ => (), _ => (),
}, },
ActiveSonarrBlock::AutomaticallySearchSeriesPrompt => { 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 = true;
self.app.data.sonarr_data.prompt_confirm_action = Some( self.app.data.sonarr_data.prompt_confirm_action = Some(
SonarrEvent::TriggerAutomaticSeriesSearch(self.extract_series_id()), 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] #[test]
fn test_extract_series_id_season_number_tuple() { fn test_extract_series_id_season_number_tuple() {
let mut app = App::test_default(); let mut app = App::test_default();
+7 -5
View File
@@ -7,9 +7,7 @@ use root_folders::RootFoldersHandler;
use system::SystemHandler; use system::SystemHandler;
use crate::{ use crate::{
app::{key_binding::DEFAULT_KEYBINDINGS, App}, app::App, event::Key, matches_key, models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock,
event::Key,
models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock,
}; };
use super::KeyEventHandler; use super::KeyEventHandler;
@@ -69,6 +67,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SonarrHandler<'a, 'b
true true
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, 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) { pub fn handle_change_tab_left_right_keys(app: &mut App<'_>, key: Key) {
let key_ref = key; let key_ref = key;
match key_ref { 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.data.sonarr_data.main_tabs.previous();
app.pop_and_push_navigation_stack(app.data.sonarr_data.main_tabs.get_active_route()); 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.data.sonarr_data.main_tabs.next();
app.pop_and_push_navigation_stack(app.data.sonarr_data.main_tabs.get_active_route()); 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::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys; 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::servarr_models::{AddRootFolderBody, RootFolder};
use crate::models::HorizontallyScrollableText; use crate::models::HorizontallyScrollableText;
use crate::network::sonarr_network::SonarrEvent; 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)] #[cfg(test)]
#[path = "root_folders_handler_tests.rs"] #[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) ROOT_FOLDERS_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -193,10 +198,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<'
let key = self.key; let key = self.key;
match self.active_sonarr_block { match self.active_sonarr_block {
ActiveSonarrBlock::RootFolders => match self.key { ActiveSonarrBlock::RootFolders => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ if key == DEFAULT_KEYBINDINGS.add.key => { _ if matches_key!(add, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::AddRootFolderPrompt.into()); .push_navigation_stack(ActiveSonarrBlock::AddRootFolderPrompt.into());
@@ -213,7 +218,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<'
) )
} }
ActiveSonarrBlock::DeleteRootFolderPrompt => { 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 = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action =
Some(SonarrEvent::DeleteRootFolder(self.extract_root_folder_id())); Some(SonarrEvent::DeleteRootFolder(self.extract_root_folder_id()));
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -600,7 +601,7 @@ mod tests {
app.data.sonarr_data.edit_root_folder = Some(HorizontallyScrollableText::default()); app.data.sonarr_data.edit_root_folder = Some(HorizontallyScrollableText::default());
RootFoldersHandler::new( RootFoldersHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveSonarrBlock::AddRootFolderPrompt, ActiveSonarrBlock::AddRootFolderPrompt,
None, None,
@@ -609,7 +610,7 @@ mod tests {
assert_str_eq!( assert_str_eq!(
app.data.sonarr_data.edit_root_folder.as_ref().unwrap().text, 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] #[test]
fn test_extract_root_folder_id() { fn test_extract_root_folder_id() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -191,7 +191,7 @@ pub(in crate::handlers::sonarr_handlers) mod utils {
title: "Test Download Title".to_owned(), title: "Test Download Title".to_owned(),
status: DownloadStatus::Downloading, status: DownloadStatus::Downloading,
id: 1, id: 1,
episode_id: 1, episode_id: Some(Number::from(1i64)),
size: 3543348019f64, size: 3543348019f64,
sizeleft: 1771674009f64, sizeleft: 1771674009f64,
output_path: Some(HorizontallyScrollableText::from( output_path: Some(HorizontallyScrollableText::from(
@@ -327,7 +327,7 @@ pub(in crate::handlers::sonarr_handlers) mod utils {
title: None, title: None,
season_number: 1, season_number: 1,
monitored: true, monitored: true,
statistics: season_statistics(), statistics: Some(season_statistics()),
} }
} }
@@ -9,6 +9,7 @@ mod tests {
use crate::test_handler_delegation; use crate::test_handler_delegation;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
use strum::IntoEnumIterator;
#[rstest] #[rstest]
#[case(0, ActiveSonarrBlock::System, ActiveSonarrBlock::Downloads)] #[case(0, ActiveSonarrBlock::System, ActiveSonarrBlock::Downloads)]
@@ -45,6 +46,77 @@ mod tests {
assert_eq!(app.get_current_route(), right_block.into()); 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] #[rstest]
fn test_delegates_library_blocks_to_library_handler( fn test_delegates_library_blocks_to_library_handler(
#[values( #[values(
@@ -59,10 +131,10 @@ mod tests {
ActiveSonarrBlock::AddSeriesSelectRootFolder, ActiveSonarrBlock::AddSeriesSelectRootFolder,
ActiveSonarrBlock::AddSeriesSelectSeriesType, ActiveSonarrBlock::AddSeriesSelectSeriesType,
ActiveSonarrBlock::AddSeriesTagsInput, ActiveSonarrBlock::AddSeriesTagsInput,
// ActiveSonarrBlock::AutomaticallySearchEpisodePrompt, ActiveSonarrBlock::AutomaticallySearchEpisodePrompt,
// ActiveSonarrBlock::AutomaticallySearchSeasonPrompt, ActiveSonarrBlock::AutomaticallySearchSeasonPrompt,
// ActiveSonarrBlock::AutomaticallySearchSeriesPrompt, ActiveSonarrBlock::AutomaticallySearchSeriesPrompt,
// ActiveSonarrBlock::DeleteEpisodeFilePrompt, ActiveSonarrBlock::DeleteEpisodeFilePrompt,
ActiveSonarrBlock::DeleteSeriesPrompt, ActiveSonarrBlock::DeleteSeriesPrompt,
ActiveSonarrBlock::EditSeriesPrompt, ActiveSonarrBlock::EditSeriesPrompt,
ActiveSonarrBlock::EditSeriesPathInput, ActiveSonarrBlock::EditSeriesPathInput,
@@ -70,39 +142,36 @@ mod tests {
ActiveSonarrBlock::EditSeriesSelectQualityProfile, ActiveSonarrBlock::EditSeriesSelectQualityProfile,
ActiveSonarrBlock::EditSeriesSelectLanguageProfile, ActiveSonarrBlock::EditSeriesSelectLanguageProfile,
ActiveSonarrBlock::EditSeriesTagsInput, ActiveSonarrBlock::EditSeriesTagsInput,
// ActiveSonarrBlock::EpisodeDetails, ActiveSonarrBlock::EpisodeDetails,
// ActiveSonarrBlock::EpisodeFile, ActiveSonarrBlock::EpisodeFile,
// ActiveSonarrBlock::EpisodeHistory, ActiveSonarrBlock::EpisodeHistory,
// ActiveSonarrBlock::EpisodesSortPrompt,
// ActiveSonarrBlock::FilterEpisodes,
// ActiveSonarrBlock::FilterEpisodesError,
ActiveSonarrBlock::FilterSeries, ActiveSonarrBlock::FilterSeries,
ActiveSonarrBlock::FilterSeriesError, ActiveSonarrBlock::FilterSeriesError,
// ActiveSonarrBlock::FilterSeriesHistory, ActiveSonarrBlock::FilterSeriesHistory,
// ActiveSonarrBlock::FilterSeriesHistoryError, ActiveSonarrBlock::FilterSeriesHistoryError,
// ActiveSonarrBlock::ManualEpisodeSearch, ActiveSonarrBlock::ManualEpisodeSearch,
// ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt, ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt,
// ActiveSonarrBlock::ManualEpisodeSearchSortPrompt, ActiveSonarrBlock::ManualEpisodeSearchSortPrompt,
// ActiveSonarrBlock::ManualSeasonSearch, ActiveSonarrBlock::ManualSeasonSearch,
// ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt, ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt,
// ActiveSonarrBlock::ManualSeasonSearchSortPrompt, ActiveSonarrBlock::ManualSeasonSearchSortPrompt,
// ActiveSonarrBlock::SearchEpisodes, ActiveSonarrBlock::SearchEpisodes,
// ActiveSonarrBlock::SearchEpisodesError, ActiveSonarrBlock::SearchEpisodesError,
// ActiveSonarrBlock::SearchSeason, ActiveSonarrBlock::SearchSeason,
// ActiveSonarrBlock::SearchSeasonError, ActiveSonarrBlock::SearchSeasonError,
ActiveSonarrBlock::SearchSeries, ActiveSonarrBlock::SearchSeries,
ActiveSonarrBlock::SearchSeriesError, ActiveSonarrBlock::SearchSeriesError,
// ActiveSonarrBlock::SearchSeriesHistory, ActiveSonarrBlock::SearchSeriesHistory,
// ActiveSonarrBlock::SearchSeriesHistoryError, ActiveSonarrBlock::SearchSeriesHistoryError,
// ActiveSonarrBlock::SeasonDetails, ActiveSonarrBlock::SeasonDetails,
ActiveSonarrBlock::Series, ActiveSonarrBlock::Series,
// ActiveSonarrBlock::SeriesDetails, ActiveSonarrBlock::SeriesDetails,
// ActiveSonarrBlock::SeriesHistory, ActiveSonarrBlock::SeriesHistory,
// ActiveSonarrBlock::SeriesHistorySortPrompt, ActiveSonarrBlock::SeriesHistorySortPrompt,
ActiveSonarrBlock::SeriesSortPrompt, ActiveSonarrBlock::SeriesSortPrompt,
ActiveSonarrBlock::UpdateAllSeriesPrompt, ActiveSonarrBlock::UpdateAllSeriesPrompt,
// ActiveSonarrBlock::UpdateAndScanSeriesPrompt ActiveSonarrBlock::UpdateAndScanSeriesPrompt,
// ActiveSonarrBlock::SeriesHistoryDetails, ActiveSonarrBlock::SeriesHistoryDetails
)] )]
active_sonarr_block: ActiveSonarrBlock, active_sonarr_block: ActiveSonarrBlock,
) { ) {
@@ -222,4 +291,42 @@ mod tests {
active_sonarr_block 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());
}
} }
+10 -6
View File
@@ -1,9 +1,9 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::sonarr_handlers::system::system_details_handler::SystemDetailsHandler; use crate::handlers::sonarr_handlers::system::system_details_handler::SystemDetailsHandler;
use crate::handlers::{handle_clear_errors, KeyEventHandler}; use crate::handlers::{handle_clear_errors, KeyEventHandler};
use crate::matches_key;
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
use crate::models::Scrollable; 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 SystemDetailsHandler::accepts(active_block) || active_block == ActiveSonarrBlock::System
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, 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 { if self.active_sonarr_block == ActiveSonarrBlock::System {
let key = self.key; let key = self.key;
match self.key { match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ if key == DEFAULT_KEYBINDINGS.events.key => { _ if matches_key!(events, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::SystemQueuedEvents.into()); .push_navigation_stack(ActiveSonarrBlock::SystemQueuedEvents.into());
} }
_ if key == DEFAULT_KEYBINDINGS.logs.key => { _ if matches_key!(logs, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::SystemLogs.into()); .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()); .set_items(self.app.data.sonarr_data.logs.items.to_vec());
self.app.data.sonarr_data.log_details.scroll_to_bottom(); self.app.data.sonarr_data.log_details.scroll_to_bottom();
} }
_ if key == DEFAULT_KEYBINDINGS.tasks.key => { _ if matches_key!(tasks, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::SystemTasks.into()); .push_navigation_stack(ActiveSonarrBlock::SystemTasks.into());
} }
_ if key == DEFAULT_KEYBINDINGS.update.key => { _ if matches_key!(update, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::SystemUpdates.into()); .push_navigation_stack(ActiveSonarrBlock::SystemUpdates.into());
@@ -1,7 +1,7 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; 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::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SYSTEM_DETAILS_BLOCKS};
use crate::models::sonarr_models::SonarrTaskName; use crate::models::sonarr_models::SonarrTaskName;
use crate::models::stateful_list::StatefulList; 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) SYSTEM_DETAILS_BLOCKS.contains(&active_block)
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -114,7 +118,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler
match self.active_sonarr_block { match self.active_sonarr_block {
ActiveSonarrBlock::SystemLogs => match self.key { ActiveSonarrBlock::SystemLogs => match self.key {
_ if key == DEFAULT_KEYBINDINGS.left.key => { _ if matches_key!(left, key) => {
self self
.app .app
.data .data
@@ -124,7 +128,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler
.iter() .iter()
.for_each(|log| log.scroll_right()); .for_each(|log| log.scroll_right());
} }
_ if key == DEFAULT_KEYBINDINGS.right.key => { _ if matches_key!(right, key) => {
self self
.app .app
.data .data
@@ -178,14 +182,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler
} }
fn handle_char_key_event(&mut self) { fn handle_char_key_event(&mut self) {
if SYSTEM_DETAILS_BLOCKS.contains(&self.active_sonarr_block) if SYSTEM_DETAILS_BLOCKS.contains(&self.active_sonarr_block) && matches_key!(refresh, self.key)
&& self.key == DEFAULT_KEYBINDINGS.refresh.key
{ {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
if self.active_sonarr_block == ActiveSonarrBlock::SystemTaskStartConfirmPrompt 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 = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action =
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; 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] #[test]
fn test_extract_task_name() { fn test_extract_task_name() {
let mut app = App::test_default(); 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] #[test]
fn test_system_handler_is_not_ready_when_loading() { fn test_system_handler_is_not_ready_when_loading() {
let mut app = App::test_default(); let mut app = App::test_default();
+11 -11
View File
@@ -42,17 +42,17 @@ macro_rules! handle_table_events {
fn [<handle_ $name _table_events>](&mut $self, config: $crate::handlers::table_handler::TableHandlingConfig<$row>) -> bool { fn [<handle_ $name _table_events>](&mut $self, config: $crate::handlers::table_handler::TableHandlingConfig<$row>) -> bool {
if $self.is_ready() { if $self.is_ready() {
match $self.key { match $self.key {
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.up.key => $self.[<handle_ $name _table_scroll_up>](config), _ if $crate::matches_key!(up, $self.key, $self.ignore_alt_navigation()) => $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 $crate::matches_key!(down, $self.key, $self.ignore_alt_navigation()) => $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 $crate::matches_key!(home, $self.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 $crate::matches_key!(end, $self.key) => $self.[<handle_ $name _table_end>](config),
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.left.key _ if $crate::matches_key!(left, $self.key, $self.ignore_alt_navigation())
|| $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.right.key => || $crate::matches_key!(right, $self.key, $self.ignore_alt_navigation()) =>
{ {
$self.[<handle_ $name _table_left_right>](config) $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 $crate::matches_key!(submit, $self.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!(esc, $self.key) => $self.[<handle_ $name _table_esc>](config),
_ if config.searching_block.is_some() _ if config.searching_block.is_some()
&& $self.app.get_current_route() == *config.searching_block.as_ref().unwrap() => && $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>]() $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), && 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), && 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), && config.sorting_block.is_some() => $self.[<handle_ $name _table_sort_key>](config),
_ => false, _ => false,
} }
+8 -4
View File
@@ -48,6 +48,10 @@ mod tests {
true true
} }
fn ignore_alt_navigation(&self) -> bool {
self.app.should_ignore_quit_key
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -1040,7 +1044,7 @@ mod tests {
app.data.radarr_data.movies.search = Some(HorizontallyScrollableText::default()); app.data.radarr_data.movies.search = Some(HorizontallyScrollableText::default());
TableHandlerUnit::new( TableHandlerUnit::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::SearchMovie, ActiveRadarrBlock::SearchMovie,
None, None,
@@ -1049,7 +1053,7 @@ mod tests {
assert_str_eq!( assert_str_eq!(
app.data.radarr_data.movies.search.as_ref().unwrap().text, 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()); app.data.radarr_data.movies.filter = Some(HorizontallyScrollableText::default());
TableHandlerUnit::new( TableHandlerUnit::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::FilterMovies, ActiveRadarrBlock::FilterMovies,
None, None,
@@ -1074,7 +1078,7 @@ mod tests {
assert_str_eq!( assert_str_eq!(
app.data.radarr_data.movies.filter.as_ref().unwrap().text, app.data.radarr_data.movies.filter.as_ref().unwrap().text,
"h" "a"
); );
} }
+63 -10
View File
@@ -1,10 +1,4 @@
use anyhow::Result; 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::{crate_authors, crate_description, crate_name, crate_version, CommandFactory, Parser};
use clap_complete::generate; use clap_complete::generate;
use crossterm::execute; use crossterm::execute;
@@ -16,6 +10,11 @@ use network::NetworkTrait;
use ratatui::backend::CrosstermBackend; use ratatui::backend::CrosstermBackend;
use ratatui::Terminal; use ratatui::Terminal;
use reqwest::Client; 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::select;
use tokio::sync::mpsc::Receiver; use tokio::sync::mpsc::Receiver;
use tokio::sync::{mpsc, Mutex}; 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, 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::cli::Command;
use crate::event::input_event::{Events, InputEvent}; use crate::event::input_event::{Events, InputEvent};
use crate::event::Key; use crate::event::Key;
use crate::network::{Network, NetworkEvent}; 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 app;
mod cli; mod cli;
@@ -74,6 +75,22 @@ struct Cli {
help = "The Managarr configuration file to use" help = "The Managarr configuration file to use"
)] )]
config_file: Option<PathBuf>, 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( #[arg(
long, long,
global = true, global = true,
@@ -98,10 +115,12 @@ async fn main() -> Result<()> {
} else { } else {
confy::load("managarr", "config")? confy::load("managarr", "config")?
}; };
let theme_name = config.theme.clone();
let spinner_disabled = args.disable_spinner; let spinner_disabled = args.disable_spinner;
debug!("Managarr loaded using config: {config:?}"); debug!("Managarr loaded using config: {config:?}");
config.validate(); config.validate();
config.post_process_initialization(); config.post_process_initialization();
let reqwest_client = build_network_client(&config); let reqwest_client = build_network_client(&config);
let (sync_network_tx, sync_network_rx) = mpsc::channel(500); let (sync_network_tx, sync_network_rx) = mpsc::channel(500);
let cancellation_token = CancellationToken::new(); let cancellation_token = CancellationToken::new();
@@ -140,7 +159,12 @@ async fn main() -> Result<()> {
std::thread::spawn(move || { std::thread::spawn(move || {
start_networking(sync_network_rx, &app_nw, cancellation_token, reqwest_client) 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(); let mut stdout = io::stdout();
enable_raw_mode()?; enable_raw_mode()?;
+15 -4
View File
@@ -42,10 +42,6 @@ pub enum Serdeable {
Sonarr(SonarrSerdeable), Sonarr(SonarrSerdeable),
} }
pub trait EnumDisplayStyle<'a> {
fn to_display_str(self) -> &'a str;
}
pub trait Scrollable { pub trait Scrollable {
fn scroll_down(&mut self); fn scroll_down(&mut self);
fn scroll_up(&mut self); fn scroll_up(&mut self);
@@ -445,6 +441,21 @@ pub fn strip_non_search_characters(input: &str) -> String {
#[macro_export] #[macro_export]
macro_rules! serde_enum_from { macro_rules! serde_enum_from {
($enum_name:ident { $($variant:ident($ty:ty),)* }) => { ($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 { impl From<$ty> for $enum_name {
fn from(value: $ty) -> Self { fn from(value: $ty) -> Self {
+26 -88
View File
@@ -1,19 +1,19 @@
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use crate::{models::HorizontallyScrollableText, serde_enum_from};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use clap::ValueEnum; use clap::ValueEnum;
use derivative::Derivative; use derivative::Derivative;
use enum_display_style_derive::EnumDisplayStyle;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{json, Number, Value}; use serde_json::{Number, Value};
use strum_macros::EnumIter; use strum_macros::{Display, EnumIter};
use crate::{models::HorizontallyScrollableText, serde_enum_from};
use super::servarr_models::{ use super::servarr_models::{
DiskSpace, HostConfig, Indexer, Language, LogResponse, QualityProfile, QualityWrapper, DiskSpace, HostConfig, Indexer, Language, LogResponse, QualityProfile, QualityWrapper,
QueueEvent, RootFolder, SecurityConfig, Tag, Update, QueueEvent, RootFolder, SecurityConfig, Tag, Update,
}; };
use super::{EnumDisplayStyle, Serdeable}; use super::Serdeable;
#[cfg(test)] #[cfg(test)]
#[path = "radarr_models_tests.rs"] #[path = "radarr_models_tests.rs"]
@@ -258,69 +258,44 @@ pub struct MediaInfo {
} }
#[derive( #[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")] #[serde(rename_all = "camelCase")]
#[strum(serialize_all = "camelCase")]
pub enum MinimumAvailability { pub enum MinimumAvailability {
#[default] #[default]
Announced, Announced,
#[display_style(name = "In Cinemas")]
InCinemas, InCinemas,
Released, Released,
#[display_style(name = "TBA")]
Tba, Tba,
} }
impl Display for MinimumAvailability { #[derive(
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { Default, PartialEq, Eq, Clone, Copy, Debug, EnumIter, ValueEnum, Display, EnumDisplayStyle,
let minimum_availability = match self { )]
MinimumAvailability::Tba => "tba", #[strum(serialize_all = "camelCase")]
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)]
pub enum MovieMonitor { pub enum MovieMonitor {
#[default] #[default]
#[display_style(name = "Movie only")]
MovieOnly, MovieOnly,
#[display_style(name = "Movie and Collection")]
MovieAndCollection, MovieAndCollection,
None, 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)] #[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Movie { 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 { impl From<RadarrSerdeable> for Serdeable {
fn from(value: RadarrSerdeable) -> Serdeable { fn from(value: RadarrSerdeable) -> Serdeable {
Serdeable::Radarr(value) Serdeable::Radarr(value)
} }
} }
impl From<()> for RadarrSerdeable {
fn from(_: ()) -> Self {
RadarrSerdeable::Value(json!({}))
}
}
serde_enum_from!( serde_enum_from!(
RadarrSerdeable { RadarrSerdeable {
Value(Value), Value(Value),
+1 -1
View File
@@ -11,7 +11,7 @@ mod tests {
RadarrSerdeable, RadarrTask, RadarrTaskName, SystemStatus, Tag, Update, RadarrSerdeable, RadarrTask, RadarrTaskName, SystemStatus, Tag, Update,
}, },
servarr_models::{HostConfig, Log, LogResponse, QueueEvent, RootFolder, SecurityConfig}, servarr_models::{HostConfig, Log, LogResponse, QueueEvent, RootFolder, SecurityConfig},
EnumDisplayStyle, Serdeable, Serdeable,
}; };
#[test] #[test]
+83 -205
View File
@@ -1,13 +1,14 @@
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use crate::serde_enum_from;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use clap::ValueEnum; use clap::ValueEnum;
use derivative::Derivative; use derivative::Derivative;
use enum_display_style_derive::EnumDisplayStyle;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{json, Number, Value}; use serde_json::{Number, Value};
use strum::EnumIter; use strum::EnumIter;
use strum_macros::Display;
use crate::serde_enum_from;
use super::{ use super::{
radarr_models::IndexerTestResult, radarr_models::IndexerTestResult,
@@ -15,7 +16,7 @@ use super::{
DiskSpace, HostConfig, Indexer, Language, LogResponse, QualityProfile, QualityWrapper, DiskSpace, HostConfig, Indexer, Language, LogResponse, QualityProfile, QualityWrapper,
QueueEvent, RootFolder, SecurityConfig, Tag, Update, QueueEvent, RootFolder, SecurityConfig, Tag, Update,
}, },
EnumDisplayStyle, HorizontallyScrollableText, Serdeable, HorizontallyScrollableText, Serdeable,
}; };
#[cfg(test)] #[cfg(test)]
@@ -112,8 +113,7 @@ pub struct DownloadRecord {
pub status: DownloadStatus, pub status: DownloadStatus,
#[serde(deserialize_with = "super::from_i64")] #[serde(deserialize_with = "super::from_i64")]
pub id: i64, pub id: i64,
#[serde(deserialize_with = "super::from_i64")] pub episode_id: Option<Number>,
pub episode_id: i64,
#[serde(deserialize_with = "super::from_f64")] #[serde(deserialize_with = "super::from_f64")]
pub size: f64, pub size: f64,
#[serde(deserialize_with = "super::from_f64")] #[serde(deserialize_with = "super::from_f64")]
@@ -126,8 +126,21 @@ pub struct DownloadRecord {
impl Eq for 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")] #[serde(rename_all = "camelCase")]
#[strum(serialize_all = "camelCase")]
pub enum DownloadStatus { pub enum DownloadStatus {
#[default] #[default]
Unknown, Unknown,
@@ -138,45 +151,11 @@ pub enum DownloadStatus {
Failed, Failed,
Warning, Warning,
Delay, Delay,
#[display_style(name = "Download Client Unavailable")]
DownloadClientUnavailable, DownloadClientUnavailable,
Fallback, 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)] #[derive(Default, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct DownloadsResponse { pub struct DownloadsResponse {
@@ -309,7 +288,7 @@ pub struct Season {
#[serde(deserialize_with = "super::from_i64")] #[serde(deserialize_with = "super::from_i64")]
pub season_number: i64, pub season_number: i64,
pub monitored: bool, pub monitored: bool,
pub statistics: SeasonStatistics, pub statistics: Option<SeasonStatistics>,
} }
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)] #[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
@@ -364,74 +343,66 @@ pub struct Series {
} }
#[derive( #[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")] #[serde(rename_all = "camelCase")]
#[strum(serialize_all = "camelCase")]
pub enum SeriesMonitor { pub enum SeriesMonitor {
#[default] #[default]
#[display_style(name = "All Episodes")]
All, All,
Unknown, Unknown,
#[display_style(name = "Future Episodes")]
Future, Future,
#[display_style(name = "Missing Episodes")]
Missing, Missing,
#[display_style(name = "Existing Episodes")]
Existing, Existing,
#[display_style(name = "Only First Season")]
FirstSeason, FirstSeason,
#[display_style(name = "Only Last Season")]
LastSeason, LastSeason,
#[display_style(name = "Only Latest Season")]
LatestSeason, LatestSeason,
#[display_style(name = "Pilot Episode")]
Pilot, Pilot,
#[display_style(name = "Recent Episodes")]
Recent, Recent,
#[display_style(name = "Only Specials")]
MonitorSpecials, MonitorSpecials,
#[display_style(name = "Not Specials")]
UnmonitorSpecials, UnmonitorSpecials,
None, None,
Skip, 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( #[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")] #[serde(rename_all = "camelCase")]
#[strum(serialize_all = "camelCase")]
pub enum SeriesType { pub enum SeriesType {
#[default] #[default]
Standard, Standard,
@@ -439,27 +410,6 @@ pub enum SeriesType {
Anime, 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)] #[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct SeriesStatistics { pub struct SeriesStatistics {
@@ -479,8 +429,21 @@ pub struct SeriesStatistics {
impl Eq for 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")] #[serde(rename_all = "camelCase")]
#[strum(serialize_all = "camelCase")]
pub enum SeriesStatus { pub enum SeriesStatus {
#[default] #[default]
Continuing, Continuing,
@@ -489,29 +452,6 @@ pub enum SeriesStatus {
Deleted, 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)] #[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct SonarrHistoryWrapper { pub struct SonarrHistoryWrapper {
@@ -538,51 +478,29 @@ pub struct SonarrHistoryData {
pub relative_path: Option<String>, 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")] #[serde(rename_all = "camelCase")]
#[strum(serialize_all = "camelCase")]
pub enum SonarrHistoryEventType { pub enum SonarrHistoryEventType {
#[default] #[default]
Unknown, Unknown,
Grabbed, Grabbed,
#[display_style(name = "Series Folder Imported")]
SeriesFolderImported, SeriesFolderImported,
#[display_style(name = "Download Folder Imported")]
DownloadFolderImported, DownloadFolderImported,
#[display_style(name = "Download Failed")]
DownloadFailed, DownloadFailed,
#[display_style(name = "Episode File Deleted")]
EpisodeFileDeleted, EpisodeFileDeleted,
#[display_style(name = "Episode File Renamed")]
EpisodeFileRenamed, EpisodeFileRenamed,
#[display_style(name = "Download Ignored")]
DownloadIgnored, 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)] #[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct SonarrHistoryItem { 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 { impl From<SonarrSerdeable> for Serdeable {
fn from(value: SonarrSerdeable) -> Serdeable { fn from(value: SonarrSerdeable) -> Serdeable {
Serdeable::Sonarr(value) Serdeable::Sonarr(value)
} }
} }
impl From<()> for SonarrSerdeable {
fn from(_: ()) -> Self {
SonarrSerdeable::Value(json!({}))
}
}
serde_enum_from!( serde_enum_from!(
SonarrSerdeable { SonarrSerdeable {
AddSeriesSearchResults(Vec<AddSeriesSearchResult>), AddSeriesSearchResults(Vec<AddSeriesSearchResult>),

Some files were not shown because too many files have changed in this diff Show More