Skip to main content

06_log_viewer/
06_log_viewer.rs

1//! Example 06: Log Viewer
2//!
3//! Demonstrates auto-scrolling streaming text, simulating a log tail.
4//!
5//! Run with: cargo run -p telex-tui --example 06_log_viewer
6
7use crossterm::event::KeyCode;
8use crossterm::style::Color;
9use std::time::Duration;
10use telex::prelude::*;
11
12telex::require_api!(0, 2);
13
14fn main() {
15    telex::run_with_theme(App, telex::theme::Theme::nord()).unwrap();
16}
17
18struct App;
19
20impl Component for App {
21    fn render(&self, cx: Scope) -> View {
22        let show_help = state!(cx, || false);
23
24        // F1 toggles help
25        cx.use_command(
26            KeyBinding::key(KeyCode::F(1)),
27            with!(show_help => move || show_help.update(|v| *v = !*v)),
28        );
29
30        // Stream log entries
31        let logs = text_stream!(cx, || {
32            let log_messages = vec![
33                "[INFO]  Application started",
34                "[INFO]  Loading configuration...",
35                "[OK]    Config loaded successfully",
36                "[INFO]  Connecting to database...",
37                "[OK]    Database connected",
38                "[INFO]  Starting web server on :8080",
39                "[OK]    Server listening",
40                "[INFO]  Processing request GET /api/users",
41                "[OK]    Response 200 in 45ms",
42                "[INFO]  Processing request POST /api/login",
43                "[OK]    Response 200 in 120ms",
44                "[WARN]  High memory usage detected: 85%",
45                "[INFO]  Running garbage collection...",
46                "[OK]    Memory freed: 200MB",
47                "[INFO]  Processing request GET /api/data",
48                "[ERROR] Database timeout after 5000ms",
49                "[INFO]  Retrying database connection...",
50                "[OK]    Database reconnected",
51                "[OK]    Response 200 in 5045ms",
52                "[INFO]  Scheduled backup starting...",
53                "[OK]    Backup completed: 1.2GB",
54                "[INFO]  Processing request GET /api/health",
55                "[OK]    Response 200 in 12ms",
56                "[INFO]  Processing request PUT /api/users/123",
57                "[OK]    Response 200 in 89ms",
58                "[INFO]  Cache invalidation triggered",
59                "[OK]    Cache cleared: 50MB",
60                "[WARN]  Slow query detected: 1200ms",
61                "[INFO]  Query optimization suggested",
62                "[INFO]  Processing request DELETE /api/sessions",
63                "[OK]    Response 204 in 34ms",
64                "[INFO]  SSL certificate check",
65                "[OK]    Certificate valid for 45 days",
66                "[INFO]  Processing request POST /api/upload",
67                "[OK]    File uploaded: 15MB",
68                "[WARN]  Disk usage at 78%",
69                "[INFO]  Processing request GET /api/reports",
70                "[OK]    Response 200 in 230ms",
71                "[INFO]  Metrics exported to monitoring",
72                "[OK]    Heartbeat sent successfully",
73                "------- Log stream completed -------",
74            ];
75
76            log_messages.into_iter().map(|msg| {
77                std::thread::sleep(Duration::from_millis(500));
78                format!("{}\n", msg)
79            })
80        });
81
82        let log_text = logs.get();
83        let is_streaming = logs.is_loading();
84
85        // Color the status indicator
86        let status = if is_streaming {
87            View::styled_text(" [LIVE]")
88                .color(Color::Green)
89                .bold()
90                .build()
91        } else {
92            View::styled_text(" [END]").dim().build()
93        };
94
95        View::vstack()
96            .child(
97                View::hstack()
98                    .child(
99                        View::styled_text("Log Viewer")
100                            .color(Color::Cyan)
101                            .bold()
102                            .build(),
103                    )
104                    .child(status)
105                    .build(),
106            )
107            .child(View::gap(1))
108            .child(
109                View::boxed()
110                    .scroll(true)
111                    .auto_scroll_bottom(true)
112                    .min_height(15)
113                    .max_height(15)
114                    .child(View::text(&log_text))
115                    .build(),
116            )
117            .child(View::gap(1))
118            .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
119            .child(
120                View::modal()
121                    .visible(show_help.get())
122                    .title("Example 06: Log Viewer")
123                    .on_dismiss(with!(show_help => move || show_help.set(false)))
124                    .child(
125                        View::vstack()
126                            .child(View::styled_text("What you're seeing").bold().build())
127                            .child(View::text("• text_stream!() macro for accumulating text"))
128                            .child(View::text("• Auto-scrolling box that follows new content"))
129                            .child(View::text("• Live/End indicator based on stream state"))
130                            .child(View::gap(1))
131                            .child(View::styled_text("Key concepts").bold().build())
132                            .child(View::text("• text_stream! concatenates yielded strings"))
133                            .child(View::text(
134                                "• auto_scroll_bottom(true) keeps newest visible",
135                            ))
136                            .child(View::text("• Simulates tailing a log file"))
137                            .child(View::gap(1))
138                            .child(View::styled_text("Try this").bold().build())
139                            .child(View::text("• Watch the [LIVE] indicator change to [END]"))
140                            .child(View::text("• Notice auto-scroll keeps up with new entries"))
141                            .child(View::gap(1))
142                            .child(View::styled_text("Next up").bold().build())
143                            .child(View::text("→ 07_file_browser: real filesystem navigation"))
144                            .child(View::gap(1))
145                            .child(View::styled_text("Press Escape to close").dim().build())
146                            .build(),
147                    )
148                    .build(),
149            )
150            .build()
151    }
152}