rush_sync_server/ui/
color.rs

1use crate::core::prelude::*;
2use log::Level;
3use once_cell::sync::Lazy;
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Copy, PartialEq)]
7pub struct AppColor(Color);
8
9static COLOR_MAP: Lazy<HashMap<&'static str, Color>> = Lazy::new(|| {
10    let mut map = HashMap::new();
11
12    // Standard-Farben
13    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    // Kategorien
31    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
41    map
42});
43
44impl AppColor {
45    pub fn new(color: Color) -> Self {
46        Self(color)
47    }
48
49    // pub fn from_any<T: Into<String>>(source: T) -> Self {
50    //     let key = source.into().to_lowercase();
51    //     Self(*COLOR_MAP.get(key.as_str()).unwrap_or(&Color::Gray))
52    // }
53
54    pub fn from_any<T: Into<String>>(source: T) -> Self {
55        let key = source.into().to_lowercase();
56
57        // ✅ DIRECT LOOKUP FIRST (für echte Farbnamen)
58        if let Some(&color) = COLOR_MAP.get(key.as_str()) {
59            log::debug!("✅ Direct color lookup: '{}' → {:?}", key, color);
60            return Self(color);
61        }
62
63        // ❌ FALLBACK: i18n category mapping (nur für Display-Kategorien)
64        log::debug!("⚠️ Using i18n category mapping for: '{}'", key);
65        let mapped_category = crate::i18n::get_color_category_for_display(&key);
66        let fallback_color = COLOR_MAP
67            .get(mapped_category.as_str())
68            .unwrap_or(&Color::Gray);
69
70        log::debug!(
71            "📍 Category mapping: '{}' → '{}' → {:?}",
72            key,
73            mapped_category,
74            fallback_color
75        );
76        Self(*fallback_color)
77    }
78
79    pub fn from_log_level(level: Level) -> Self {
80        Self::from_any(level.to_string())
81    }
82
83    pub fn from_string(color_str: &str) -> crate::core::error::Result<Self> {
84        let normalized = color_str.trim().to_lowercase();
85
86        // ✅ NUR DEBUG-LEVEL (nicht INFO)
87        log::debug!(
88            "🎨 AppColor::from_string: '{}' → normalized: '{}'",
89            color_str,
90            normalized
91        );
92
93        // ✅ DIRECT LOOKUP: Gehe DIREKT zu COLOR_MAP, nicht über i18n!
94        let color = COLOR_MAP.get(normalized.as_str()).copied().ok_or_else(|| {
95            log::error!("❌ Color '{}' not found in COLOR_MAP", normalized);
96            log::debug!(
97                "📋 Available colors: {:?}",
98                COLOR_MAP.keys().collect::<Vec<_>>()
99            );
100            AppError::Validation(format!("Invalid color: {}", color_str))
101        })?;
102
103        let app_color = Self(color);
104
105        // ✅ NUR DEBUG-LEVEL (nicht INFO) - viel weniger Spam
106        log::debug!(
107            "✅ Color '{}' → '{}' → RGB({:?})",
108            color_str,
109            app_color.to_name(),
110            color
111        );
112
113        Ok(app_color)
114    }
115
116    pub fn format_message(&self, level: &str, message: &str) -> String {
117        if level.is_empty() {
118            format!("\x1B[{}m{}\x1B[0m", self.to_ansi_code(), message)
119        } else {
120            format!(
121                "\x1B[{}m[{}] {}\x1B[0m",
122                self.to_ansi_code(),
123                level,
124                message
125            )
126        }
127    }
128
129    pub fn to_ansi_code(&self) -> u8 {
130        match self.0 {
131            Color::Black => 30,
132            Color::Red => 31,
133            Color::Green => 32,
134            Color::Yellow => 33,
135            Color::Blue => 34,
136            Color::Magenta => 35,
137            Color::Cyan => 36,
138            Color::Gray => 37,
139            Color::DarkGray => 90,
140            Color::LightRed => 91,
141            Color::LightGreen => 92,
142            Color::LightYellow => 93,
143            Color::LightBlue => 94,
144            Color::LightMagenta => 95,
145            Color::LightCyan => 96,
146            Color::White => 97,
147            _ => 37,
148        }
149    }
150
151    pub fn to_name(&self) -> &'static str {
152        COLOR_MAP
153            .iter()
154            .find(|(_, &v)| v == self.0)
155            .map(|(k, _)| *k)
156            .unwrap_or("gray")
157    }
158}
159
160impl From<AppColor> for Color {
161    fn from(app_color: AppColor) -> Self {
162        app_color.0
163    }
164}
165
166impl From<&AppColor> for Color {
167    fn from(app_color: &AppColor) -> Self {
168        app_color.0
169    }
170}
171
172impl Default for AppColor {
173    fn default() -> Self {
174        Self(Color::Gray)
175    }
176}