Skip to main content

euv_ui/component/vconsole/hook/
impl.rs

1use super::*;
2
3/// Implements the Console struct providing web console API methods.
4///
5/// Each method outputs to both the browser developer console and the
6/// vConsole panel signal, with appropriate log level classification.
7/// Methods are associated functions that internally access the global
8/// Console instance, so callers never need to hold a reference.
9impl Console {
10    /// Initializes the global Console log signal.
11    ///
12    /// Must be called once during application startup before any `Console::log`,
13    /// `Console::warn`, or `Console::error` calls.
14    pub fn init() {
15        let signal: Signal<Vec<ConsoleEntry>> = Signal::create(Vec::new());
16        CONSOLE_LOG_SIGNAL.set(signal);
17    }
18
19    /// Logs an informational message (equivalent to console.log).
20    ///
21    /// The vConsole panel entry is appended only when `Console::init` has
22    /// been called; the browser console output always happens.
23    ///
24    /// # Arguments
25    ///
26    /// - `M: AsRef<str>` - The message to log.
27    pub fn log<M>(message: M)
28    where
29        M: AsRef<str>,
30    {
31        let message_ref: &str = message.as_ref();
32        console::log_1(&message_ref.into());
33        Self::append_entry(ConsoleEntry::new(LogLevel::Log, message_ref.to_string()));
34    }
35
36    /// Logs a warning message (equivalent to console.warn).
37    ///
38    /// The vConsole panel entry is appended only when `Console::init` has
39    /// been called; the browser console output always happens.
40    ///
41    /// # Arguments
42    ///
43    /// - `M: AsRef<str>` - The warning message to log.
44    pub fn warn<M>(message: M)
45    where
46        M: AsRef<str>,
47    {
48        let message_ref: &str = message.as_ref();
49        console::warn_1(&message_ref.into());
50        Self::append_entry(ConsoleEntry::new(LogLevel::Warn, message_ref.to_string()));
51    }
52
53    /// Logs an error message (equivalent to console.error).
54    ///
55    /// The vConsole panel entry is appended only when `Console::init` has
56    /// been called; the browser console output always happens.
57    ///
58    /// # Arguments
59    ///
60    /// - `M: AsRef<str>` - The error message to log.
61    pub fn error<M>(message: M)
62    where
63        M: AsRef<str>,
64    {
65        let message_ref: &str = message.as_ref();
66        console::error_1(&message_ref.into());
67        Self::append_entry(ConsoleEntry::new(LogLevel::Error, message_ref.to_string()));
68    }
69
70    /// Clears all log entries from the vConsole panel signal.
71    ///
72    /// No-op when `Console::init` has not been called yet.
73    pub fn clear() {
74        let Some(log) = Self::get_signal() else {
75            return;
76        };
77        log.set(Vec::new());
78    }
79
80    /// Returns the global vConsole log signal, if initialized.
81    ///
82    /// # Returns
83    ///
84    /// - `Option<Signal<Vec<ConsoleEntry>>>` - The console log signal, or
85    ///   `None` when `Console::init` has not been called yet.
86    pub(crate) fn get_signal() -> Option<Signal<Vec<ConsoleEntry>>> {
87        CONSOLE_LOG_SIGNAL.loaded()
88    }
89
90    /// Creates a click event handler that opens the vConsole fab panel.
91    ///
92    /// Pushes an overlay state and sets the panel visibility signal to true.
93    ///
94    /// # Arguments
95    ///
96    /// - `Signal<bool>` - The signal controlling panel visibility.
97    ///
98    /// # Returns
99    ///
100    /// - `Option<Rc<dyn Fn(Event)>>` - A click handler that opens the panel.
101    pub(crate) fn fab_on_click(panel_open: Signal<bool>) -> Option<Rc<dyn Fn(Event)>> {
102        Some(Rc::new(move |_: Event| {
103            let closer: Rc<dyn Fn()> = Rc::new(move || {
104                panel_open.set(false);
105            });
106            Router::overlay_stack_push(closer);
107            panel_open.set(true);
108        }))
109    }
110
111    /// Filters and reverses console log entries based on the current filter signal value.
112    ///
113    /// # Arguments
114    ///
115    /// - `Signal<Vec<ConsoleEntry>>` - The console log signal.
116    /// - `Signal<LogFilter>` - The current filter level signal.
117    ///
118    /// # Returns
119    ///
120    /// - `Vec<(usize, ConsoleEntry)>` - The filtered and reversed entries with original indices.
121    pub(crate) fn filter_entries(
122        logs: Signal<Vec<ConsoleEntry>>,
123        filter: Signal<LogFilter>,
124    ) -> Vec<(usize, ConsoleEntry)> {
125        let log_list: Vec<ConsoleEntry> = logs.get();
126        let filter_value: LogFilter = filter.get();
127        let mut result: Vec<(usize, ConsoleEntry)> = log_list
128            .iter()
129            .enumerate()
130            .filter(|(_, entry): &(usize, &ConsoleEntry)| match filter_value {
131                LogFilter::All => true,
132                LogFilter::Log => entry.get_level() == LogLevel::Log,
133                LogFilter::Warn => entry.get_level() == LogLevel::Warn,
134                LogFilter::Error => entry.get_level() == LogLevel::Error,
135            })
136            .map(|(index, entry): (usize, &ConsoleEntry)| (index, entry.clone()))
137            .collect();
138        result.reverse();
139        result
140    }
141
142    /// Appends an entry to the vConsole log signal, trimming if over capacity.
143    ///
144    /// No-op when `Console::init` has not been called yet.
145    ///
146    /// # Arguments
147    ///
148    /// - `ConsoleEntry` - The console entry to append.
149    fn append_entry(entry: ConsoleEntry) {
150        let Some(log) = Self::get_signal() else {
151            return;
152        };
153        let mut current: Vec<ConsoleEntry> = log.get();
154        current.push(entry);
155        if current.len() > MAX_CONSOLE_LOG_ENTRIES {
156            let excess: usize = current.len() - MAX_CONSOLE_LOG_ENTRIES;
157            current.drain(0..excess);
158        }
159        log.set(current);
160    }
161}
162
163/// Implements the Display trait for LogFilter to render filter button labels.
164impl Display for LogFilter {
165    /// Formats the [`LogFilter`] via the supplied formatter.
166    ///
167    /// # Arguments
168    ///
169    /// - `&mut Formatter<'_>` - The formatter receiving the formatted output.
170    ///
171    /// # Returns
172    ///
173    /// - `FmtResult` - Result of the formatting operation.
174    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
175        let label: &str = match self {
176            LogFilter::All => "All",
177            LogFilter::Log => "Log",
178            LogFilter::Warn => "Warn",
179            LogFilter::Error => "Error",
180        };
181        write!(formatter, "{}", label)
182    }
183}
184
185/// Implementation of log level badge rendering.
186impl LogLevel {
187    /// Returns the short badge label for a log level.
188    ///
189    /// # Returns
190    ///
191    /// - `&str` - The badge label string ("LOG", "WRN", "ERR").
192    pub(crate) fn badge(self) -> &'static str {
193        match self {
194            LogLevel::Log => "LOG",
195            LogLevel::Warn => "WRN",
196            LogLevel::Error => "ERR",
197        }
198    }
199}
200
201/// Implementation of log filter event handlers.
202impl LogFilter {
203    /// Creates a click event handler that sets the log filter to "All".
204    ///
205    /// # Arguments
206    ///
207    /// - `Signal<LogFilter>` - The signal controlling the active log filter.
208    ///
209    /// # Returns
210    ///
211    /// - `Option<Rc<dyn Fn(Event)>>` - A click handler that sets filter to All.
212    pub(crate) fn on_filter_all(filter_signal: Signal<LogFilter>) -> Option<Rc<dyn Fn(Event)>> {
213        Some(Rc::new(move |_: Event| {
214            filter_signal.set(LogFilter::All);
215        }))
216    }
217
218    /// Creates a click event handler that sets the log filter to "Log".
219    ///
220    /// # Arguments
221    ///
222    /// - `Signal<LogFilter>` - The signal controlling the active log filter.
223    ///
224    /// # Returns
225    ///
226    /// - `Option<Rc<dyn Fn(Event)>>` - A click handler that sets filter to Log.
227    pub(crate) fn on_filter_log(filter_signal: Signal<LogFilter>) -> Option<Rc<dyn Fn(Event)>> {
228        Some(Rc::new(move |_: Event| {
229            filter_signal.set(LogFilter::Log);
230        }))
231    }
232
233    /// Creates a click event handler that sets the log filter to "Warn".
234    ///
235    /// # Arguments
236    ///
237    /// - `Signal<LogFilter>` - The signal controlling the active log filter.
238    ///
239    /// # Returns
240    ///
241    /// - `Option<Rc<dyn Fn(Event)>>` - A click handler that sets filter to Warn.
242    pub(crate) fn on_filter_warn(filter_signal: Signal<LogFilter>) -> Option<Rc<dyn Fn(Event)>> {
243        Some(Rc::new(move |_: Event| {
244            filter_signal.set(LogFilter::Warn);
245        }))
246    }
247
248    /// Creates a click event handler that sets the log filter to "Error".
249    ///
250    /// # Arguments
251    ///
252    /// - `Signal<LogFilter>` - The signal controlling the active log filter.
253    ///
254    /// # Returns
255    ///
256    /// - `Option<Rc<dyn Fn(Event)>>` - A click handler that sets filter to Error.
257    pub(crate) fn on_filter_error(filter_signal: Signal<LogFilter>) -> Option<Rc<dyn Fn(Event)>> {
258        Some(Rc::new(move |_: Event| {
259            filter_signal.set(LogFilter::Error);
260        }))
261    }
262}