106 lines
3.7 KiB
Rust
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."
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|