06_log_viewer/
06_log_viewer.rs1use 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 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 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 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}