Files
ruin/aspirational_examples/01_async_data_and_effects.rs

106 lines
3.7 KiB
Rust

//! Aspirational RUIN app API for async resources and effects.
//!
//! Intentionally non-compiling; this is a design sketch.
use ruin::prelude::*;
#[ruin_runtime::async_main]
async fn main() -> ruin::Result<()> {
App::new()
.window(Window::new().title("RUIN Issue Dashboard").size(1240.0, 820.0))
.mount::<IssueDashboard>()
.run()
.await
}
#[component]
fn IssueDashboard() -> impl View {
let repo = use_signal(|| "wtemple/ruin".to_string());
let selected_issue = use_signal(|| None::<u64>);
let issues = use_resource(move || {
let repo = repo.get();
async move { github::list_issues(&repo).await }
});
let details = use_resource(move || {
let repo = repo.get();
let issue_id = selected_issue.get();
async move {
let issue_id = issue_id?;
github::issue_details(&repo, issue_id).await.ok()
}
});
use_effect(move || {
tracing::info!(
repo = %repo.get(),
selected_issue = ?selected_issue.get(),
"dashboard dependencies changed"
);
});
view! {
row(fill = true, gap = 20, padding = 20) {
block(width = 360, gap = 12) {
text(role = TextRole::Heading(1), size = 28, weight = FontWeight::Semibold) {
"Issues"
}
text_input(value = repo, placeholder = "owner/repo") {}
suspense(fallback = || view! { ProgressSpinner(label = "Loading issues...") {} }) {
match issues.read() {
Ok(items) => view! {
list(gap = 8) {
for issue in items keyed by issue.id {
button(
kind = if selected_issue.get() == Some(issue.id) {
ButtonKind::Primary
} else {
ButtonKind::Secondary
},
on_press = move |_| selected_issue.set(Some(issue.id)),
) {
issue.title
}
}
}
},
Err(error) => view! {
ErrorPanel(
title = "Failed to load issues",
detail = error.to_string(),
) {}
},
}
}
}
block(fill = true, gap = 16) {
text(role = TextRole::Heading(2), size = 24, weight = FontWeight::Semibold) {
"Details"
}
match details.read() {
Pending => view! { ProgressSpinner(label = "Loading issue details...") {} },
Ready(Some(issue)) => view! {
column(gap = 12) {
text(role = TextRole::Heading(3), size = 22, weight = FontWeight::Semibold) {
issue.title
}
Markdown(source = issue.body) {}
}
},
Ready(None) => view! {
EmptyState(title = "Pick an issue") {
"The detail panel should track the current selection and re-run only \
the async work that actually depends on it."
}
},
}
}
}
}
}