feat: Write built in themes to the themes file on first run so users can define custom themes

This commit is contained in:
2025-03-06 17:44:52 -07:00
parent 709f6ca6ca
commit df38ea5413
25 changed files with 758 additions and 74 deletions
+5
View File
@@ -206,6 +206,9 @@ Key:
- [ ] Support for Tautulli - [ ] Support for Tautulli
### Themes
Managarr ships with a few themes out of the box. See the [Themes README](themes/README.md) page for more information.
### The Managarr CLI ### The Managarr CLI
Managarr can be used in one of two ways: As a TUI, or as a CLI for managing your Servarrs. Managarr can be used in one of two ways: As a TUI, or as a CLI for managing your Servarrs.
@@ -315,6 +318,7 @@ managarr --config-file /path/to/config.yml
### Example Configuration: ### Example Configuration:
```yaml ```yaml
theme: default
radarr: radarr:
- host: 192.168.0.78 - host: 192.168.0.78
port: 7878 port: 7878
@@ -357,6 +361,7 @@ tautulli:
### Example Multi-Instance Configuration: ### Example Multi-Instance Configuration:
```yaml ```yaml
theme: default
radarr: radarr:
- host: 192.168.0.78 # No name specified, so this instance's name will default to 'Radarr 1' - host: 192.168.0.78 # No name specified, so this instance's name will default to 'Radarr 1'
port: 7878 port: 7878
+7 -5
View File
@@ -1,6 +1,6 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields}; use syn::{Data, DeriveInput, Fields, parse_macro_input};
/// Derive macro for generating a `validate` method for a Theme struct. /// Derive macro for generating a `validate` method for a Theme struct.
/// The `validate` method ensures that all values with the `validate` attribute are not `None`. /// The `validate` method ensures that all values with the `validate` attribute are not `None`.
@@ -77,9 +77,10 @@ pub fn derive_validate_theme(input: TokenStream) -> TokenStream {
for field in &fields.named { for field in &fields.named {
let field_name = &field.ident; let field_name = &field.ident;
let has_validate_attr = field.attrs.iter().any(|attr| { let has_validate_attr = field
attr.path().is_ident("validate") .attrs
}); .iter()
.any(|attr| attr.path().is_ident("validate"));
if has_validate_attr { if has_validate_attr {
validation_checks.push(quote! { validation_checks.push(quote! {
@@ -100,5 +101,6 @@ pub fn derive_validate_theme(input: TokenStream) -> TokenStream {
#(#validation_checks)* #(#validation_checks)*
} }
} }
}.into() }
.into()
} }
+92
View File
@@ -0,0 +1,92 @@
use crate::ui::theme::{Background, Style, Theme, ThemeDefinition};
use ratatui::style::Color;
use std::str::FromStr;
#[cfg(test)]
#[path = "builtin_themes_tests.rs"]
mod builtin_themes_tests;
pub fn get_builtin_themes() -> Vec<ThemeDefinition> {
let watermelon_dark = Theme {
background: Some(Background {
enabled: Some(false),
color: Some(Color::from_str("#233237").unwrap()),
}),
default: Some(Style {
color: Some(Color::from_str("#00FF00").unwrap()),
}),
downloaded: Some(Style {
color: Some(Color::from_str("#80ffbf").unwrap()),
}),
failure: Some(Style {
color: Some(Color::from_str("#ff8080").unwrap()),
}),
missing: Some(Style {
color: Some(Color::from_str("#ff8080").unwrap()),
}),
primary: Some(Style {
color: Some(Color::from_str("#ff19d9").unwrap()),
}),
secondary: Some(Style {
color: Some(Color::from_str("#8c19ff").unwrap()),
}),
..Theme::default()
};
let dracula = Theme {
background: Some(Background {
enabled: Some(false),
color: Some(Color::from_str("#233237").unwrap()),
}),
default: Some(Style {
color: Some(Color::from_str("#f8f8f2").unwrap()),
}),
downloaded: Some(Style {
color: Some(Color::from_str("#50fa7b").unwrap()),
}),
downloading: Some(Style {
color: Some(Color::from_str("#f1fa8c").unwrap()),
}),
failure: Some(Style {
color: Some(Color::from_str("#ff5555").unwrap()),
}),
missing: Some(Style {
color: Some(Color::from_str("#ffb86c").unwrap()),
}),
primary: Some(Style {
color: Some(Color::from_str("#ff79c6").unwrap()),
}),
secondary: Some(Style {
color: Some(Color::from_str("#ff79c6").unwrap()),
}),
unmonitored_missing: Some(Style {
color: Some(Color::from_str("#6272a4").unwrap()),
}),
help: Some(Style {
color: Some(Color::from_str("#6272a4").unwrap()),
}),
success: Some(Style {
color: Some(Color::from_str("#50fa7b").unwrap()),
}),
warning: Some(Style {
color: Some(Color::from_str("#f1fa8c").unwrap()),
}),
unreleased: Some(Style {
color: Some(Color::from_str("#f8f8f2").unwrap()),
}),
..Theme::default()
};
vec![
ThemeDefinition {
name: "default".to_owned(),
theme: Theme::default(),
},
ThemeDefinition {
name: "watermelon-dark".to_owned(),
theme: watermelon_dark,
},
ThemeDefinition {
name: "dracula".to_owned(),
theme: dracula,
},
]
}
+96
View File
@@ -0,0 +1,96 @@
#[cfg(test)]
mod test {
use crate::builtin_themes::get_builtin_themes;
use crate::ui::theme::{Background, Style, Theme, ThemeDefinition};
use pretty_assertions::assert_eq;
use ratatui::prelude::Color;
use std::str::FromStr;
#[test]
fn test_builtin_themes() {
let watermelon_dark = Theme {
background: Some(Background {
enabled: Some(false),
color: Some(Color::from_str("#233237").unwrap()),
}),
default: Some(Style {
color: Some(Color::from_str("#00FF00").unwrap()),
}),
downloaded: Some(Style {
color: Some(Color::from_str("#80ffbf").unwrap()),
}),
failure: Some(Style {
color: Some(Color::from_str("#ff8080").unwrap()),
}),
missing: Some(Style {
color: Some(Color::from_str("#ff8080").unwrap()),
}),
primary: Some(Style {
color: Some(Color::from_str("#ff19d9").unwrap()),
}),
secondary: Some(Style {
color: Some(Color::from_str("#8c19ff").unwrap()),
}),
..Theme::default()
};
let dracula = Theme {
background: Some(Background {
enabled: Some(false),
color: Some(Color::from_str("#233237").unwrap()),
}),
default: Some(Style {
color: Some(Color::from_str("#f8f8f2").unwrap()),
}),
downloaded: Some(Style {
color: Some(Color::from_str("#50fa7b").unwrap()),
}),
downloading: Some(Style {
color: Some(Color::from_str("#f1fa8c").unwrap()),
}),
failure: Some(Style {
color: Some(Color::from_str("#ff5555").unwrap()),
}),
missing: Some(Style {
color: Some(Color::from_str("#ffb86c").unwrap()),
}),
primary: Some(Style {
color: Some(Color::from_str("#ff79c6").unwrap()),
}),
secondary: Some(Style {
color: Some(Color::from_str("#ff79c6").unwrap()),
}),
unmonitored_missing: Some(Style {
color: Some(Color::from_str("#6272a4").unwrap()),
}),
help: Some(Style {
color: Some(Color::from_str("#6272a4").unwrap()),
}),
success: Some(Style {
color: Some(Color::from_str("#50fa7b").unwrap()),
}),
warning: Some(Style {
color: Some(Color::from_str("#f1fa8c").unwrap()),
}),
unreleased: Some(Style {
color: Some(Color::from_str("#f8f8f2").unwrap()),
}),
..Theme::default()
};
let expected_themes = vec![
ThemeDefinition {
name: "default".to_owned(),
theme: Theme::default(),
},
ThemeDefinition {
name: "watermelon-dark".to_owned(),
theme: watermelon_dark,
},
ThemeDefinition {
name: "dracula".to_owned(),
theme: dracula,
},
];
assert_eq!(expected_themes, get_builtin_themes());
}
}
+13 -8
View File
@@ -28,11 +28,13 @@ use crate::cli::Command;
use crate::event::input_event::{Events, InputEvent}; use crate::event::input_event::{Events, InputEvent};
use crate::event::Key; use crate::event::Key;
use crate::network::{Network, NetworkEvent}; use crate::network::{Network, NetworkEvent};
use crate::ui::theme::{Theme, ThemeDefinition}; use crate::ui::theme::{Theme, ThemeDefinitionsWrapper};
use crate::ui::{ui, THEME}; use crate::ui::{ui, THEME};
use crate::utils::load_theme_config; use crate::utils::load_theme_config;
mod app; mod app;
mod builtin_themes;
mod builtin_themes_tests;
mod cli; mod cli;
mod event; mod event;
mod handlers; mod handlers;
@@ -80,9 +82,9 @@ struct Cli {
global = true, global = true,
value_parser, value_parser,
env = "MANAGARR_THEME_FILE", env = "MANAGARR_THEME_FILE",
help = "The Managarr theme file to use" help = "The Managarr themes file to use"
)] )]
theme_file: Option<PathBuf>, themes_file: Option<PathBuf>,
#[arg( #[arg(
long, long,
global = true, global = true,
@@ -161,7 +163,7 @@ async fn main() -> Result<()> {
}); });
start_ui( start_ui(
&app, &app,
&args.theme_file, &args.themes_file,
args.theme.unwrap_or(theme_name.unwrap_or_default()), args.theme.unwrap_or(theme_name.unwrap_or_default()),
) )
.await?; .await?;
@@ -200,16 +202,19 @@ async fn start_networking(
async fn start_ui( async fn start_ui(
app: &Arc<Mutex<App<'_>>>, app: &Arc<Mutex<App<'_>>>,
theme_file_arg: &Option<PathBuf>, themes_file_arg: &Option<PathBuf>,
theme_name: String, theme_name: String,
) -> Result<()> { ) -> Result<()> {
let theme_definitions = if let Some(ref theme_file) = theme_file_arg { let theme_definitions_wrapper = if let Some(ref theme_file) = themes_file_arg {
load_theme_config(theme_file.to_str().expect("Invalid theme file specified"))? load_theme_config(theme_file.to_str().expect("Invalid theme file specified"))?
} else { } else {
confy::load("managarr", "themes").unwrap_or_else(|_| vec![ThemeDefinition::default()]) confy::load("managarr", "themes").unwrap_or_else(|_| ThemeDefinitionsWrapper::default())
}; };
let theme = if !theme_name.is_empty() { let theme = if !theme_name.is_empty() {
let theme_definition = theme_definitions.iter().find(|t| t.name == theme_name); let theme_definition = theme_definitions_wrapper
.theme_definitions
.iter()
.find(|t| t.name == theme_name);
if theme_definition.is_none() { if theme_definition.is_none() {
log_and_print_error(format!("The specified theme was not found: {theme_name}")); log_and_print_error(format!("The specified theme was not found: {theme_name}"));
+36 -2
View File
@@ -1,7 +1,8 @@
use crate::builtin_themes::get_builtin_themes;
use anyhow::Result; use anyhow::Result;
use derivative::Derivative; use derivative::Derivative;
use ratatui::style::Color; use ratatui::style::Color;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
use std::str::FromStr; use std::str::FromStr;
use validate_theme_derive::ValidateTheme; use validate_theme_derive::ValidateTheme;
@@ -118,6 +119,20 @@ pub struct ThemeDefinition {
pub theme: Theme, pub theme: Theme,
} }
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct ThemeDefinitionsWrapper {
pub theme_definitions: Vec<ThemeDefinition>,
}
impl Default for ThemeDefinitionsWrapper {
fn default() -> Self {
Self {
theme_definitions: get_builtin_themes(),
}
}
}
fn default_background_color() -> Option<Color> { fn default_background_color() -> Option<Color> {
Some(Color::Rgb(35, 50, 55)) Some(Color::Rgb(35, 50, 55))
} }
@@ -229,9 +244,28 @@ fn default_warning_style() -> Option<Style> {
}) })
} }
impl<'de> Deserialize<'de> for ThemeDefinitionsWrapper {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let theme_definitions = Vec::<ThemeDefinition>::deserialize(deserializer)?;
Ok(ThemeDefinitionsWrapper { theme_definitions })
}
}
impl Serialize for ThemeDefinitionsWrapper {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.theme_definitions.serialize(serializer)
}
}
fn deserialize_color_str<'de, D>(deserializer: D) -> Result<Option<Color>, D::Error> fn deserialize_color_str<'de, D>(deserializer: D) -> Result<Option<Color>, D::Error>
where where
D: serde::Deserializer<'de>, D: Deserializer<'de>,
{ {
let s: Option<String> = Option::deserialize(deserializer)?; let s: Option<String> = Option::deserialize(deserializer)?;
match s { match s {
+251 -2
View File
@@ -1,7 +1,8 @@
mod tests { mod tests {
use crate::ui::theme::{Background, Style, Theme, ThemeDefinition}; use crate::ui::theme::{Background, Style, Theme, ThemeDefinition, ThemeDefinitionsWrapper};
use pretty_assertions::assert_eq; use pretty_assertions::{assert_eq, assert_str_eq};
use ratatui::style::Color; use ratatui::style::Color;
use std::str::FromStr;
#[test] #[test]
fn test_background_default() { fn test_background_default() {
@@ -188,4 +189,252 @@ warning:
assert_eq!(theme, expected_theme); assert_eq!(theme, expected_theme);
} }
#[test]
fn test_theme_definitions_wrapper_default() {
let watermelon_dark = Theme {
background: Some(Background {
enabled: Some(false),
color: Some(Color::from_str("#233237").unwrap()),
}),
default: Some(Style {
color: Some(Color::from_str("#00FF00").unwrap()),
}),
downloaded: Some(Style {
color: Some(Color::from_str("#80ffbf").unwrap()),
}),
failure: Some(Style {
color: Some(Color::from_str("#ff8080").unwrap()),
}),
missing: Some(Style {
color: Some(Color::from_str("#ff8080").unwrap()),
}),
primary: Some(Style {
color: Some(Color::from_str("#ff19d9").unwrap()),
}),
secondary: Some(Style {
color: Some(Color::from_str("#8c19ff").unwrap()),
}),
..Theme::default()
};
let dracula = Theme {
background: Some(Background {
enabled: Some(false),
color: Some(Color::from_str("#233237").unwrap()),
}),
default: Some(Style {
color: Some(Color::from_str("#f8f8f2").unwrap()),
}),
downloaded: Some(Style {
color: Some(Color::from_str("#50fa7b").unwrap()),
}),
downloading: Some(Style {
color: Some(Color::from_str("#f1fa8c").unwrap()),
}),
failure: Some(Style {
color: Some(Color::from_str("#ff5555").unwrap()),
}),
missing: Some(Style {
color: Some(Color::from_str("#ffb86c").unwrap()),
}),
primary: Some(Style {
color: Some(Color::from_str("#ff79c6").unwrap()),
}),
secondary: Some(Style {
color: Some(Color::from_str("#ff79c6").unwrap()),
}),
unmonitored_missing: Some(Style {
color: Some(Color::from_str("#6272a4").unwrap()),
}),
help: Some(Style {
color: Some(Color::from_str("#6272a4").unwrap()),
}),
success: Some(Style {
color: Some(Color::from_str("#50fa7b").unwrap()),
}),
warning: Some(Style {
color: Some(Color::from_str("#f1fa8c").unwrap()),
}),
unreleased: Some(Style {
color: Some(Color::from_str("#f8f8f2").unwrap()),
}),
..Theme::default()
};
let theme_definitions_wrapper = ThemeDefinitionsWrapper {
theme_definitions: vec![
ThemeDefinition {
name: "default".to_owned(),
theme: Theme::default(),
},
ThemeDefinition {
name: "watermelon-dark".to_owned(),
theme: watermelon_dark,
},
ThemeDefinition {
name: "dracula".to_owned(),
theme: dracula,
},
],
};
assert_eq!(
ThemeDefinitionsWrapper::default(),
theme_definitions_wrapper
);
}
#[test]
fn test_theme_definitions_wrapper_deserialization() {
let theme_definitions = r###"
- name: test
theme:
background:
enabled: false
color: "#000000"
awaiting_import:
color: "#000000"
indeterminate:
color: "#000000"
default:
color: "#000000"
downloaded:
color: "#000000"
downloading:
color: "#000000"
failure:
color: "#000000"
help:
color: "#000000"
missing:
color: "#000000"
primary:
color: "#000000"
secondary:
color: "#000000"
success:
color: "#000000"
system_function:
color: "#000000"
unmonitored:
color: "#000000"
unmonitored_missing:
color: "#000000"
unreleased:
color: "#000000"
warning:
color: "#000000"
"###;
let theme_definition_wrapper: ThemeDefinitionsWrapper =
serde_yaml::from_str(theme_definitions).unwrap();
let expected_theme_definitions = ThemeDefinitionsWrapper {
theme_definitions: vec![ThemeDefinition {
name: "test".to_owned(),
theme: Theme {
background: Some(Background {
enabled: Some(false),
color: Some(Color::Rgb(0, 0, 0)),
}),
awaiting_import: Some(Style {
color: Some(Color::Rgb(0, 0, 0)),
}),
indeterminate: Some(Style {
color: Some(Color::Rgb(0, 0, 0)),
}),
default: Some(Style {
color: Some(Color::Rgb(0, 0, 0)),
}),
downloaded: Some(Style {
color: Some(Color::Rgb(0, 0, 0)),
}),
downloading: Some(Style {
color: Some(Color::Rgb(0, 0, 0)),
}),
failure: Some(Style {
color: Some(Color::Rgb(0, 0, 0)),
}),
help: Some(Style {
color: Some(Color::Rgb(0, 0, 0)),
}),
missing: Some(Style {
color: Some(Color::Rgb(0, 0, 0)),
}),
primary: Some(Style {
color: Some(Color::Rgb(0, 0, 0)),
}),
secondary: Some(Style {
color: Some(Color::Rgb(0, 0, 0)),
}),
success: Some(Style {
color: Some(Color::Rgb(0, 0, 0)),
}),
system_function: Some(Style {
color: Some(Color::Rgb(0, 0, 0)),
}),
unmonitored: Some(Style {
color: Some(Color::Rgb(0, 0, 0)),
}),
unmonitored_missing: Some(Style {
color: Some(Color::Rgb(0, 0, 0)),
}),
unreleased: Some(Style {
color: Some(Color::Rgb(0, 0, 0)),
}),
warning: Some(Style {
color: Some(Color::Rgb(0, 0, 0)),
}),
},
}],
};
assert_eq!(theme_definition_wrapper, expected_theme_definitions);
}
#[test]
fn test_theme_definition_wrapper_serialization() {
let theme_definition_wrapper = ThemeDefinitionsWrapper {
theme_definitions: vec![ThemeDefinition::default()],
};
let expected_yaml = r###"- name: ''
theme:
background:
color: '#233237'
enabled: true
awaiting_import:
color: '#FFAA42'
indeterminate:
color: '#FFAA42'
default:
color: White
downloaded:
color: Green
downloading:
color: Magenta
failure:
color: Red
help:
color: LightBlue
missing:
color: Red
primary:
color: Cyan
secondary:
color: Yellow
success:
color: Green
system_function:
color: Yellow
unmonitored:
color: Gray
unmonitored_missing:
color: Yellow
unreleased:
color: LightCyan
warning:
color: Magenta
"###;
let serialized_yaml = serde_yaml::to_string(&theme_definition_wrapper).unwrap();
assert_str_eq!(serialized_yaml, expected_yaml);
}
} }
+2 -2
View File
@@ -21,7 +21,7 @@ use tokio_util::sync::CancellationToken;
use crate::app::{log_and_print_error, App, AppConfig}; use crate::app::{log_and_print_error, App, AppConfig};
use crate::cli::{self, Command}; use crate::cli::{self, Command};
use crate::network::Network; use crate::network::Network;
use crate::ui::theme::ThemeDefinition; use crate::ui::theme::ThemeDefinitionsWrapper;
#[cfg(test)] #[cfg(test)]
#[path = "utils_tests.rs"] #[path = "utils_tests.rs"]
@@ -154,7 +154,7 @@ pub(super) fn load_config(path: &str) -> Result<AppConfig> {
} }
} }
pub(super) fn load_theme_config(path: &str) -> Result<Vec<ThemeDefinition>> { pub(super) fn load_theme_config(path: &str) -> Result<ThemeDefinitionsWrapper> {
match File::open(path).map_err(|e| anyhow!(e)) { match File::open(path).map_err(|e| anyhow!(e)) {
Ok(file) => { Ok(file) => {
let reader = BufReader::new(file); let reader = BufReader::new(file);
+92
View File
@@ -0,0 +1,92 @@
# Managarr Themes
The Managarr TUI can be customized to look how you like with various themes.
There are a few themes included by default with Managarr and are added to your `themes.yml`
on first startup. You can simply add more custom themes as you wish to this file.
## Table of Contents
- [Built In Themes](#built-in-themes)
- [Creating a Custom Theme](#creating-a-custom-theme)
## Built-In Themes
Managarr ships with a handful of built-in themes that you can either use or base your own
custom theme off of. The following themes are included by default:
### [Default](./default/README.md)
![sonarr-library](./default/sonarr_library.png)
### [Dracula](./dracula/README.md)
![sonarr-library](./dracula/sonarr_library.png)
### [Watermelon Dark](./watermelon-dark/README.md)
![sonarr-library](./watermelon-dark/sonarr_library.png)
## Creating a Custom Theme
To create a custom theme, you will need to add a new entry to the `themes.yml` file. If you decide not to add it to the
`themes.yml` file, you can also specify a different file to load themes from using the `--themes-file` argument.
Themes are customizable using hex color codes for the various elements of the TUI. The following
is an example that shows every available customization option for a custom theme:
```yaml
- name: my-theme
theme:
background:
enabled: true # Disable for transparent backgrounds
color: "#233237"
awaiting_import:
color: "#FFAA42"
indeterminate:
color: "#FFAA42"
default:
color: "#FFFFFF"
downloaded:
color: "#00FF00"
downloading:
color: "#762671"
failure:
color: "#DE382B"
help:
color: "#00FFFF"
missing:
color: "#DE382B"
primary:
color: "#2CB5E9"
secondary:
color: "#FFC706"
success:
color: "#39B54A"
system_function:
color: "#FFC706"
unmonitored:
color: "#808080"
unmonitored_missing:
color: "#FFC706"
unreleased:
color: "#00FFFF"
warning:
color: "#FF00FF"
```
In order to activate your custom theme, you can either update your `config.yml`:
```yaml
theme: my-theme
radarr:
...
sonarr:
...
```
Or you can test out your theme via the `--theme` flag on the CLI:
```shell
managarr --theme my-theme
```
If you're developing your own theme and don't want to add it to the main `themes.yml` file, you can
also use the `--themes-file` argument to specify a different file to load themes from:
```shell
managarr --themes-file /path/to/my/testing-themes.yml
```
+9
View File
@@ -0,0 +1,9 @@
# Managarr Default Theme
The [themes.yml](./themes.yml) file in this directory corresponds to the theme configuration for the
default Managarr theme.
## Screenshots
![sonarr-library](./sonarr_library.png)
![manual-episode-search](./manual_episode_search.png)
![radarr-system](./radarr_system.png)
Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

+37
View File
@@ -0,0 +1,37 @@
- name: default
theme:
background:
enabled: true
color: "#233237"
awaiting_import:
color: "#FFAA42"
indeterminate:
color: "#FFAA42"
default:
color: "#FFFFFF"
downloaded:
color: "#00FF00"
downloading:
color: "#762671"
failure:
color: "#DE382B"
help:
color: "#00FFFF"
missing:
color: "#DE382B"
primary:
color: "#2CB5E9"
secondary:
color: "#FFC706"
success:
color: "#39B54A"
system_function:
color: "#FFC706"
unmonitored:
color: "#808080"
unmonitored_missing:
color: "#FFC706"
unreleased:
color: "#00FFFF"
warning:
color: "#FF00FF"
+9
View File
@@ -0,0 +1,9 @@
# Managarr Dracula Theme
The [themes.yml](./themes.yml) file in this directory corresponds to the theme configuration for the
Dracula Managarr theme.
## Screenshots
![sonarr-library](./sonarr_library.png)
![manual-episode-search](./manual_episode_search.png)
![radarr-system](./radarr_system.png)
Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

+29
View File
@@ -0,0 +1,29 @@
- name: dracula
theme:
background:
enabled: false
color: "#282a36"
default:
color: "#f8f8f2"
downloaded:
color: "#50fa7b"
downloading:
color: "#8be9fd"
failure:
color: "#ff5555"
missing:
color: "#ffb86c"
unmonitored-missing:
color: "#6272a4"
help:
color: "#6272a4"
primary:
color: "#ff79c6"
secondary:
color: "#ff79c6"
success:
color: "#50fa7b"
warning:
color: "#f1fa8c"
unreleased:
color: "#f8f8f2"
+9
View File
@@ -0,0 +1,9 @@
# Managarr Watermelon Dark Theme
The [themes.yml](./themes.yml) file in this directory corresponds to the theme configuration for the
Watermelon Dark Managarr theme.
## Screenshots
![sonarr-library](./sonarr_library.png)
![manual-episode-search](./manual_episode_search.png)
![radarr-system](./radarr_system.png)
Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

+16
View File
@@ -0,0 +1,16 @@
- name: watermelon-dark
theme:
background:
enabled: false
default:
color: "#00FF00"
downloaded:
color: "#80ffbf"
failure:
color: "#ff8080"
missing:
color: "#ff8080"
primary:
color: "#ff19d9"
secondary:
color: "#8c19ff"