use crossterm::event::KeyCode;
use std::thread;
use std::time::Duration;
use telex::prelude::*;
use telex::Color;
telex::require_api!(0, 1);
fn main() {
telex::run_with_theme(App, telex::theme::Theme::nord()).unwrap();
}
struct App;
#[derive(Clone)]
struct UserProfile {
name: String,
email: String,
member_since: String,
}
#[derive(Clone)]
struct Stats {
posts: u32,
followers: u32,
following: u32,
}
impl Component for App {
fn render(&self, cx: Scope) -> View {
let show_help = state!(cx, || false);
cx.use_command(
KeyBinding::key(KeyCode::F(1)),
with!(show_help => move || show_help.update(|v| *v = !*v)),
);
let profile = cx.use_async(|| {
thread::sleep(Duration::from_secs(2));
Ok(UserProfile {
name: "Alice Johnson".to_string(),
email: "alice@example.com".to_string(),
member_since: "January 2024".to_string(),
})
});
let stats = cx.use_async(|| {
thread::sleep(Duration::from_secs(1));
Ok(Stats {
posts: 142,
followers: 1234,
following: 567,
})
});
let failing_data = cx.use_async(|| {
thread::sleep(Duration::from_millis(500));
Err::<String, _>("Network error: Connection refused".to_string())
});
View::vstack()
.spacing(1)
.child(
View::boxed()
.border(true)
.padding(1)
.child(
View::vstack()
.child(View::styled_text("Async Data Loading Demo").bold().build())
.child(
View::styled_text(
"Demonstrates use_async for loading data with loading/error states",
)
.dim()
.build(),
)
.build(),
)
.build(),
)
.child(
View::hstack()
.spacing(1)
.child(
View::boxed()
.flex(1)
.border(true)
.padding(1)
.child(render_profile_section(&profile))
.build(),
)
.child(
View::boxed()
.flex(1)
.border(true)
.padding(1)
.child(render_stats_section(&stats))
.build(),
)
.child(
View::boxed()
.flex(1)
.border(true)
.padding(1)
.child(render_error_section(&failing_data))
.build(),
)
.build(),
)
.child(
View::boxed()
.border(true)
.padding(1)
.child(
View::vstack()
.child(View::text(format!(
"Overall status: {}",
if profile.is_loading() || stats.is_loading() {
"Loading..."
} else if profile.is_error() || stats.is_error() || failing_data.is_error() {
"Some requests failed"
} else {
"All data loaded"
}
)))
.child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
.build(),
)
.build(),
)
.child(
View::modal()
.visible(show_help.get())
.title("Example 24: Async Data")
.on_dismiss(with!(show_help => move || show_help.set(false)))
.child(
View::vstack()
.child(View::styled_text("What you're seeing").bold().build())
.child(View::text("• Three async data loads in parallel"))
.child(View::text("• Loading, success, and error states"))
.child(View::text("• Different load times for each"))
.child(View::gap(1))
.child(View::styled_text("Key concepts").bold().build())
.child(View::text("• cx.use_async() runs in background"))
.child(View::text("• Returns Async<T> enum"))
.child(View::text("• .is_loading() / .is_error() helpers"))
.child(View::text("• Pattern match for state handling"))
.child(View::gap(1))
.child(View::styled_text("Try this").bold().build())
.child(View::text("• Watch data load progressively"))
.child(View::text("• Notice the failing request"))
.child(View::gap(1))
.child(View::styled_text("Next up").bold().build())
.child(View::text("→ 25_context: context API"))
.child(View::gap(1))
.child(View::styled_text("Press Escape to close").dim().build())
.build()
)
.build()
)
.build()
}
}
fn render_profile_section(profile: &Async<UserProfile>) -> View {
View::vstack()
.spacing(1)
.child(
View::styled_text("User Profile")
.bold()
.color(Color::Cyan)
.build(),
)
.child(View::styled_text("(loads in 2s)").dim().build())
.child(View::gap(1))
.child(match profile {
Async::Loading => View::vstack()
.child(View::styled_text("Loading...").dim().build())
.child(View::text("[=========> ]"))
.build(),
Async::Ready(p) => View::vstack()
.spacing(0)
.child(View::text(format!("Name: {}", p.name)))
.child(View::text(format!("Email: {}", p.email)))
.child(View::text(format!("Member since: {}", p.member_since)))
.build(),
Async::Error(e) => View::styled_text(format!("Error: {}", e))
.color(Color::Red)
.build(),
})
.build()
}
fn render_stats_section(stats: &Async<Stats>) -> View {
View::vstack()
.spacing(1)
.child(
View::styled_text("User Stats")
.bold()
.color(Color::Green)
.build(),
)
.child(View::styled_text("(loads in 1s)").dim().build())
.child(View::gap(1))
.child(match stats {
Async::Loading => View::vstack()
.child(View::styled_text("Loading...").dim().build())
.child(View::text("[==================> ]"))
.build(),
Async::Ready(s) => View::vstack()
.spacing(0)
.child(View::text(format!("Posts: {}", s.posts)))
.child(View::text(format!("Followers: {}", s.followers)))
.child(View::text(format!("Following: {}", s.following)))
.build(),
Async::Error(e) => View::styled_text(format!("Error: {}", e))
.color(Color::Red)
.build(),
})
.build()
}
fn render_error_section(data: &Async<String>) -> View {
View::vstack()
.spacing(1)
.child(
View::styled_text("Failing Request")
.bold()
.color(Color::Red)
.build(),
)
.child(View::styled_text("(fails after 0.5s)").dim().build())
.child(View::gap(1))
.child(match data {
Async::Loading => View::vstack()
.child(View::styled_text("Loading...").dim().build())
.child(View::text("[=====================]"))
.build(),
Async::Ready(d) => View::text(format!("Data: {}", d)),
Async::Error(e) => View::vstack()
.child(
View::styled_text("Request failed!")
.color(Color::Red)
.build(),
)
.child(View::styled_text(e.clone()).dim().build())
.build(),
})
.build()
}