Refactored tables and loading blocks to use the new dedicated widgets for Tables and Loading blocks

This commit is contained in:
2024-02-10 19:23:19 -07:00
parent 68de986c48
commit 51b789fd0f
19 changed files with 1174 additions and 1150 deletions
Generated
+110 -128
View File
@@ -75,9 +75,9 @@ dependencies = [
[[package]] [[package]]
name = "anstyle" name = "anstyle"
version = "1.0.4" version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]] [[package]]
name = "anstyle-parse" name = "anstyle-parse"
@@ -224,9 +224,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.31" version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
@@ -234,7 +234,7 @@ dependencies = [
"num-traits", "num-traits",
"serde", "serde",
"wasm-bindgen", "wasm-bindgen",
"windows-targets 0.48.5", "windows-targets 0.52.0",
] ]
[[package]] [[package]]
@@ -274,7 +274,7 @@ checksum = "15d296c475c6ed4093824c28e222420831d27577aaaf0a1163a3b7fc35b248a5"
dependencies = [ dependencies = [
"directories", "directories",
"serde", "serde",
"serde_yaml 0.9.30", "serde_yaml",
"thiserror", "thiserror",
] ]
@@ -374,9 +374,9 @@ dependencies = [
[[package]] [[package]]
name = "either" name = "either"
version = "1.9.0" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
@@ -569,19 +569,13 @@ dependencies = [
"futures-sink", "futures-sink",
"futures-util", "futures-util",
"http", "http",
"indexmap 2.1.0", "indexmap",
"slab", "slab",
"tokio", "tokio",
"tokio-util", "tokio-util",
"tracing", "tracing",
] ]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.14.3" version = "0.14.3"
@@ -600,9 +594,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.3.4" version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3"
[[package]] [[package]]
name = "http" name = "http"
@@ -699,9 +693,9 @@ dependencies = [
[[package]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.59" version = "0.1.60"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
dependencies = [ dependencies = [
"android_system_properties", "android_system_properties",
"core-foundation-sys", "core-foundation-sys",
@@ -732,22 +726,12 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.9.3" version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
]
[[package]]
name = "indexmap"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.14.3", "hashbrown",
] ]
[[package]] [[package]]
@@ -764,9 +748,9 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.12.0" version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [ dependencies = [
"either", "either",
] ]
@@ -779,9 +763,9 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.67" version = "0.3.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee"
dependencies = [ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
@@ -794,9 +778,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.152" version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]] [[package]]
name = "libredox" name = "libredox"
@@ -809,12 +793,6 @@ dependencies = [
"redox_syscall", "redox_syscall",
] ]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.13" version = "0.4.13"
@@ -848,9 +826,9 @@ checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7"
[[package]] [[package]]
name = "log4rs" name = "log4rs"
version = "1.2.0" version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d36ca1786d9e79b8193a68d480a0907b612f109537115c6ff655a3a1967533fd" checksum = "0816135ae15bd0391cf284eab37e6e3ee0a6ee63d2ceeb659862bd8d0a984ca6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arc-swap", "arc-swap",
@@ -861,11 +839,13 @@ dependencies = [
"libc", "libc",
"log", "log",
"log-mdc", "log-mdc",
"once_cell",
"parking_lot", "parking_lot",
"rand",
"serde", "serde",
"serde-value", "serde-value",
"serde_json", "serde_json",
"serde_yaml 0.8.26", "serde_yaml",
"thiserror", "thiserror",
"thread-id", "thread-id",
"typemap-ors", "typemap-ors",
@@ -874,11 +854,11 @@ dependencies = [
[[package]] [[package]]
name = "lru" name = "lru"
version = "0.12.1" version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2994eeba8ed550fd9b47a0b38f0242bc3344e496483c6180b69139cc2fa5d1d7" checksum = "db2c024b41519440580066ba82aab04092b333e09066a5eb86c7c4890df31f22"
dependencies = [ dependencies = [
"hashbrown 0.14.3", "hashbrown",
] ]
[[package]] [[package]]
@@ -905,7 +885,7 @@ dependencies = [
"rstest", "rstest",
"serde", "serde",
"serde_json", "serde_json",
"serde_yaml 0.9.30", "serde_yaml",
"strum", "strum",
"strum_macros", "strum_macros",
"tokio", "tokio",
@@ -927,9 +907,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.7.1" version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
dependencies = [ dependencies = [
"adler", "adler",
] ]
@@ -984,10 +964,16 @@ dependencies = [
] ]
[[package]] [[package]]
name = "num-traits" name = "num-conv"
version = "0.2.17" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num-traits"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
dependencies = [ dependencies = [
"autocfg", "autocfg",
] ]
@@ -1028,9 +1014,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]] [[package]]
name = "openssl" name = "openssl"
version = "0.10.62" version = "0.10.63"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8"
dependencies = [ dependencies = [
"bitflags 2.4.2", "bitflags 2.4.2",
"cfg-if", "cfg-if",
@@ -1060,9 +1046,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.98" version = "0.9.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@@ -1173,9 +1159,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.76" version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -1262,9 +1248,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.10.2" version = "1.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@@ -1274,9 +1260,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.4.3" version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@@ -1297,9 +1283,9 @@ checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.23" version = "0.11.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251"
dependencies = [ dependencies = [
"base64", "base64",
"bytes", "bytes",
@@ -1319,9 +1305,11 @@ dependencies = [
"once_cell", "once_cell",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"rustls-pemfile",
"serde", "serde",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
"sync_wrapper",
"system-configuration", "system-configuration",
"tokio", "tokio",
"tokio-native-tls", "tokio-native-tls",
@@ -1379,9 +1367,9 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.30" version = "0.38.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
dependencies = [ dependencies = [
"bitflags 2.4.2", "bitflags 2.4.2",
"errno", "errno",
@@ -1390,6 +1378,15 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "rustls-pemfile"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
"base64",
]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.14" version = "1.0.14"
@@ -1448,9 +1445,9 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.195" version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@@ -1467,9 +1464,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.195" version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1478,9 +1475,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.111" version = "1.0.113"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@@ -1510,23 +1507,11 @@ dependencies = [
[[package]] [[package]]
name = "serde_yaml" name = "serde_yaml"
version = "0.8.26" version = "0.9.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" checksum = "adf8a49373e98a4c5f0ceb5d05aa7c648d75f63774981ed95b7c7443bbd50c6e"
dependencies = [ dependencies = [
"indexmap 1.9.3", "indexmap",
"ryu",
"serde",
"yaml-rust",
]
[[package]]
name = "serde_yaml"
version = "0.9.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38"
dependencies = [
"indexmap 2.1.0",
"itoa", "itoa",
"ryu", "ryu",
"serde", "serde",
@@ -1654,6 +1639,12 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]] [[package]]
name = "system-configuration" name = "system-configuration"
version = "0.5.1" version = "0.5.1"
@@ -1677,13 +1668,12 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.9.0" version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
"redox_syscall",
"rustix", "rustix",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
@@ -1720,12 +1710,13 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.31" version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
dependencies = [ dependencies = [
"deranged", "deranged",
"libc", "libc",
"num-conv",
"num_threads", "num_threads",
"powerfmt", "powerfmt",
"serde", "serde",
@@ -1755,9 +1746,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.35.1" version = "1.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@@ -1809,9 +1800,9 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.8.8" version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290"
dependencies = [ dependencies = [
"serde", "serde",
"serde_spanned", "serde_spanned",
@@ -1830,11 +1821,11 @@ dependencies = [
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.21.0" version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" checksum = "0c9ffdf896f8daaabf9b66ba8e77ea1ed5ed0f72821b398aba62352e95062951"
dependencies = [ dependencies = [
"indexmap 2.1.0", "indexmap",
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
@@ -1903,9 +1894,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.10.1" version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
@@ -1989,9 +1980,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.90" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"wasm-bindgen-macro", "wasm-bindgen-macro",
@@ -1999,9 +1990,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "wasm-bindgen-backend"
version = "0.2.90" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"log", "log",
@@ -2014,9 +2005,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-futures" name = "wasm-bindgen-futures"
version = "0.4.40" version = "0.4.41"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys", "js-sys",
@@ -2026,9 +2017,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.90" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@@ -2036,9 +2027,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.90" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2049,15 +2040,15 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.90" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838"
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.67" version = "0.3.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@@ -2236,15 +2227,6 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]
[[package]] [[package]]
name = "yansi" name = "yansi"
version = "0.5.1" version = "0.5.1"
+7 -102
View File
@@ -4,15 +4,13 @@ use ratatui::layout::{Alignment, Constraint, Flex, Layout, Rect};
use ratatui::style::{Style, Stylize}; use ratatui::style::{Style, Stylize};
use ratatui::text::{Line, Text}; use ratatui::text::{Line, Text};
use ratatui::widgets::Paragraph; use ratatui::widgets::Paragraph;
use ratatui::widgets::Row;
use ratatui::widgets::Table;
use ratatui::widgets::Tabs; use ratatui::widgets::Tabs;
use ratatui::widgets::{Block, Wrap}; use ratatui::widgets::Wrap;
use ratatui::widgets::{Clear, List, ListItem}; use ratatui::widgets::{Clear, List, ListItem};
use ratatui::Frame; use ratatui::Frame;
use crate::app::App; use crate::app::App;
use crate::models::{HorizontallyScrollableText, Route, StatefulList, StatefulTable, TabState}; use crate::models::{HorizontallyScrollableText, Route, StatefulList, TabState};
use crate::ui::radarr_ui::RadarrUi; use crate::ui::radarr_ui::RadarrUi;
use crate::ui::styles::ManagarrStyle; use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{ use crate::ui::utils::{
@@ -22,6 +20,7 @@ use crate::ui::utils::{
use crate::ui::widgets::button::Button; use crate::ui::widgets::button::Button;
use crate::ui::widgets::checkbox::Checkbox; use crate::ui::widgets::checkbox::Checkbox;
use crate::ui::widgets::input_box::InputBox; use crate::ui::widgets::input_box::InputBox;
use crate::ui::widgets::loading_block::LoadingBlock;
mod radarr_ui; mod radarr_ui;
mod styles; mod styles;
@@ -275,14 +274,6 @@ fn draw_tabs(f: &mut Frame<'_>, area: Rect, title: &str, tab_state: &TabState) -
content_area content_area
} }
pub struct TableProps<'a, T> {
pub content: Option<&'a mut StatefulTable<T>>,
pub wrapped_content: Option<Option<&'a mut StatefulTable<T>>>,
pub table_headers: Vec<&'a str>,
pub constraints: Vec<Constraint>,
pub help: Option<String>,
}
pub struct ListProps<'a, T> { pub struct ListProps<'a, T> {
pub content: &'a mut StatefulList<T>, pub content: &'a mut StatefulList<T>,
pub title: &'static str, pub title: &'static str,
@@ -291,94 +282,6 @@ pub struct ListProps<'a, T> {
pub help: Option<String>, pub help: Option<String>,
} }
fn draw_table<'a, T, F>(
f: &mut Frame<'_>,
area: Rect,
block: Block<'_>,
table_props: TableProps<'a, T>,
row_mapper: F,
is_loading: bool,
highlight: bool,
) where
F: Fn(&T) -> Row<'a>,
{
let TableProps {
content,
wrapped_content,
table_headers,
constraints,
help,
} = table_props;
let content_area = draw_help_footer_and_get_content_area(f, area, help);
#[allow(clippy::unnecessary_unwrap)]
if wrapped_content.is_some() && wrapped_content.as_ref().unwrap().is_some() {
draw_table_contents(
f,
block,
row_mapper,
highlight,
wrapped_content.unwrap().as_mut().unwrap(),
table_headers,
constraints,
content_area,
);
} else if content.is_some() && !content.as_ref().unwrap().items.is_empty() {
draw_table_contents(
f,
block,
row_mapper,
highlight,
content.unwrap(),
table_headers,
constraints,
content_area,
);
} else {
loading(f, block, content_area, is_loading);
}
}
#[allow(clippy::too_many_arguments)]
fn draw_table_contents<'a, T, F>(
f: &mut Frame<'_>,
block: Block<'_>,
row_mapper: F,
highlight: bool,
content: &mut StatefulTable<T>,
table_headers: Vec<&str>,
constraints: Vec<Constraint>,
area: Rect,
) where
F: Fn(&T) -> Row<'a>,
{
let rows = content.items.iter().map(row_mapper);
let headers = Row::new(table_headers).default().bold().bottom_margin(0);
let mut table = Table::new(rows, &constraints).header(headers).block(block);
if highlight {
table = table
.highlight_style(Style::new().highlight())
.highlight_symbol(HIGHLIGHT_SYMBOL);
}
f.render_stateful_widget(table, area, &mut content.state);
}
pub fn loading(f: &mut Frame<'_>, block: Block<'_>, area: Rect, is_loading: bool) {
if is_loading {
let paragraph = Paragraph::new(Text::from("\n\n Loading ...\n\n"))
.system_function()
.block(block);
f.render_widget(paragraph, area);
} else {
f.render_widget(block, area)
}
}
pub fn draw_prompt_box( pub fn draw_prompt_box(
f: &mut Frame<'_>, f: &mut Frame<'_>,
area: Rect, area: Rect,
@@ -532,7 +435,7 @@ pub fn draw_list_box<'a, T>(
f.render_stateful_widget(list, content_area, &mut content.state); f.render_stateful_widget(list, content_area, &mut content.state);
} else { } else {
loading(f, block, content_area, is_loading); f.render_widget(LoadingBlock::new(is_loading, block), content_area);
} }
} }
@@ -543,7 +446,9 @@ fn draw_help_footer_and_get_content_area(
) -> Rect { ) -> Rect {
if let Some(help_string) = help { if let Some(help_string) = help {
let [content_area, help_footer_area] = let [content_area, help_footer_area] =
Layout::vertical([Constraint::Fill(0), Constraint::Length(2)]).areas(area); Layout::vertical([Constraint::Fill(0), Constraint::Length(2)])
.margin(1)
.areas(area);
let help_paragraph = Paragraph::new(Text::from(format!(" {help_string}").help())) let help_paragraph = Paragraph::new(Text::from(format!(" {help_string}").help()))
.block(layout_block_top_border()) .block(layout_block_top_border())
@@ -18,7 +18,8 @@ use crate::ui::utils::{
borderless_block, get_width_from_percentage, layout_block_top_border_with_title, title_block, borderless_block, get_width_from_percentage, layout_block_top_border_with_title, title_block,
title_style, title_style,
}; };
use crate::ui::{draw_large_popup_over, draw_small_popup_over, draw_table, DrawUi, TableProps}; use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::{draw_large_popup_over, draw_small_popup_over, DrawUi};
use crate::utils::convert_runtime; use crate::utils::convert_runtime;
#[cfg(test)] #[cfg(test)]
@@ -67,13 +68,10 @@ impl DrawUi for CollectionDetailsUi {
} }
pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let [description_area, table_area, help_footer_area] = Layout::vertical([ let [description_area, table_area] =
Constraint::Percentage(25), Layout::vertical([Constraint::Percentage(25), Constraint::Fill(0)])
Constraint::Percentage(70), .margin(1)
Constraint::Percentage(5), .areas(area);
])
.margin(1)
.areas(area);
let collection_selection = let collection_selection =
if let Some(filtered_collections) = app.data.radarr_data.filtered_collections.as_ref() { if let Some(filtered_collections) = app.data.radarr_data.filtered_collections.as_ref() {
filtered_collections.current_selection() filtered_collections.current_selection()
@@ -97,13 +95,63 @@ pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
.current_selection() .current_selection()
.clone() .clone()
}; };
let help_text = Text::from( let movie_row_mapper = |movie: &CollectionMovie| {
format!( let in_library = if app
"<↑↓> scroll table | {}", .data
build_context_clue_string(&COLLECTION_DETAILS_CONTEXT_CLUES) .radarr_data
) .movies
.help(), .items
); .iter()
.any(|mov| mov.tmdb_id == movie.tmdb_id)
{
""
} else {
""
};
movie.title.scroll_left_or_reset(
get_width_from_percentage(table_area, 20),
current_selection == *movie,
app.tick_count % app.ticks_until_scroll == 0,
);
let (hours, minutes) = convert_runtime(movie.runtime);
let imdb_rating = movie
.ratings
.imdb
.clone()
.unwrap_or_default()
.value
.as_f64()
.unwrap();
let rotten_tomatoes_rating = movie
.ratings
.rotten_tomatoes
.clone()
.unwrap_or_default()
.value
.as_u64()
.unwrap();
let imdb_rating = if imdb_rating == 0.0 {
String::new()
} else {
format!("{imdb_rating:.1}")
};
let rotten_tomatoes_rating = if rotten_tomatoes_rating == 0 {
String::new()
} else {
format!("{rotten_tomatoes_rating}%")
};
Row::new(vec![
Cell::from(in_library),
Cell::from(movie.title.to_string()),
Cell::from(movie.year.to_string()),
Cell::from(format!("{hours}h {minutes}m")),
Cell::from(imdb_rating),
Cell::from(rotten_tomatoes_rating),
Cell::from(movie.genres.join(", ")),
])
.primary()
};
let monitored = if collection_selection.monitored { let monitored = if collection_selection.monitored {
"Yes" "Yes"
} else { } else {
@@ -115,10 +163,14 @@ pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
"No" "No"
}; };
let minimum_availability = collection_selection.minimum_availability.to_display_str(); let minimum_availability = collection_selection.minimum_availability.to_display_str();
let help_footer = format!(
"<↑↓> scroll table | {}",
build_context_clue_string(&COLLECTION_DETAILS_CONTEXT_CLUES)
);
let collection_description = Text::from(vec![ let collection_description = Text::from(vec![
Line::from(vec![ Line::from(vec![
"Overview ".primary().bold(), "Overview: ".primary().bold(),
collection_selection collection_selection
.overview .overview
.clone() .clone()
@@ -151,102 +203,36 @@ pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
let description_paragraph = Paragraph::new(collection_description) let description_paragraph = Paragraph::new(collection_description)
.block(borderless_block()) .block(borderless_block())
.wrap(Wrap { trim: false }); .wrap(Wrap { trim: false });
let help_paragraph = Paragraph::new(help_text) let movies_table = ManagarrTable::new(
.block(borderless_block()) Some(&mut app.data.radarr_data.collection_movies),
.alignment(Alignment::Center); movie_row_mapper,
)
.block(layout_block_top_border_with_title(title_style("Movies")))
.loading(app.is_loading)
.footer_alignment(Alignment::Center)
.footer(Some(help_footer))
.headers([
"",
"Title",
"Year",
"Runtime",
"IMDB Rating",
"Rotten Tomatoes Rating",
"Genres",
])
.constraints([
Constraint::Percentage(2),
Constraint::Percentage(20),
Constraint::Percentage(8),
Constraint::Percentage(10),
Constraint::Percentage(10),
Constraint::Percentage(18),
Constraint::Percentage(28),
]);
f.render_widget(title_block(&collection_selection.title.text), area); f.render_widget(title_block(&collection_selection.title.text), area);
f.render_widget(description_paragraph, description_area); f.render_widget(description_paragraph, description_area);
f.render_widget(help_paragraph, help_footer_area); f.render_widget(movies_table, table_area);
draw_table(
f,
table_area,
layout_block_top_border_with_title(title_style("Movies")),
TableProps {
content: Some(&mut app.data.radarr_data.collection_movies),
wrapped_content: None,
table_headers: vec![
"",
"Title",
"Year",
"Runtime",
"IMDB Rating",
"Rotten Tomatoes Rating",
"Genres",
],
constraints: vec![
Constraint::Percentage(2),
Constraint::Percentage(20),
Constraint::Percentage(8),
Constraint::Percentage(10),
Constraint::Percentage(10),
Constraint::Percentage(18),
Constraint::Percentage(28),
],
help: None,
},
|movie| {
let in_library = if app
.data
.radarr_data
.movies
.items
.iter()
.any(|mov| mov.tmdb_id == movie.tmdb_id)
{
""
} else {
""
};
movie.title.scroll_left_or_reset(
get_width_from_percentage(table_area, 20),
current_selection == *movie,
app.tick_count % app.ticks_until_scroll == 0,
);
let (hours, minutes) = convert_runtime(movie.runtime);
let imdb_rating = movie
.ratings
.imdb
.clone()
.unwrap_or_default()
.value
.as_f64()
.unwrap();
let rotten_tomatoes_rating = movie
.ratings
.rotten_tomatoes
.clone()
.unwrap_or_default()
.value
.as_u64()
.unwrap();
let imdb_rating = if imdb_rating == 0.0 {
String::new()
} else {
format!("{imdb_rating:.1}")
};
let rotten_tomatoes_rating = if rotten_tomatoes_rating == 0 {
String::new()
} else {
format!("{rotten_tomatoes_rating}%")
};
Row::new(vec![
Cell::from(in_library),
Cell::from(movie.title.to_string()),
Cell::from(movie.year.to_string()),
Cell::from(format!("{hours}h {minutes}m")),
Cell::from(imdb_rating),
Cell::from(rotten_tomatoes_rating),
Cell::from(movie.genres.join(", ")),
])
.primary()
},
app.is_loading,
true,
);
} }
fn draw_movie_overview(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { fn draw_movie_overview(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
+57 -61
View File
@@ -12,9 +12,10 @@ use crate::ui::radarr_ui::collections::collection_details_ui::CollectionDetailsU
use crate::ui::radarr_ui::collections::edit_collection_ui::EditCollectionUi; use crate::ui::radarr_ui::collections::edit_collection_ui::EditCollectionUi;
use crate::ui::styles::ManagarrStyle; use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{get_width_from_percentage, layout_block_top_border}; use crate::ui::utils::{get_width_from_percentage, layout_block_top_border};
use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::{ use crate::ui::{
draw_error_message_popup, draw_input_box_popup, draw_popup_over, draw_prompt_box, draw_error_message_popup, draw_input_box_popup, draw_popup_over, draw_prompt_box,
draw_prompt_popup_over, draw_table, DrawUi, TableProps, draw_prompt_popup_over, DrawUi,
}; };
mod collection_details_ui; mod collection_details_ui;
@@ -113,67 +114,62 @@ pub(super) fn draw_collections(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
Some(filtered_collections) if !app.data.radarr_data.is_filtering => Some(filtered_collections), Some(filtered_collections) if !app.data.radarr_data.is_filtering => Some(filtered_collections),
_ => Some(&mut app.data.radarr_data.collections), _ => Some(&mut app.data.radarr_data.collections),
}; };
draw_table( let collections_table_footer = app
f, .data
area, .radarr_data
layout_block_top_border(), .main_tabs
TableProps { .get_active_tab_contextual_help();
content, let collection_row_mapping = |collection: &Collection| {
wrapped_content: None, let number_of_movies = collection.movies.clone().unwrap_or_default().len();
table_headers: vec![ collection.title.scroll_left_or_reset(
"Collection", get_width_from_percentage(area, 25),
"Number of Movies", *collection == current_selection,
"Root Folder Path", app.tick_count % app.ticks_until_scroll == 0,
"Quality Profile", );
"Search on Add", let monitored = if collection.monitored { "🏷" } else { "" };
"Monitored", let search_on_add = if collection.search_on_add {
], "Yes"
constraints: vec![ } else {
Constraint::Percentage(25), "No"
Constraint::Percentage(15), };
Constraint::Percentage(15),
Constraint::Percentage(15),
Constraint::Percentage(15),
Constraint::Percentage(15),
],
help: app
.data
.radarr_data
.main_tabs
.get_active_tab_contextual_help(),
},
|collection| {
let number_of_movies = collection.movies.clone().unwrap_or_default().len();
collection.title.scroll_left_or_reset(
get_width_from_percentage(area, 25),
*collection == current_selection,
app.tick_count % app.ticks_until_scroll == 0,
);
let monitored = if collection.monitored { "🏷" } else { "" };
let search_on_add = if collection.search_on_add {
"Yes"
} else {
"No"
};
Row::new(vec![ Row::new(vec![
Cell::from(collection.title.to_string()), Cell::from(collection.title.to_string()),
Cell::from(number_of_movies.to_string()), Cell::from(number_of_movies.to_string()),
Cell::from(collection.root_folder_path.clone().unwrap_or_default()), Cell::from(collection.root_folder_path.clone().unwrap_or_default()),
Cell::from( Cell::from(
quality_profile_map quality_profile_map
.get_by_left(&collection.quality_profile_id) .get_by_left(&collection.quality_profile_id)
.unwrap() .unwrap()
.to_owned(), .to_owned(),
), ),
Cell::from(search_on_add), Cell::from(search_on_add),
Cell::from(monitored), Cell::from(monitored),
]) ])
.primary() .primary()
}, };
app.is_loading, let collections_table = ManagarrTable::new(content, collection_row_mapping)
true, .loading(app.is_loading)
); .footer(collections_table_footer)
.block(layout_block_top_border())
.headers([
"Collection",
"Number of Movies",
"Root Folder Path",
"Quality Profile",
"Search on Add",
"Monitored",
])
.constraints([
Constraint::Percentage(25),
Constraint::Percentage(15),
Constraint::Percentage(15),
Constraint::Percentage(15),
Constraint::Percentage(15),
Constraint::Percentage(15),
]);
f.render_widget(collections_table, area);
} }
fn draw_update_all_collections_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { fn draw_update_all_collections_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
+66 -67
View File
@@ -8,7 +8,8 @@ use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLO
use crate::models::{HorizontallyScrollableText, Route}; use crate::models::{HorizontallyScrollableText, Route};
use crate::ui::styles::ManagarrStyle; use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{get_width_from_percentage, layout_block_top_border}; use crate::ui::utils::{get_width_from_percentage, layout_block_top_border};
use crate::ui::{draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps}; use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::{draw_prompt_box, draw_prompt_popup_over, DrawUi};
use crate::utils::convert_to_gb; use crate::utils::convert_to_gb;
#[cfg(test)] #[cfg(test)]
@@ -48,76 +49,74 @@ fn draw_downloads(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
} else { } else {
app.data.radarr_data.downloads.current_selection().clone() app.data.radarr_data.downloads.current_selection().clone()
}; };
let downloads_table_footer = app
.data
.radarr_data
.main_tabs
.get_active_tab_contextual_help();
draw_table( let downloads_row_mapping = |download_record: &DownloadRecord| {
f, let DownloadRecord {
area, title,
layout_block_top_border(), size,
TableProps { sizeleft,
content: Some(&mut app.data.radarr_data.downloads), download_client,
wrapped_content: None, indexer,
table_headers: vec![ output_path,
"Title", ..
"Percent Complete", } = download_record;
"Size",
"Output Path",
"Indexer",
"Download Client",
],
constraints: vec![
Constraint::Percentage(30),
Constraint::Percentage(11),
Constraint::Percentage(11),
Constraint::Percentage(18),
Constraint::Percentage(17),
Constraint::Percentage(13),
],
help: app
.data
.radarr_data
.main_tabs
.get_active_tab_contextual_help(),
},
|download_record| {
let DownloadRecord {
title,
size,
sizeleft,
download_client,
indexer,
output_path,
..
} = download_record;
if output_path.is_some() { if output_path.is_some() {
output_path.as_ref().unwrap().scroll_left_or_reset( output_path.as_ref().unwrap().scroll_left_or_reset(
get_width_from_percentage(area, 18), get_width_from_percentage(area, 18),
current_selection == *download_record, current_selection == *download_record,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count % app.ticks_until_scroll == 0,
); );
} }
let percent = 1f64 - (*sizeleft as f64 / *size as f64); let percent = 1f64 - (*sizeleft as f64 / *size as f64);
let file_size: f64 = convert_to_gb(*size); let file_size: f64 = convert_to_gb(*size);
Row::new(vec![ Row::new(vec![
Cell::from(title.to_owned()), Cell::from(title.to_owned()),
Cell::from(format!("{:.0}%", percent * 100.0)), Cell::from(format!("{:.0}%", percent * 100.0)),
Cell::from(format!("{file_size:.2} GB")), Cell::from(format!("{file_size:.2} GB")),
Cell::from( Cell::from(
output_path output_path
.as_ref() .as_ref()
.unwrap_or(&HorizontallyScrollableText::default()) .unwrap_or(&HorizontallyScrollableText::default())
.to_string(), .to_string(),
), ),
Cell::from(indexer.to_owned()), Cell::from(indexer.to_owned()),
Cell::from(download_client.to_owned()), Cell::from(download_client.to_owned()),
]) ])
.primary() .primary()
}, };
app.is_loading, let downloads_table = ManagarrTable::new(
true, Some(&mut app.data.radarr_data.downloads),
); downloads_row_mapping,
)
.block(layout_block_top_border())
.loading(app.is_loading)
.footer(downloads_table_footer)
.headers([
"Title",
"Percent Complete",
"Size",
"Output Path",
"Indexer",
"Download Client",
])
.constraints([
Constraint::Percentage(30),
Constraint::Percentage(11),
Constraint::Percentage(11),
Constraint::Percentage(18),
Constraint::Percentage(17),
Constraint::Percentage(13),
]);
f.render_widget(downloads_table, area);
} }
fn draw_delete_download_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { fn draw_delete_download_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
+3 -2
View File
@@ -8,7 +8,8 @@ use crate::ui::utils::title_block_centered;
use crate::ui::widgets::button::Button; use crate::ui::widgets::button::Button;
use crate::ui::widgets::checkbox::Checkbox; use crate::ui::widgets::checkbox::Checkbox;
use crate::ui::widgets::input_box::InputBox; use crate::ui::widgets::input_box::InputBox;
use crate::ui::{draw_popup_over, loading, DrawUi}; use crate::ui::widgets::loading_block::LoadingBlock;
use crate::ui::{draw_popup_over, DrawUi};
use ratatui::layout::{Constraint, Flex, Layout, Rect}; use ratatui::layout::{Constraint, Flex, Layout, Rect};
use ratatui::Frame; use ratatui::Frame;
@@ -160,6 +161,6 @@ fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
f.render_widget(cancel_button, cancel_area); f.render_widget(cancel_button, cancel_area);
} }
} else { } else {
loading(f, block, area, app.is_loading); f.render_widget(LoadingBlock::new(app.is_loading, block), area);
} }
} }
@@ -13,7 +13,8 @@ use crate::ui::utils::title_block_centered;
use crate::ui::widgets::button::Button; use crate::ui::widgets::button::Button;
use crate::ui::widgets::checkbox::Checkbox; use crate::ui::widgets::checkbox::Checkbox;
use crate::ui::widgets::input_box::InputBox; use crate::ui::widgets::input_box::InputBox;
use crate::ui::{draw_popup_over, loading, DrawUi}; use crate::ui::widgets::loading_block::LoadingBlock;
use crate::ui::{draw_popup_over, DrawUi};
#[cfg(test)] #[cfg(test)]
#[path = "indexer_settings_ui_tests.rs"] #[path = "indexer_settings_ui_tests.rs"]
@@ -155,6 +156,6 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area:
f.render_widget(save_button, save_area); f.render_widget(save_button, save_area);
f.render_widget(cancel_button, cancel_area); f.render_widget(cancel_button, cancel_area);
} else { } else {
loading(f, block, area, app.is_loading); f.render_widget(LoadingBlock::new(app.is_loading, block), area);
} }
} }
+73 -74
View File
@@ -12,7 +12,8 @@ use crate::ui::radarr_ui::indexers::indexer_settings_ui::IndexerSettingsUi;
use crate::ui::radarr_ui::indexers::test_all_indexers_ui::TestAllIndexersUi; use crate::ui::radarr_ui::indexers::test_all_indexers_ui::TestAllIndexersUi;
use crate::ui::styles::ManagarrStyle; use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::layout_block_top_border; use crate::ui::utils::layout_block_top_border;
use crate::ui::{draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps}; use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::{draw_prompt_box, draw_prompt_popup_over, DrawUi};
mod edit_indexer_ui; mod edit_indexer_ui;
mod indexer_settings_ui; mod indexer_settings_ui;
@@ -59,83 +60,81 @@ impl DrawUi for IndexersUi {
} }
fn draw_indexers(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { fn draw_indexers(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
draw_table( let indexers_row_mapping = |indexer: &'_ Indexer| {
f, let Indexer {
area, name,
layout_block_top_border(), enable_rss,
TableProps { enable_automatic_search,
content: Some(&mut app.data.radarr_data.indexers), enable_interactive_search,
wrapped_content: None, priority,
table_headers: vec![ tags,
"Indexer", ..
"RSS", } = indexer;
"Automatic Search", let bool_to_text = |flag: bool| {
"Interactive Search", if flag {
"Priority", return Text::from("Enabled").success();
"Tags", }
],
constraints: vec![
Constraint::Percentage(25),
Constraint::Percentage(13),
Constraint::Percentage(13),
Constraint::Percentage(13),
Constraint::Percentage(13),
Constraint::Percentage(23),
],
help: app
.data
.radarr_data
.main_tabs
.get_active_tab_contextual_help(),
},
|indexer: &'_ Indexer| {
let Indexer {
name,
enable_rss,
enable_automatic_search,
enable_interactive_search,
priority,
tags,
..
} = indexer;
let bool_to_text = |flag: bool| {
if flag {
return Text::from("Enabled").success();
}
Text::from("Disabled").failure() Text::from("Disabled").failure()
}; };
let rss = bool_to_text(*enable_rss); let rss = bool_to_text(*enable_rss);
let automatic_search = bool_to_text(*enable_automatic_search); let automatic_search = bool_to_text(*enable_automatic_search);
let interactive_search = bool_to_text(*enable_interactive_search); let interactive_search = bool_to_text(*enable_interactive_search);
let tags: String = tags let tags: String = tags
.iter() .iter()
.map(|tag_id| { .map(|tag_id| {
app app
.data .data
.radarr_data .radarr_data
.tags_map .tags_map
.get_by_left(&tag_id.as_i64().unwrap()) .get_by_left(&tag_id.as_i64().unwrap())
.unwrap() .unwrap()
.clone() .clone()
}) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", "); .join(", ");
Row::new(vec![ Row::new(vec![
Cell::from(name.clone().unwrap_or_default()), Cell::from(name.clone().unwrap_or_default()),
Cell::from(rss), Cell::from(rss),
Cell::from(automatic_search), Cell::from(automatic_search),
Cell::from(interactive_search), Cell::from(interactive_search),
Cell::from(priority.to_string()), Cell::from(priority.to_string()),
Cell::from(tags), Cell::from(tags),
]) ])
.primary() .primary()
}, };
app.is_loading, let indexers_table_footer = app
true, .data
.radarr_data
.main_tabs
.get_active_tab_contextual_help();
let indexers_table = ManagarrTable::new(
Some(&mut app.data.radarr_data.indexers),
indexers_row_mapping,
) )
.block(layout_block_top_border())
.footer(indexers_table_footer)
.loading(app.is_loading)
.headers([
"Indexer",
"RSS",
"Automatic Search",
"Interactive Search",
"Priority",
"Tags",
])
.constraints([
Constraint::Percentage(25),
Constraint::Percentage(13),
Constraint::Percentage(13),
Constraint::Percentage(13),
Constraint::Percentage(13),
Constraint::Percentage(23),
]);
f.render_widget(indexers_table, area);
} }
fn draw_delete_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { fn draw_delete_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
@@ -6,10 +6,9 @@ use crate::models::Route;
use crate::ui::radarr_ui::indexers::draw_indexers; use crate::ui::radarr_ui::indexers::draw_indexers;
use crate::ui::styles::ManagarrStyle; use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{borderless_block, get_width_from_percentage, title_block}; use crate::ui::utils::{borderless_block, get_width_from_percentage, title_block};
use crate::ui::{ use crate::ui::widgets::managarr_table::ManagarrTable;
draw_help_footer_and_get_content_area, draw_large_popup_over, draw_table, DrawUi, TableProps, use crate::ui::{draw_large_popup_over, DrawUi};
}; use ratatui::layout::{Alignment, Constraint, Rect};
use ratatui::layout::{Constraint, Rect};
use ratatui::widgets::{Cell, Row}; use ratatui::widgets::{Cell, Row};
use ratatui::Frame; use ratatui::Frame;
@@ -47,47 +46,45 @@ fn draw_test_all_indexers_test_results(f: &mut Frame<'_>, app: &mut App<'_>, are
IndexerTestResultModalItem::default() IndexerTestResultModalItem::default()
}; };
f.render_widget(title_block("Test All Indexers"), area); f.render_widget(title_block("Test All Indexers"), area);
let help = Some(format!( let help_footer = format!(
"<↑↓> scroll | {}", "<↑↓> scroll | {}",
build_context_clue_string(&BARE_POPUP_CONTEXT_CLUES) build_context_clue_string(&BARE_POPUP_CONTEXT_CLUES)
));
let content_area = draw_help_footer_and_get_content_area(f, area, help);
draw_table(
f,
content_area,
borderless_block(),
TableProps {
content: app.data.radarr_data.indexer_test_all_results.as_mut(),
wrapped_content: None,
table_headers: vec!["Indexer", "Pass/Fail", "Failure Messages"],
constraints: vec![
Constraint::Percentage(20),
Constraint::Percentage(10),
Constraint::Percentage(70),
],
help: None,
},
|result| {
result.validation_failures.scroll_left_or_reset(
get_width_from_percentage(area, 86),
*result == current_selection,
app.tick_count % app.ticks_until_scroll == 0,
);
let pass_fail = if result.is_valid { "" } else { "" };
let row = Row::new(vec![
Cell::from(result.name.to_owned()),
Cell::from(pass_fail.to_owned()),
Cell::from(result.validation_failures.to_string()),
]);
if result.is_valid {
row.success()
} else {
row.failure()
}
},
app.is_loading,
true,
); );
let test_results_row_mapping = |result: &IndexerTestResultModalItem| {
result.validation_failures.scroll_left_or_reset(
get_width_from_percentage(area, 86),
*result == current_selection,
app.tick_count % app.ticks_until_scroll == 0,
);
let pass_fail = if result.is_valid { "" } else { "" };
let row = Row::new(vec![
Cell::from(result.name.to_owned()),
Cell::from(pass_fail.to_owned()),
Cell::from(result.validation_failures.to_string()),
]);
if result.is_valid {
row.success()
} else {
row.failure()
}
};
let indexers_test_results_table = ManagarrTable::new(
app.data.radarr_data.indexer_test_all_results.as_mut(),
test_results_row_mapping,
)
.block(borderless_block())
.loading(app.is_loading)
.footer(Some(help_footer))
.footer_alignment(Alignment::Center)
.margin(1)
.headers(["Indexer", "Pass/Fail", "Failure Messages"])
.constraints([
Constraint::Percentage(20),
Constraint::Percentage(10),
Constraint::Percentage(70),
]);
f.render_widget(indexers_test_results_table, area);
} }
+92 -90
View File
@@ -18,9 +18,10 @@ use crate::ui::utils::{
}; };
use crate::ui::widgets::button::Button; use crate::ui::widgets::button::Button;
use crate::ui::widgets::input_box::InputBox; use crate::ui::widgets::input_box::InputBox;
use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::{ use crate::ui::{
draw_drop_down_popup, draw_error_popup, draw_error_popup_over, draw_large_popup_over, draw_drop_down_popup, draw_error_popup, draw_error_popup_over, draw_large_popup_over,
draw_medium_popup_over, draw_selectable_list, draw_table, DrawUi, TableProps, draw_medium_popup_over, draw_selectable_list, DrawUi,
}; };
use crate::utils::convert_runtime; use crate::utils::convert_runtime;
use crate::{render_selectable_input_box, App}; use crate::{render_selectable_input_box, App};
@@ -116,6 +117,64 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.unwrap() .unwrap()
.offset .offset
.borrow(); .borrow();
let search_results_row_mapping = |movie: &AddMovieSearchResult| {
let (hours, minutes) = convert_runtime(movie.runtime);
let imdb_rating = movie
.ratings
.imdb
.clone()
.unwrap_or_default()
.value
.as_f64()
.unwrap();
let rotten_tomatoes_rating = movie
.ratings
.rotten_tomatoes
.clone()
.unwrap_or_default()
.value
.as_u64()
.unwrap();
let imdb_rating = if imdb_rating == 0.0 {
String::new()
} else {
format!("{imdb_rating:.1}")
};
let rotten_tomatoes_rating = if rotten_tomatoes_rating == 0 {
String::new()
} else {
format!("{rotten_tomatoes_rating}%")
};
let in_library = if app
.data
.radarr_data
.movies
.items
.iter()
.any(|mov| mov.tmdb_id == movie.tmdb_id)
{
""
} else {
""
};
movie.title.scroll_left_or_reset(
get_width_from_percentage(area, 27),
*movie == current_selection,
app.tick_count % app.ticks_until_scroll == 0,
);
Row::new(vec![
Cell::from(in_library),
Cell::from(movie.title.to_string()),
Cell::from(movie.year.to_string()),
Cell::from(format!("{hours}h {minutes}m")),
Cell::from(imdb_rating),
Cell::from(rotten_tomatoes_rating),
Cell::from(movie.genres.join(", ")),
])
.primary()
};
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
match active_radarr_block { match active_radarr_block {
@@ -134,8 +193,14 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
f.render_widget(help_paragraph, help_area); f.render_widget(help_paragraph, help_area);
} }
ActiveRadarrBlock::AddMovieEmptySearchResults => { ActiveRadarrBlock::AddMovieEmptySearchResults => {
let help_text = Text::from(build_context_clue_string(&BARE_POPUP_CONTEXT_CLUES).help());
let help_paragraph = Paragraph::new(help_text)
.block(borderless_block())
.alignment(Alignment::Center);
f.render_widget(layout_block(), results_area); f.render_widget(layout_block(), results_area);
draw_error_popup(f, "No movies found matching your query!"); draw_error_popup(f, "No movies found matching your query!");
f.render_widget(help_paragraph, help_area);
} }
ActiveRadarrBlock::AddMovieSearchResults ActiveRadarrBlock::AddMovieSearchResults
| ActiveRadarrBlock::AddMoviePrompt | ActiveRadarrBlock::AddMoviePrompt
@@ -150,96 +215,33 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let help_paragraph = Paragraph::new(help_text) let help_paragraph = Paragraph::new(help_text)
.block(borderless_block()) .block(borderless_block())
.alignment(Alignment::Center); .alignment(Alignment::Center);
let search_results_table = ManagarrTable::new(
app.data.radarr_data.add_searched_movies.as_mut(),
search_results_row_mapping,
)
.loading(is_loading)
.block(layout_block())
.headers([
"",
"Title",
"Year",
"Runtime",
"IMDB",
"Rotten Tomatoes",
"Genres",
])
.constraints([
Constraint::Percentage(2),
Constraint::Percentage(27),
Constraint::Percentage(8),
Constraint::Percentage(10),
Constraint::Percentage(8),
Constraint::Percentage(14),
Constraint::Percentage(28),
]);
f.render_widget(search_results_table, results_area);
f.render_widget(help_paragraph, help_area); f.render_widget(help_paragraph, help_area);
draw_table(
f,
results_area,
layout_block(),
TableProps {
content: None,
wrapped_content: Some(app.data.radarr_data.add_searched_movies.as_mut()),
table_headers: vec![
"",
"Title",
"Year",
"Runtime",
"IMDB",
"Rotten Tomatoes",
"Genres",
],
constraints: vec![
Constraint::Percentage(2),
Constraint::Percentage(27),
Constraint::Percentage(8),
Constraint::Percentage(10),
Constraint::Percentage(8),
Constraint::Percentage(14),
Constraint::Percentage(28),
],
help: None,
},
|movie| {
let (hours, minutes) = convert_runtime(movie.runtime);
let imdb_rating = movie
.ratings
.imdb
.clone()
.unwrap_or_default()
.value
.as_f64()
.unwrap();
let rotten_tomatoes_rating = movie
.ratings
.rotten_tomatoes
.clone()
.unwrap_or_default()
.value
.as_u64()
.unwrap();
let imdb_rating = if imdb_rating == 0.0 {
String::new()
} else {
format!("{imdb_rating:.1}")
};
let rotten_tomatoes_rating = if rotten_tomatoes_rating == 0 {
String::new()
} else {
format!("{rotten_tomatoes_rating}%")
};
let in_library = if app
.data
.radarr_data
.movies
.items
.iter()
.any(|mov| mov.tmdb_id == movie.tmdb_id)
{
""
} else {
""
};
movie.title.scroll_left_or_reset(
get_width_from_percentage(area, 27),
*movie == current_selection,
app.tick_count % app.ticks_until_scroll == 0,
);
Row::new(vec![
Cell::from(in_library),
Cell::from(movie.title.to_string()),
Cell::from(movie.year.to_string()),
Cell::from(format!("{hours}h {minutes}m")),
Cell::from(imdb_rating),
Cell::from(rotten_tomatoes_rating),
Cell::from(movie.genres.join(", ")),
])
.primary()
},
is_loading,
true,
);
} }
_ => (), _ => (),
} }
+79 -83
View File
@@ -12,9 +12,10 @@ use crate::ui::radarr_ui::library::delete_movie_ui::DeleteMovieUi;
use crate::ui::radarr_ui::library::edit_movie_ui::EditMovieUi; use crate::ui::radarr_ui::library::edit_movie_ui::EditMovieUi;
use crate::ui::radarr_ui::library::movie_details_ui::MovieDetailsUi; use crate::ui::radarr_ui::library::movie_details_ui::MovieDetailsUi;
use crate::ui::utils::{get_width_from_percentage, layout_block_top_border}; use crate::ui::utils::{get_width_from_percentage, layout_block_top_border};
use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::{ use crate::ui::{
draw_error_message_popup, draw_input_box_popup, draw_popup_over, draw_prompt_box, draw_error_message_popup, draw_input_box_popup, draw_popup_over, draw_prompt_box,
draw_prompt_popup_over, draw_table, DrawUi, TableProps, draw_prompt_popup_over, DrawUi,
}; };
use crate::utils::{convert_runtime, convert_to_gb}; use crate::utils::{convert_runtime, convert_to_gb};
@@ -106,90 +107,85 @@ pub(super) fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
Some(filtered_movies) if !app.data.radarr_data.is_filtering => Some(filtered_movies), Some(filtered_movies) if !app.data.radarr_data.is_filtering => Some(filtered_movies),
_ => Some(&mut app.data.radarr_data.movies), _ => Some(&mut app.data.radarr_data.movies),
}; };
let help_footer = app
.data
.radarr_data
.main_tabs
.get_active_tab_contextual_help();
draw_table( let library_table_row_mapping = |movie: &Movie| {
f, movie.title.scroll_left_or_reset(
area, get_width_from_percentage(area, 27),
layout_block_top_border(), *movie == current_selection,
TableProps { app.tick_count % app.ticks_until_scroll == 0,
content, );
wrapped_content: None, let monitored = if movie.monitored { "🏷" } else { "" };
table_headers: vec![ let (hours, minutes) = convert_runtime(movie.runtime);
"Title", let file_size: f64 = convert_to_gb(movie.size_on_disk);
"Year", let certification = movie.certification.clone().unwrap_or_default();
"Studio", let quality_profile = quality_profile_map
"Runtime", .get_by_left(&movie.quality_profile_id)
"Rating", .unwrap()
"Language", .to_owned();
"Size", let tags = movie
"Quality Profile", .tags
"Monitored", .iter()
"Tags", .map(|tag_id| {
], tags_map
constraints: vec![ .get_by_left(&tag_id.as_i64().unwrap())
Constraint::Percentage(27), .unwrap()
Constraint::Percentage(4), .clone()
Constraint::Percentage(17), })
Constraint::Percentage(6), .collect::<Vec<String>>()
Constraint::Percentage(6), .join(", ");
Constraint::Percentage(6),
Constraint::Percentage(6),
Constraint::Percentage(10),
Constraint::Percentage(6),
Constraint::Percentage(12),
],
help: app
.data
.radarr_data
.main_tabs
.get_active_tab_contextual_help(),
},
|movie| {
movie.title.scroll_left_or_reset(
get_width_from_percentage(area, 27),
*movie == current_selection,
app.tick_count % app.ticks_until_scroll == 0,
);
let monitored = if movie.monitored { "🏷" } else { "" };
let (hours, minutes) = convert_runtime(movie.runtime);
let file_size: f64 = convert_to_gb(movie.size_on_disk);
let certification = movie.certification.clone().unwrap_or_default();
let quality_profile = quality_profile_map
.get_by_left(&movie.quality_profile_id)
.unwrap()
.to_owned();
let tags = movie
.tags
.iter()
.map(|tag_id| {
tags_map
.get_by_left(&tag_id.as_i64().unwrap())
.unwrap()
.clone()
})
.collect::<Vec<String>>()
.join(", ");
decorate_with_row_style( decorate_with_row_style(
downloads_vec, downloads_vec,
movie, movie,
Row::new(vec![ Row::new(vec![
Cell::from(movie.title.to_string()), Cell::from(movie.title.to_string()),
Cell::from(movie.year.to_string()), Cell::from(movie.year.to_string()),
Cell::from(movie.studio.to_string()), Cell::from(movie.studio.to_string()),
Cell::from(format!("{hours}h {minutes}m")), Cell::from(format!("{hours}h {minutes}m")),
Cell::from(certification), Cell::from(certification),
Cell::from(movie.original_language.name.to_owned()), Cell::from(movie.original_language.name.to_owned()),
Cell::from(format!("{file_size:.2} GB")), Cell::from(format!("{file_size:.2} GB")),
Cell::from(quality_profile), Cell::from(quality_profile),
Cell::from(monitored.to_owned()), Cell::from(monitored.to_owned()),
Cell::from(tags), Cell::from(tags),
]), ]),
) )
}, };
app.is_loading, let library_table = ManagarrTable::new(content, library_table_row_mapping)
true, .block(layout_block_top_border())
); .loading(app.is_loading)
.footer(help_footer)
.headers([
"Title",
"Year",
"Studio",
"Runtime",
"Rating",
"Language",
"Size",
"Quality Profile",
"Monitored",
"Tags",
])
.constraints([
Constraint::Percentage(27),
Constraint::Percentage(4),
Constraint::Percentage(17),
Constraint::Percentage(6),
Constraint::Percentage(6),
Constraint::Percentage(6),
Constraint::Percentage(6),
Constraint::Percentage(10),
Constraint::Percentage(6),
Constraint::Percentage(12),
]);
f.render_widget(library_table, area);
} }
fn draw_update_all_movies_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { fn draw_update_all_movies_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
+211 -222
View File
@@ -15,10 +15,11 @@ use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{ use crate::ui::utils::{
borderless_block, get_width_from_percentage, layout_block_bottom_border, layout_block_top_border, borderless_block, get_width_from_percentage, layout_block_bottom_border, layout_block_top_border,
}; };
use crate::ui::widgets::loading_block::LoadingBlock;
use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::{ use crate::ui::{
draw_drop_down_popup, draw_large_popup_over, draw_prompt_box, draw_prompt_box_with_content, draw_drop_down_popup, draw_large_popup_over, draw_prompt_box, draw_prompt_box_with_content,
draw_prompt_popup_over, draw_selectable_list, draw_small_popup_over, draw_table, draw_tabs, draw_prompt_popup_over, draw_selectable_list, draw_small_popup_over, draw_tabs, DrawUi,
loading, DrawUi, TableProps,
}; };
use crate::utils::convert_to_gb; use crate::utils::convert_to_gb;
@@ -189,7 +190,10 @@ fn draw_file_info(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
f.render_widget(video_details_title_paragraph, video_details_title_area); f.render_widget(video_details_title_paragraph, video_details_title_area);
f.render_widget(video_details_paragraph, video_details_area); f.render_widget(video_details_paragraph, video_details_area);
} }
_ => loading(f, layout_block_top_border(), area, app.is_loading), _ => f.render_widget(
LoadingBlock::new(app.is_loading, layout_block_top_border()),
area,
),
} }
} }
@@ -230,11 +234,12 @@ fn draw_movie_details(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
f.render_widget(paragraph, area); f.render_widget(paragraph, area);
} }
_ => loading( _ => f.render_widget(
f, LoadingBlock::new(
block, app.is_loading || app.data.radarr_data.movie_details_modal.is_none(),
block,
),
area, area,
app.is_loading || app.data.radarr_data.movie_details_modal.is_none(),
), ),
} }
} }
@@ -249,148 +254,137 @@ fn draw_movie_history(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.current_selection() .current_selection()
.clone() .clone()
}; };
let history_row_mapping = |movie_history_item: &MovieHistoryItem| {
let MovieHistoryItem {
source_title,
quality,
languages,
date,
event_type,
} = movie_history_item;
draw_table( movie_history_item.source_title.scroll_left_or_reset(
f, get_width_from_percentage(area, 34),
area, current_selection == *movie_history_item,
layout_block_top_border(), app.tick_count % app.ticks_until_scroll == 0,
TableProps { );
content: Some(&mut movie_details_modal.movie_history),
wrapped_content: None,
table_headers: vec!["Source Title", "Event Type", "Languages", "Quality", "Date"],
constraints: vec![
Constraint::Percentage(34),
Constraint::Percentage(17),
Constraint::Percentage(14),
Constraint::Percentage(14),
Constraint::Percentage(21),
],
help: app
.data
.radarr_data
.movie_info_tabs
.get_active_tab_contextual_help(),
},
|movie_history_item| {
let MovieHistoryItem {
source_title,
quality,
languages,
date,
event_type,
} = movie_history_item;
movie_history_item.source_title.scroll_left_or_reset( Row::new(vec![
get_width_from_percentage(area, 34), Cell::from(source_title.to_string()),
current_selection == *movie_history_item, Cell::from(event_type.to_owned()),
app.tick_count % app.ticks_until_scroll == 0, Cell::from(
); languages
.iter()
.map(|language| language.name.to_owned())
.collect::<Vec<String>>()
.join(","),
),
Cell::from(quality.quality.name.to_owned()),
Cell::from(date.to_string()),
])
.success()
};
let help_footer = app
.data
.radarr_data
.movie_info_tabs
.get_active_tab_contextual_help();
let history_table = ManagarrTable::new(
Some(&mut movie_details_modal.movie_history),
history_row_mapping,
)
.block(layout_block_top_border())
.loading(app.is_loading)
.footer(help_footer)
.headers(["Source Title", "Event Type", "Languages", "Quality", "Date"])
.constraints([
Constraint::Percentage(34),
Constraint::Percentage(17),
Constraint::Percentage(14),
Constraint::Percentage(14),
Constraint::Percentage(21),
]);
Row::new(vec![ f.render_widget(history_table, area);
Cell::from(source_title.to_string()),
Cell::from(event_type.to_owned()),
Cell::from(
languages
.iter()
.map(|language| language.name.to_owned())
.collect::<Vec<String>>()
.join(","),
),
Cell::from(quality.quality.name.to_owned()),
Cell::from(date.to_string()),
])
.success()
},
app.is_loading,
true,
);
} }
} }
fn draw_movie_cast(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { fn draw_movie_cast(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
draw_table( let cast_row_mapping = |cast_member: &Credit| {
f, let Credit {
area, person_name,
layout_block_top_border(), character,
TableProps { ..
content: Some( } = cast_member;
&mut app
.data
.radarr_data
.movie_details_modal
.as_mut()
.unwrap()
.movie_cast,
),
wrapped_content: None,
constraints: iter::repeat(Constraint::Ratio(1, 2)).take(2).collect(),
table_headers: vec!["Cast Member", "Character"],
help: app
.data
.radarr_data
.movie_info_tabs
.get_active_tab_contextual_help(),
},
|cast_member| {
let Credit {
person_name,
character,
..
} = cast_member;
Row::new(vec![ Row::new(vec![
Cell::from(person_name.to_owned()), Cell::from(person_name.to_owned()),
Cell::from(character.clone().unwrap_or_default()), Cell::from(character.clone().unwrap_or_default()),
]) ])
.success() .success()
}, };
app.is_loading, let content = Some(
true, &mut app
.data
.radarr_data
.movie_details_modal
.as_mut()
.unwrap()
.movie_cast,
); );
let help_footer = app
.data
.radarr_data
.movie_info_tabs
.get_active_tab_contextual_help();
let cast_table = ManagarrTable::new(content, cast_row_mapping)
.block(layout_block_top_border())
.footer(help_footer)
.loading(app.is_loading)
.headers(["Cast Member", "Character"])
.constraints([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]);
f.render_widget(cast_table, area);
} }
fn draw_movie_crew(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { fn draw_movie_crew(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
draw_table( let crew_row_mapping = |crew_member: &Credit| {
f, let Credit {
area, person_name,
layout_block_top_border(), job,
TableProps { department,
content: Some( ..
&mut app } = crew_member;
.data
.radarr_data
.movie_details_modal
.as_mut()
.unwrap()
.movie_crew,
),
wrapped_content: None,
constraints: iter::repeat(Constraint::Ratio(1, 3)).take(3).collect(),
table_headers: vec!["Crew Member", "Job", "Department"],
help: app
.data
.radarr_data
.movie_info_tabs
.get_active_tab_contextual_help(),
},
|crew_member| {
let Credit {
person_name,
job,
department,
..
} = crew_member;
Row::new(vec![ Row::new(vec![
Cell::from(person_name.to_owned()), Cell::from(person_name.to_owned()),
Cell::from(job.clone().unwrap_or_default()), Cell::from(job.clone().unwrap_or_default()),
Cell::from(department.clone().unwrap_or_default()), Cell::from(department.clone().unwrap_or_default()),
]) ])
.success() .success()
}, };
app.is_loading, let content = Some(
true, &mut app
.data
.radarr_data
.movie_details_modal
.as_mut()
.unwrap()
.movie_crew,
); );
let help_footer = app
.data
.radarr_data
.movie_info_tabs
.get_active_tab_contextual_help();
let crew_table = ManagarrTable::new(content, crew_row_mapping)
.block(layout_block_top_border())
.loading(app.is_loading)
.headers(["Crew Member", "Job", "Department"])
.constraints(iter::repeat(Constraint::Ratio(1, 3)).take(3))
.footer(help_footer);
f.render_widget(crew_table, area);
} }
fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
@@ -407,6 +401,11 @@ fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
_ => (Release::default(), true, None), _ => (Release::default(), true, None),
}; };
let current_route = *app.get_current_route(); let current_route = *app.get_current_route();
let help_footer = app
.data
.radarr_data
.movie_info_tabs
.get_active_tab_contextual_help();
let mut table_headers_vec = vec![ let mut table_headers_vec = vec![
"Source".to_owned(), "Source".to_owned(),
"Age".to_owned(), "Age".to_owned(),
@@ -442,99 +441,89 @@ fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
ReleaseField::Quality => table_headers_vec[8].push_str(direction), ReleaseField::Quality => table_headers_vec[8].push_str(direction),
} }
} }
let content = Some(
&mut app
.data
.radarr_data
.movie_details_modal
.as_mut()
.unwrap()
.movie_releases,
);
let releases_row_mapping = |release: &Release| {
let Release {
protocol,
age,
title,
indexer,
size,
rejected,
seeders,
leechers,
languages,
quality,
..
} = release;
let age = format!("{age} days");
title.scroll_left_or_reset(
get_width_from_percentage(area, 30),
current_selection == *release
&& current_route != ActiveRadarrBlock::ManualSearchConfirmPrompt.into(),
app.tick_count % app.ticks_until_scroll == 0,
);
let size = convert_to_gb(*size);
let rejected_str = if *rejected { "" } else { "" };
let peers = if seeders.is_none() || leechers.is_none() {
Text::from("")
} else {
let seeders = seeders.clone().unwrap().as_u64().unwrap();
let leechers = leechers.clone().unwrap().as_u64().unwrap();
draw_table( decorate_peer_style(
f,
area,
layout_block_top_border(),
TableProps {
content: Some(
&mut app
.data
.radarr_data
.movie_details_modal
.as_mut()
.unwrap()
.movie_releases,
),
wrapped_content: None,
constraints: vec![
Constraint::Length(9),
Constraint::Length(10),
Constraint::Length(5),
Constraint::Percentage(30),
Constraint::Percentage(18),
Constraint::Length(12),
Constraint::Length(12),
Constraint::Percentage(7),
Constraint::Percentage(10),
],
table_headers: table_headers_vec.iter().map(|s| &**s).collect(),
help: app
.data
.radarr_data
.movie_info_tabs
.get_active_tab_contextual_help(),
},
|release| {
let Release {
protocol,
age,
title,
indexer,
size,
rejected,
seeders, seeders,
leechers, leechers,
languages, Text::from(format!("{seeders} / {leechers}")),
quality, )
.. };
} = release;
let age = format!("{age} days");
title.scroll_left_or_reset(
get_width_from_percentage(area, 30),
current_selection == *release
&& current_route != ActiveRadarrBlock::ManualSearchConfirmPrompt.into(),
app.tick_count % app.ticks_until_scroll == 0,
);
let size = convert_to_gb(*size);
let rejected_str = if *rejected { "" } else { "" };
let peers = if seeders.is_none() || leechers.is_none() {
Text::from("")
} else {
let seeders = seeders.clone().unwrap().as_u64().unwrap();
let leechers = leechers.clone().unwrap().as_u64().unwrap();
decorate_peer_style( let language = if languages.is_some() {
seeders, languages.clone().unwrap()[0].name.clone()
leechers, } else {
Text::from(format!("{seeders} / {leechers}")), String::new()
) };
}; let quality = quality.quality.name.clone();
let language = if languages.is_some() { Row::new(vec![
languages.clone().unwrap()[0].name.clone() Cell::from(protocol.clone()),
} else { Cell::from(age),
String::new() Cell::from(rejected_str),
}; Cell::from(title.to_string()),
let quality = quality.quality.name.clone(); Cell::from(indexer.clone()),
Cell::from(format!("{size:.1} GB")),
Cell::from(peers),
Cell::from(language),
Cell::from(quality),
])
.primary()
};
let releases_table = ManagarrTable::new(content, releases_row_mapping)
.block(layout_block_top_border())
.loading(app.is_loading || is_empty)
.footer(help_footer)
.headers(table_headers_vec.iter().map(|s| &**s))
.constraints([
Constraint::Length(9),
Constraint::Length(10),
Constraint::Length(5),
Constraint::Percentage(30),
Constraint::Percentage(18),
Constraint::Length(12),
Constraint::Length(12),
Constraint::Percentage(7),
Constraint::Percentage(10),
]);
Row::new(vec![ f.render_widget(releases_table, area);
Cell::from(protocol.clone()),
Cell::from(age),
Cell::from(rejected_str),
Cell::from(title.to_string()),
Cell::from(indexer.clone()),
Cell::from(format!("{size:.1} GB")),
Cell::from(peers),
Cell::from(language),
Cell::from(quality),
])
.primary()
},
app.is_loading || is_empty,
true,
);
} }
fn draw_manual_search_confirm_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { fn draw_manual_search_confirm_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
+3 -3
View File
@@ -13,7 +13,6 @@ use crate::models::radarr_models::{DiskSpace, DownloadRecord, Movie, RootFolder}
use crate::models::servarr_data::radarr::radarr_data::RadarrData; use crate::models::servarr_data::radarr::radarr_data::RadarrData;
use crate::models::Route; use crate::models::Route;
use crate::ui::draw_tabs; use crate::ui::draw_tabs;
use crate::ui::loading;
use crate::ui::radarr_ui::collections::CollectionsUi; use crate::ui::radarr_ui::collections::CollectionsUi;
use crate::ui::radarr_ui::downloads::DownloadsUi; use crate::ui::radarr_ui::downloads::DownloadsUi;
use crate::ui::radarr_ui::indexers::IndexersUi; use crate::ui::radarr_ui::indexers::IndexersUi;
@@ -24,6 +23,7 @@ use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{ use crate::ui::utils::{
borderless_block, layout_block, line_gauge_with_label, line_gauge_with_title, title_block, borderless_block, layout_block, line_gauge_with_label, line_gauge_with_title, title_block,
}; };
use crate::ui::widgets::loading_block::LoadingBlock;
use crate::ui::DrawUi; use crate::ui::DrawUi;
use crate::utils::convert_to_gb; use crate::utils::convert_to_gb;
@@ -163,7 +163,7 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
) )
} }
} else { } else {
loading(f, block, area, app.is_loading); f.render_widget(LoadingBlock::new(app.is_loading, block), area);
} }
} }
@@ -195,7 +195,7 @@ fn draw_downloads_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
f.render_widget(download_gauge, download_item_areas[i]); f.render_widget(download_gauge, download_item_areas[i]);
} }
} else { } else {
loading(f, block, area, app.is_loading); f.render_widget(LoadingBlock::new(app.is_loading, block), area);
} }
} }
+44 -45
View File
@@ -8,9 +8,9 @@ use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, ROOT_F
use crate::models::Route; use crate::models::Route;
use crate::ui::styles::ManagarrStyle; use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::layout_block_top_border; use crate::ui::utils::layout_block_top_border;
use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::{ use crate::ui::{
draw_input_box_popup, draw_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table, draw_input_box_popup, draw_popup_over, draw_prompt_box, draw_prompt_popup_over, DrawUi,
DrawUi, TableProps,
}; };
use crate::utils::convert_to_gb; use crate::utils::convert_to_gb;
@@ -56,51 +56,50 @@ impl DrawUi for RootFoldersUi {
} }
fn draw_root_folders(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { fn draw_root_folders(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
draw_table( let help_footer = app
f, .data
area, .radarr_data
layout_block_top_border(), .main_tabs
TableProps { .get_active_tab_contextual_help();
content: Some(&mut app.data.radarr_data.root_folders), let root_folders_row_mapping = |root_folders: &RootFolder| {
wrapped_content: None, let RootFolder {
table_headers: vec!["Path", "Free Space", "Unmapped Folders"], path,
constraints: vec![ free_space,
Constraint::Percentage(60), unmapped_folders,
Constraint::Percentage(20), ..
Constraint::Percentage(20), } = root_folders;
],
help: app
.data
.radarr_data
.main_tabs
.get_active_tab_contextual_help(),
},
|root_folders| {
let RootFolder {
path,
free_space,
unmapped_folders,
..
} = root_folders;
let space: f64 = convert_to_gb(*free_space); let space: f64 = convert_to_gb(*free_space);
Row::new(vec![ Row::new(vec![
Cell::from(path.to_owned()), Cell::from(path.to_owned()),
Cell::from(format!("{space:.2} GB")), Cell::from(format!("{space:.2} GB")),
Cell::from( Cell::from(
unmapped_folders unmapped_folders
.as_ref() .as_ref()
.unwrap_or(&Vec::new()) .unwrap_or(&Vec::new())
.len() .len()
.to_string(), .to_string(),
), ),
]) ])
.primary() .primary()
}, };
app.is_loading,
true, let root_folders_table = ManagarrTable::new(
); Some(&mut app.data.radarr_data.root_folders),
root_folders_row_mapping,
)
.block(layout_block_top_border())
.loading(app.is_loading)
.footer(help_footer)
.headers(["Path", "Free Space", "Unmapped Folders"])
.constraints([
Constraint::Ratio(3, 5),
Constraint::Ratio(1, 5),
Constraint::Ratio(1, 5),
]);
f.render_widget(root_folders_table, area);
} }
fn draw_add_root_folder_prompt_box(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { fn draw_add_root_folder_prompt_box(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
+72 -80
View File
@@ -11,13 +11,14 @@ use ratatui::{
}; };
use crate::app::App; use crate::app::App;
use crate::models::radarr_models::Task; use crate::models::radarr_models::{QueueEvent, Task};
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::ui::radarr_ui::radarr_ui_utils::{convert_to_minutes_hours_days, style_log_list_item}; use crate::ui::radarr_ui::radarr_ui_utils::{convert_to_minutes_hours_days, style_log_list_item};
use crate::ui::radarr_ui::system::system_details_ui::SystemDetailsUi; use crate::ui::radarr_ui::system::system_details_ui::SystemDetailsUi;
use crate::ui::styles::ManagarrStyle; use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::layout_block_top_border; use crate::ui::utils::layout_block_top_border;
use crate::ui::{draw_table, ListProps, TableProps}; use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::ListProps;
use crate::{ use crate::{
models::Route, models::Route,
ui::{draw_list_box, utils::title_block, DrawUi}, ui::{draw_list_box, utils::title_block, DrawUi},
@@ -87,92 +88,83 @@ pub(super) fn draw_system_ui_layout(f: &mut Frame<'_>, app: &mut App<'_>, area:
} }
fn draw_tasks(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { fn draw_tasks(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
draw_table( let tasks_row_mapping = |task: &Task| {
f, let task_props = extract_task_props(task);
area,
title_block("Tasks"),
TableProps {
content: Some(&mut app.data.radarr_data.tasks),
wrapped_content: None,
table_headers: TASK_TABLE_HEADERS.to_vec(),
constraints: TASK_TABLE_CONSTRAINTS.to_vec(),
help: None,
},
|task| {
let task_props = extract_task_props(task);
Row::new(vec![ Row::new(vec![
Cell::from(task_props.name), Cell::from(task_props.name),
Cell::from(task_props.interval), Cell::from(task_props.interval),
Cell::from(task_props.last_execution), Cell::from(task_props.last_execution),
Cell::from(task_props.last_duration), Cell::from(task_props.last_duration),
Cell::from(task_props.next_execution), Cell::from(task_props.next_execution),
]) ])
.primary() .primary()
}, };
app.is_loading, let tasks_table = ManagarrTable::new(Some(&mut app.data.radarr_data.tasks), tasks_row_mapping)
false, .block(title_block("Tasks"))
); .loading(app.is_loading)
.highlight_rows(false)
.headers(TASK_TABLE_HEADERS)
.constraints(TASK_TABLE_CONSTRAINTS);
f.render_widget(tasks_table, area);
} }
pub(super) fn draw_queued_events(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { pub(super) fn draw_queued_events(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
draw_table( let events_row_mapping = |event: &QueueEvent| {
f, let queued = convert_to_minutes_hours_days(Utc::now().sub(event.queued).num_minutes());
area, let queued_string = if queued != "now" {
title_block("Queued Events"), format!("{queued} ago")
TableProps { } else {
content: Some(&mut app.data.radarr_data.queued_events), queued
wrapped_content: None, };
table_headers: vec!["Trigger", "Status", "Name", "Queued", "Started", "Duration"], let started_string = if event.started.is_some() {
constraints: vec![ let started =
Constraint::Percentage(13), convert_to_minutes_hours_days(Utc::now().sub(event.started.unwrap()).num_minutes());
Constraint::Percentage(13),
Constraint::Percentage(30),
Constraint::Percentage(16),
Constraint::Percentage(14),
Constraint::Percentage(14),
],
help: None,
},
|event| {
let queued = convert_to_minutes_hours_days(Utc::now().sub(event.queued).num_minutes());
let queued_string = if queued != "now" {
format!("{queued} ago")
} else {
queued
};
let started_string = if event.started.is_some() {
let started =
convert_to_minutes_hours_days(Utc::now().sub(event.started.unwrap()).num_minutes());
if started != "now" { if started != "now" {
format!("{started} ago") format!("{started} ago")
} else {
started
}
} else { } else {
String::new() started
}; }
} else {
String::new()
};
let duration = if event.duration.is_some() { let duration = if event.duration.is_some() {
&event.duration.as_ref().unwrap()[..8] &event.duration.as_ref().unwrap()[..8]
} else { } else {
"" ""
}; };
Row::new(vec![ Row::new(vec![
Cell::from(event.trigger.clone()), Cell::from(event.trigger.clone()),
Cell::from(event.status.clone()), Cell::from(event.status.clone()),
Cell::from(event.command_name.clone()), Cell::from(event.command_name.clone()),
Cell::from(queued_string), Cell::from(queued_string),
Cell::from(started_string), Cell::from(started_string),
Cell::from(duration.to_owned()), Cell::from(duration.to_owned()),
]) ])
.primary() .primary()
}, };
app.is_loading, let events_table = ManagarrTable::new(
false, Some(&mut app.data.radarr_data.queued_events),
); events_row_mapping,
)
.block(title_block("Queued Events"))
.loading(app.is_loading)
.highlight_rows(false)
.headers(["Trigger", "Status", "Name", "Queued", "Started", "Duration"])
.constraints([
Constraint::Percentage(13),
Constraint::Percentage(13),
Constraint::Percentage(30),
Constraint::Percentage(16),
Constraint::Percentage(14),
Constraint::Percentage(14),
]);
f.render_widget(events_table, area);
} }
fn draw_logs(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { fn draw_logs(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
+34 -37
View File
@@ -1,4 +1,4 @@
use ratatui::layout::Rect; use ratatui::layout::{Alignment, Rect};
use ratatui::text::{Span, Text}; use ratatui::text::{Span, Text};
use ratatui::widgets::{Cell, ListItem, Paragraph, Row}; use ratatui::widgets::{Cell, ListItem, Paragraph, Row};
use ratatui::Frame; use ratatui::Frame;
@@ -6,6 +6,7 @@ use ratatui::Frame;
use crate::app::context_clues::{build_context_clue_string, BARE_POPUP_CONTEXT_CLUES}; use crate::app::context_clues::{build_context_clue_string, BARE_POPUP_CONTEXT_CLUES};
use crate::app::radarr::radarr_context_clues::SYSTEM_TASKS_CONTEXT_CLUES; use crate::app::radarr::radarr_context_clues::SYSTEM_TASKS_CONTEXT_CLUES;
use crate::app::App; use crate::app::App;
use crate::models::radarr_models::Task;
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::Route; use crate::models::Route;
use crate::ui::radarr_ui::radarr_ui_utils::style_log_list_item; use crate::ui::radarr_ui::radarr_ui_utils::style_log_list_item;
@@ -15,10 +16,11 @@ use crate::ui::radarr_ui::system::{
}; };
use crate::ui::styles::ManagarrStyle; use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{borderless_block, title_block}; use crate::ui::utils::{borderless_block, title_block};
use crate::ui::widgets::loading_block::LoadingBlock;
use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::{ use crate::ui::{
draw_help_footer_and_get_content_area, draw_large_popup_over, draw_list_box, draw_help_footer_and_get_content_area, draw_large_popup_over, draw_list_box,
draw_medium_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table, loading, DrawUi, draw_medium_popup_over, draw_prompt_box, draw_prompt_popup_over, DrawUi, ListProps,
ListProps, TableProps,
}; };
#[cfg(test)] #[cfg(test)]
@@ -82,40 +84,35 @@ fn draw_logs_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
fn draw_tasks_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { fn draw_tasks_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let tasks_popup_table = |f: &mut Frame<'_>, app: &mut App<'_>, area: Rect| { let tasks_popup_table = |f: &mut Frame<'_>, app: &mut App<'_>, area: Rect| {
let help_footer = Some(build_context_clue_string(&SYSTEM_TASKS_CONTEXT_CLUES));
// let context_area = draw_help_footer_and_get_content_area(
// f,
// area,
// help_footer,
// );
let tasks_row_mapping = |task: &Task| {
let task_props = extract_task_props(task);
Row::new(vec![
Cell::from(task_props.name),
Cell::from(task_props.interval),
Cell::from(task_props.last_execution),
Cell::from(task_props.last_duration),
Cell::from(task_props.next_execution),
])
.primary()
};
let tasks_table = ManagarrTable::new(Some(&mut app.data.radarr_data.tasks), tasks_row_mapping)
.block(borderless_block())
.loading(app.is_loading)
.margin(1)
.footer(help_footer)
.footer_alignment(Alignment::Center)
.headers(TASK_TABLE_HEADERS)
.constraints(TASK_TABLE_CONSTRAINTS);
f.render_widget(title_block("Tasks"), area); f.render_widget(title_block("Tasks"), area);
f.render_widget(tasks_table, area);
let context_area = draw_help_footer_and_get_content_area(
f,
area,
Some(build_context_clue_string(&SYSTEM_TASKS_CONTEXT_CLUES)),
);
draw_table(
f,
context_area,
borderless_block(),
TableProps {
content: Some(&mut app.data.radarr_data.tasks),
wrapped_content: None,
table_headers: TASK_TABLE_HEADERS.to_vec(),
constraints: TASK_TABLE_CONSTRAINTS.to_vec(),
help: None,
},
|task| {
let task_props = extract_task_props(task);
Row::new(vec![
Cell::from(task_props.name),
Cell::from(task_props.interval),
Cell::from(task_props.last_execution),
Cell::from(task_props.last_duration),
Cell::from(task_props.next_execution),
])
.primary()
},
app.is_loading,
true,
)
}; };
if matches!( if matches!(
@@ -163,6 +160,6 @@ fn draw_updates_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
f.render_widget(updates_paragraph, content_area); f.render_widget(updates_paragraph, content_area);
} else { } else {
loading(f, block, content_area, app.is_loading); f.render_widget(LoadingBlock::new(app.is_loading, block), content_area);
} }
} }
+33
View File
@@ -0,0 +1,33 @@
use crate::ui::styles::ManagarrStyle;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::prelude::Text;
use ratatui::widgets::{Block, Paragraph, Widget};
pub struct LoadingBlock<'a> {
is_loading: bool,
block: Block<'a>,
}
impl<'a> LoadingBlock<'a> {
pub fn new(is_loading: bool, block: Block<'a>) -> Self {
Self { is_loading, block }
}
fn render_loading_block(&self, area: Rect, buf: &mut Buffer) {
if self.is_loading {
Paragraph::new(Text::from("\n\n Loading ...\n\n"))
.system_function()
.block(self.block.clone())
.render(area, buf);
} else {
self.block.clone().render(area, buf);
}
}
}
impl<'a> Widget for LoadingBlock<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_loading_block(area, buf);
}
}
+148
View File
@@ -0,0 +1,148 @@
use crate::models::StatefulTable;
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::layout_block_top_border;
use crate::ui::widgets::loading_block::LoadingBlock;
use crate::ui::HIGHLIGHT_SYMBOL;
use ratatui::buffer::Buffer;
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::prelude::{Style, Stylize, Text};
use ratatui::widgets::{Block, Paragraph, Row, StatefulWidget, Table, Widget};
pub struct ManagarrTable<'a, T, F>
where
F: Fn(&T) -> Row<'a>,
{
content: Option<&'a mut StatefulTable<T>>,
table_headers: Vec<Text<'a>>,
constraints: Vec<Constraint>,
row_mapper: F,
footer: Option<String>,
footer_alignment: Alignment,
block: Block<'a>,
margin: u16,
is_loading: bool,
highlight_rows: bool,
}
impl<'a, T, F> ManagarrTable<'a, T, F>
where
F: Fn(&T) -> Row<'a>,
{
pub fn new(content: Option<&'a mut StatefulTable<T>>, row_mapper: F) -> Self {
Self {
content,
table_headers: Vec::new(),
constraints: Vec::new(),
row_mapper,
footer: None,
footer_alignment: Alignment::Left,
block: Block::new(),
margin: 0,
is_loading: false,
highlight_rows: true,
}
}
pub fn headers<I>(mut self, headers: I) -> Self
where
I: IntoIterator,
I::Item: Into<Text<'a>>,
{
self.table_headers = headers.into_iter().map(Into::into).collect();
self
}
pub fn constraints<I>(mut self, constraints: I) -> Self
where
I: IntoIterator,
I::Item: Into<Constraint>,
{
self.constraints = constraints.into_iter().map(Into::into).collect();
self
}
pub fn footer(mut self, footer: Option<String>) -> Self {
self.footer = footer;
self
}
pub fn footer_alignment(mut self, alignment: Alignment) -> Self {
self.footer_alignment = alignment;
self
}
pub fn block(mut self, block: Block<'a>) -> Self {
self.block = block;
self
}
pub fn margin(mut self, margin: u16) -> Self {
self.margin = margin;
self
}
pub fn loading(mut self, is_loading: bool) -> Self {
self.is_loading = is_loading;
self
}
pub fn highlight_rows(mut self, hightlight_rows: bool) -> Self {
self.highlight_rows = hightlight_rows;
self
}
fn render_table(&mut self, area: Rect, buf: &mut Buffer) {
let table_area = if let Some(ref footer) = self.footer {
let [content_area, footer_area] =
Layout::vertical([Constraint::Fill(0), Constraint::Length(2)])
.margin(self.margin)
.areas(area);
Paragraph::new(Text::from(format!(" {footer}").help()))
.block(layout_block_top_border())
.alignment(self.footer_alignment)
.render(footer_area, buf);
content_area
} else {
area
};
let loading_block = LoadingBlock::new(self.is_loading, self.block.clone());
if let Some(ref mut content) = self.content {
if !content.items.is_empty() {
let rows = content.items.iter().map(&self.row_mapper);
let headers = Row::new(self.table_headers.clone())
.default()
.bold()
.bottom_margin(0);
let mut table = Table::new(rows, &self.constraints)
.header(headers)
.block(self.block.clone());
if self.highlight_rows {
table = table
.highlight_style(Style::new().highlight())
.highlight_symbol(HIGHLIGHT_SYMBOL);
}
StatefulWidget::render(table, table_area, buf, &mut content.state);
} else {
loading_block.render(table_area, buf);
}
} else {
loading_block.render(table_area, buf);
}
}
}
impl<'a, T, F> Widget for ManagarrTable<'a, T, F>
where
F: Fn(&T) -> Row<'a>,
{
fn render(mut self, area: Rect, buf: &mut Buffer) {
self.render_table(area, buf);
}
}
+3 -1
View File
@@ -1,3 +1,5 @@
pub(super) mod button; pub(super) mod button;
pub(super) mod input_box;
pub(super) mod checkbox; pub(super) mod checkbox;
pub(super) mod input_box;
pub(super) mod loading_block;
pub(super) mod managarr_table;