use crate::context::ResourceContext;
use crate::ui::core::MultiResourceComponent;
use ratatui::widgets::{Block, Borders, List, ListItem};
pub trait LogProvider: Send + Sync + 'static {
fn log_messages(&self) -> &[String];
}
pub struct LogComponent<T: LogProvider> {
title: String,
_phantom: std::marker::PhantomData<T>,
}
impl<T: LogProvider> LogComponent<T> {
pub fn new() -> Self {
Self::with_title("Log")
}
pub fn with_title(title: impl Into<String>) -> Self {
Self {
title: title.into(),
_phantom: std::marker::PhantomData,
}
}
pub fn render_with_dynamic_title<F>(
&self,
resources: &ResourceContext,
title_fn: F,
) -> Option<List<'static>>
where
F: FnOnce(&T) -> String,
{
let provider = resources.try_get::<T>()?;
let title = title_fn(&*provider);
let items: Vec<ListItem> = provider
.log_messages()
.iter()
.map(|msg| ListItem::new(msg.clone()))
.collect();
Some(List::new(items).block(Block::default().borders(Borders::ALL).title(title)))
}
}
impl<T: LogProvider> Default for LogComponent<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: LogProvider> MultiResourceComponent for LogComponent<T> {
type Output = List<'static>;
fn render_multi(&self, resources: &ResourceContext) -> Option<Self::Output> {
let provider = resources.try_get::<T>()?;
let items: Vec<ListItem> = provider
.log_messages()
.iter()
.map(|msg| ListItem::new(msg.clone()))
.collect();
Some(
List::new(items).block(
Block::default()
.borders(Borders::ALL)
.title(self.title.clone()),
),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::context::ResourceContext;
#[derive(Debug, Clone)]
struct TestLog {
messages: Vec<String>,
}
impl LogProvider for TestLog {
fn log_messages(&self) -> &[String] {
&self.messages
}
}
#[test]
fn test_log_component() {
let mut resources = ResourceContext::new();
resources.insert(TestLog {
messages: vec!["Message 1".to_string(), "Message 2".to_string()],
});
let component = LogComponent::<TestLog>::new();
let widget = component.render_multi(&resources);
assert!(widget.is_some());
}
#[test]
fn test_log_component_with_custom_title() {
let mut resources = ResourceContext::new();
resources.insert(TestLog {
messages: vec!["Test".to_string()],
});
let component = LogComponent::<TestLog>::with_title("Custom Log");
let widget = component.render_multi(&resources);
assert!(widget.is_some());
}
#[test]
fn test_log_component_dynamic_title() {
let mut resources = ResourceContext::new();
resources.insert(TestLog {
messages: vec!["A".to_string(), "B".to_string()],
});
let component = LogComponent::<TestLog>::new();
let widget = component.render_with_dynamic_title(&resources, |log| {
format!("Log ({} messages)", log.log_messages().len())
});
assert!(widget.is_some());
}
#[test]
fn test_log_component_missing_resource() {
let resources = ResourceContext::new();
let component = LogComponent::<TestLog>::new();
let widget = component.render_multi(&resources);
assert!(widget.is_none());
}
}