rush_sync_server/ui/
color.rs1use crate::core::prelude::*;
2use log::Level;
3use std::collections::HashMap;
4use std::sync::LazyLock;
5
6#[derive(Debug, Clone, Copy, PartialEq)]
7pub struct AppColor(Color);
8
9static COLOR_MAP: LazyLock<HashMap<&'static str, Color>> = LazyLock::new(|| {
10 let mut map = HashMap::new();
11
12 map.insert("black", Color::Black);
14 map.insert("red", Color::Red);
15 map.insert("green", Color::Green);
16 map.insert("yellow", Color::Yellow);
17 map.insert("blue", Color::Blue);
18 map.insert("magenta", Color::Magenta);
19 map.insert("cyan", Color::Cyan);
20 map.insert("gray", Color::Gray);
21 map.insert("darkgray", Color::DarkGray);
22 map.insert("lightred", Color::LightRed);
23 map.insert("lightgreen", Color::LightGreen);
24 map.insert("lightyellow", Color::LightYellow);
25 map.insert("lightblue", Color::LightBlue);
26 map.insert("lightmagenta", Color::LightMagenta);
27 map.insert("lightcyan", Color::LightCyan);
28 map.insert("white", Color::White);
29
30 map.insert("error", Color::Red);
32 map.insert("warning", Color::Yellow);
33 map.insert("warn", Color::Yellow);
34 map.insert("info", Color::Green);
35 map.insert("debug", Color::Blue);
36 map.insert("trace", Color::White);
37 map.insert("lang", Color::Cyan);
38 map.insert("version", Color::LightBlue);
39 map.insert("startup", Color::Magenta);
40 map.insert("theme", Color::LightMagenta);
41
42 map
43});
44
45static DISPLAY_COLOR_MAP: LazyLock<HashMap<&'static str, Color>> = LazyLock::new(|| {
47 let mut map = HashMap::new();
48
49 map.insert("CONFIRM", Color::Yellow);
51 map.insert("BESTÄTIGEN", Color::Yellow);
52
53 map.insert("ERROR", Color::Red);
55 map.insert("FEHLER", Color::Red);
56 map.insert("RENDER", Color::Red);
57
58 map.insert("WARN", Color::Yellow);
60 map.insert("WARNING", Color::Yellow);
61 map.insert("TERMINAL", Color::Yellow);
62
63 map.insert("INFO", Color::Green);
65 map.insert("CLIPBOARD", Color::Green);
66 map.insert("HISTORY", Color::Green);
67 map.insert("HISTORIE", Color::Green);
68 map.insert("LOG_LEVEL", Color::Green);
69 map.insert("SYSTEM", Color::Green);
70
71 map.insert("DEBUG", Color::Blue);
73
74 map.insert("TRACE", Color::White);
76
77 map.insert("THEME", Color::LightMagenta);
79 map.insert("LANG", Color::Cyan);
80 map.insert("SPRACHE", Color::Cyan);
81 map.insert("VERSION", Color::LightBlue);
82 map.insert("READY", Color::Magenta);
83 map.insert("BEREIT", Color::Magenta);
84
85 map
86});
87
88impl AppColor {
89 pub fn new(color: Color) -> Self {
90 Self(color)
91 }
92
93 pub fn from_display_text(display_text: &str) -> Self {
95 let normalized = display_text.trim().to_uppercase();
96 let color = DISPLAY_COLOR_MAP
97 .get(normalized.as_str())
98 .copied()
99 .unwrap_or(Color::Green); Self(color)
102 }
103
104 pub fn from_category(category: &str) -> Self {
105 let normalized = category.trim().to_lowercase();
106 let color = COLOR_MAP
107 .get(normalized.as_str())
108 .copied()
109 .unwrap_or(Color::Green);
110 Self(color)
111 }
112
113 pub fn from_any<T: Into<String>>(source: T) -> Self {
115 let key = source.into().to_lowercase();
116 let color = COLOR_MAP.get(key.as_str()).copied().unwrap_or(Color::Green);
117 Self(color)
118 }
119
120 pub fn from_log_level(level: Level) -> Self {
121 Self::from_category(&level.to_string())
122 }
123
124 pub fn from_string(color_str: &str) -> crate::core::error::Result<Self> {
125 let normalized = color_str.trim().to_lowercase();
126 let color = COLOR_MAP
127 .get(normalized.as_str())
128 .copied()
129 .ok_or_else(|| AppError::Validation(format!("Invalid color: {}", color_str)))?;
130 Ok(Self(color))
131 }
132
133 pub fn from_display_text_with_timing(display_text: &str) -> (Self, std::time::Duration) {
135 let start = std::time::Instant::now();
136 let color = Self::from_display_text(display_text);
137 let duration = start.elapsed();
138 (color, duration)
139 }
140
141 pub fn available_display_texts() -> Vec<&'static str> {
142 DISPLAY_COLOR_MAP.keys().copied().collect()
143 }
144
145 pub fn available_categories() -> Vec<&'static str> {
146 COLOR_MAP.keys().copied().collect()
147 }
148
149 pub fn format_message(&self, level: &str, message: &str) -> String {
150 if level.is_empty() {
151 format!("\x1B[{}m{}\x1B[0m", self.to_ansi_code(), message)
152 } else {
153 format!(
154 "\x1B[{}m[{}] {}\x1B[0m",
155 self.to_ansi_code(),
156 level,
157 message
158 )
159 }
160 }
161
162 pub fn to_ansi_code(&self) -> u8 {
163 match self.0 {
164 Color::Black => 30,
165 Color::Red => 31,
166 Color::Green => 32,
167 Color::Yellow => 33,
168 Color::Blue => 34,
169 Color::Magenta => 35,
170 Color::Cyan => 36,
171 Color::Gray => 37,
172 Color::DarkGray => 90,
173 Color::LightRed => 91,
174 Color::LightGreen => 92,
175 Color::LightYellow => 93,
176 Color::LightBlue => 94,
177 Color::LightMagenta => 95,
178 Color::LightCyan => 96,
179 Color::White => 97,
180 _ => 37,
181 }
182 }
183
184 pub fn to_name(&self) -> &'static str {
185 COLOR_MAP
186 .iter()
187 .find(|(_, &v)| v == self.0)
188 .map(|(k, _)| *k)
189 .unwrap_or("gray")
190 }
191}
192
193impl From<AppColor> for Color {
194 fn from(app_color: AppColor) -> Self {
195 app_color.0
196 }
197}
198
199impl From<&AppColor> for Color {
200 fn from(app_color: &AppColor) -> Self {
201 app_color.0
202 }
203}
204
205impl Default for AppColor {
206 fn default() -> Self {
207 Self(Color::Gray)
208 }
209}