A sweep that read every render found 28 roots still on w_full, reached by 64 components: the field button under Select and every picker, the checked and mention fields, the slider track, NumberInput, Steps, the state views, the agent previews, and every chart and finance frame. All drop it. LivePreview's size_full becomes h_full, since its card no longer fills. Every host of those roots was listed and read, 231 in all. Five tests that mounted a picker as the window root sit in a size_full box, the index manager gives its method select a flex_1 box, and the notification center and the gallery's state frames stack their state views in a column so they stretch. Each dropped root carries a debug selector, and six chapter tests mount 43 components in a padded card whose column is measured by content, through layout::tests::narrow_width: each root must take the full 240px. With w_full back on the roots, all six fail. AGENTS.md names the roots that keep w_full and why: picture frames that keep a ratio, and rows that hand a text field its share.
338 lines
12 KiB
Rust
338 lines
12 KiB
Rust
use std::time::{Duration, Instant};
|
|
|
|
use anyhow::anyhow;
|
|
use ely_gpui_component::{
|
|
buttons::{Button, ButtonVariant},
|
|
feedback::{
|
|
ConfirmationCard, ConnectionStatus, Countdown, ErrorBoundary, ErrorView, InlineMessage,
|
|
ResultView, SaveState, SavingIndicator, SyncState, SyncStatus, Timer,
|
|
},
|
|
primitives::Severity,
|
|
shell::Connectivity,
|
|
theme::{ActiveTheme, Radius, TextSize},
|
|
typography::Caption,
|
|
};
|
|
use gpui::{App, Div, IntoElement, ParentElement, Styled, Window, div, prelude::FluentBuilder};
|
|
|
|
use crate::{
|
|
probe::probe,
|
|
ui::{keep, row, section, set, specimen, specimens},
|
|
};
|
|
|
|
fn frame(cx: &App) -> Div {
|
|
let theme = cx.theme();
|
|
div()
|
|
.flex_1()
|
|
.min_w_0()
|
|
.h_80()
|
|
.flex()
|
|
.flex_col()
|
|
.justify_center()
|
|
.rounded(theme.radius(Radius::Lg))
|
|
.border_1()
|
|
.border_color(theme.colors.border)
|
|
}
|
|
|
|
pub fn errors(cx: &mut App) -> impl IntoElement + use<> {
|
|
section(
|
|
"ErrorView / NotFound / NoPermission / Maintenance / Offline",
|
|
"A view that could not show what was asked says what happened and offers a way on.",
|
|
cx,
|
|
)
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.gap_3()
|
|
.child(frame(cx).child(
|
|
ErrorView::not_found("error-404").action(Button::new("error-home", "Go home")),
|
|
))
|
|
.child(
|
|
frame(cx).child(
|
|
ErrorView::no_permission("error-403")
|
|
.action(Button::new("error-ask", "Request access").primary()),
|
|
),
|
|
),
|
|
)
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.gap_3()
|
|
.child(frame(cx).child(ErrorView::maintenance("error-maintenance")))
|
|
.child(frame(cx).child(
|
|
ErrorView::offline("error-offline").action(Button::new("error-retry", "Try again")),
|
|
)),
|
|
)
|
|
}
|
|
|
|
pub fn boundary(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let broken = keep("boundary-broken", || false, window, cx);
|
|
let (now, toggle, retry) = (*broken.read(cx), broken.clone(), broken.clone());
|
|
let theme = cx.theme();
|
|
let content = if now {
|
|
Err(anyhow!("the export service answered 503").context("Revenue, third quarter"))
|
|
} else {
|
|
Ok(div()
|
|
.size_full()
|
|
.flex()
|
|
.items_center()
|
|
.justify_center()
|
|
.text_color(theme.colors.fg_muted)
|
|
.child("Revenue, third quarter: 1.2 M"))
|
|
};
|
|
section(
|
|
"ErrorBoundary",
|
|
"Shows its content, or the error it holds, logged once. It renders a Result; it never catches a panic.",
|
|
cx,
|
|
)
|
|
.child(
|
|
div().flex().child(
|
|
frame(cx).child(
|
|
ErrorBoundary::new("boundary", content)
|
|
.on_retry(move |_, cx| set(&retry, false, cx)),
|
|
),
|
|
),
|
|
)
|
|
.child(
|
|
row().child(probe(
|
|
"boundary-break",
|
|
Button::new("boundary-toggle", if now { "Fix it" } else { "Break it" })
|
|
.on_click(move |_, _, cx| set(&toggle, !now, cx)),
|
|
)),
|
|
)
|
|
}
|
|
|
|
pub fn results(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let turn = keep("result-turn", || 0usize, window, cx);
|
|
let (now, replay) = (*turn.read(cx), turn.clone());
|
|
section(
|
|
"Result",
|
|
"Where a task ended. The ring and its mark draw themselves once.",
|
|
cx,
|
|
)
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.gap_3()
|
|
.child(
|
|
frame(cx).child(
|
|
ResultView::success(("result-ok", now), "Payment sent")
|
|
.body("€240.00 to Studio Ottanta. A receipt is on its way.")
|
|
.action(Button::new("result-done", "Done").primary())
|
|
.action(Button::new("result-receipt", "View receipt")),
|
|
),
|
|
)
|
|
.child(
|
|
frame(cx).child(
|
|
ResultView::failure(("result-no", now), "Payment declined")
|
|
.body("Your bank declined the charge. No money moved.")
|
|
.action(Button::new("result-again", "Try again")),
|
|
),
|
|
),
|
|
)
|
|
.child(
|
|
row().child(probe(
|
|
"result-replay",
|
|
Button::new("result-replay", "Replay")
|
|
.variant(ButtonVariant::Ghost)
|
|
.on_click(move |_, _, cx| set(&replay, now + 1, cx)),
|
|
)),
|
|
)
|
|
}
|
|
|
|
pub fn confirmations(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let said = keep("confirm-said", || None::<&'static str>, window, cx);
|
|
let theme = cx.theme();
|
|
let answer = |what: &'static str| {
|
|
let said = said.clone();
|
|
move |_: &mut Window, cx: &mut App| set(&said, Some(what), cx)
|
|
};
|
|
let command = div()
|
|
.px_3()
|
|
.py_2()
|
|
.rounded(theme.radius(Radius::Md))
|
|
.bg(theme.colors.sunken)
|
|
.font_family(theme.mono_family.clone())
|
|
.text_size(theme.text_size(TextSize::Sm))
|
|
.child("cargo clean && cargo build --release");
|
|
section(
|
|
"ConfirmationCard",
|
|
"Asks in place before an action runs, with what it will do.",
|
|
cx,
|
|
)
|
|
.child(
|
|
div()
|
|
.w_128()
|
|
.flex()
|
|
.flex_col()
|
|
.gap_3()
|
|
.child(
|
|
ConfirmationCard::new("confirm-run", "Run this command?")
|
|
.body("The agent wants to rebuild the project from scratch.")
|
|
.confirm("Run")
|
|
.on_confirm(answer("Ran the command"))
|
|
.on_cancel(answer("Kept the build"))
|
|
.child(command),
|
|
)
|
|
.child(
|
|
ConfirmationCard::new("confirm-delete", "Delete 3 branches?")
|
|
.body("feature/toasts, feature/menus and old/draft go for good.")
|
|
.confirm("Delete")
|
|
.destructive()
|
|
.on_confirm(answer("Deleted 3 branches"))
|
|
.on_cancel(answer("Kept the branches")),
|
|
)
|
|
.when_some(*said.read(cx), |cards, what| {
|
|
cards.child(InlineMessage::new(Severity::Info, what))
|
|
}),
|
|
)
|
|
}
|
|
|
|
pub fn statuses(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
const SAVES: [SaveState; 3] = [SaveState::Saving, SaveState::Saved, SaveState::Failed];
|
|
const SYNCS: [SyncState; 4] = [
|
|
SyncState::Syncing(12),
|
|
SyncState::Synced,
|
|
SyncState::Paused,
|
|
SyncState::Failed,
|
|
];
|
|
const LINKS: [Connectivity; 3] = [
|
|
Connectivity::Online,
|
|
Connectivity::Reconnecting,
|
|
Connectivity::Offline,
|
|
];
|
|
let at = keep("status-states", || [0usize; 3], window, cx);
|
|
let now = *at.read(cx);
|
|
let next = |slot: usize, count: usize| {
|
|
let at = at.clone();
|
|
move |_: &gpui::ClickEvent, _: &mut Window, cx: &mut App| {
|
|
let mut then = now;
|
|
then[slot] = (now[slot] + 1) % count;
|
|
set(&at, then, cx)
|
|
}
|
|
};
|
|
let retry = at.clone();
|
|
section(
|
|
"SavingIndicator / SyncStatus / ConnectionStatus",
|
|
"Quiet lines for work in the background. Each rises in when its state changes; sync arrows turn while they work.",
|
|
cx,
|
|
)
|
|
.child(
|
|
specimens()
|
|
.child(specimen(
|
|
"saving",
|
|
row()
|
|
.child(probe(
|
|
"saving-next",
|
|
Button::new("saving-next", "Next").on_click(next(0, SAVES.len())),
|
|
))
|
|
.child(
|
|
SavingIndicator::new("saving", SAVES[now[0]]).on_retry(move |_, cx| {
|
|
let mut then = now;
|
|
then[0] = 0;
|
|
set(&retry, then, cx)
|
|
}),
|
|
),
|
|
cx,
|
|
))
|
|
.child(specimen(
|
|
"sync",
|
|
row()
|
|
.child(probe(
|
|
"sync-next",
|
|
Button::new("sync-next", "Next").on_click(next(1, SYNCS.len())),
|
|
))
|
|
.child(SyncStatus::new("sync", SYNCS[now[1]])),
|
|
cx,
|
|
))
|
|
.child(specimen(
|
|
"connection",
|
|
row()
|
|
.child(probe(
|
|
"connection-next",
|
|
Button::new("connection-next", "Next").on_click(next(2, LINKS.len())),
|
|
))
|
|
.child(ConnectionStatus::new("connection", LINKS[now[2]])),
|
|
cx,
|
|
)),
|
|
)
|
|
}
|
|
|
|
pub fn clocks(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let opened = keep("clock-opened", Instant::now, window, cx);
|
|
let short = keep(
|
|
"clock-short",
|
|
|| Instant::now() + Duration::from_secs(10),
|
|
window,
|
|
cx,
|
|
);
|
|
let stopped = keep("clock-stopped", || None::<Instant>, window, cx);
|
|
let over = keep("clock-over", || false, window, cx);
|
|
let (since, until, halt) = (*opened.read(cx), *short.read(cx), *stopped.read(cx));
|
|
let (restart, toggle, done, reset) =
|
|
(short.clone(), stopped.clone(), over.clone(), opened.clone());
|
|
let reopen = over.clone();
|
|
section(
|
|
"Countdown / Timer",
|
|
"Time left and time spent. Digits roll as they change.",
|
|
cx,
|
|
)
|
|
.child(
|
|
specimens()
|
|
.child(specimen(
|
|
"launch in",
|
|
Countdown::new(
|
|
"countdown-launch",
|
|
since + Duration::from_secs(2 * 3_600 + 14 * 60 + 7),
|
|
),
|
|
cx,
|
|
))
|
|
.child(specimen(
|
|
"ten seconds",
|
|
div()
|
|
.flex()
|
|
.items_center()
|
|
.gap_3()
|
|
.child(
|
|
Countdown::new("countdown-short", until)
|
|
.on_done(move |_, cx| set(&done, true, cx)),
|
|
)
|
|
.child(probe(
|
|
"countdown-restart",
|
|
Button::new("countdown-restart", "Restart")
|
|
.variant(ButtonVariant::Ghost)
|
|
.on_click(move |_, _, cx| {
|
|
set(&restart, Instant::now() + Duration::from_secs(10), cx);
|
|
set(&reopen, false, cx);
|
|
}),
|
|
)),
|
|
cx,
|
|
))
|
|
.child(specimen(
|
|
"on this page",
|
|
div()
|
|
.flex()
|
|
.items_center()
|
|
.gap_3()
|
|
.child(Timer::new("timer", since).stopped(halt))
|
|
.child(
|
|
Button::new(
|
|
"timer-toggle",
|
|
if halt.is_some() { "Reset" } else { "Stop" },
|
|
)
|
|
.variant(ButtonVariant::Ghost)
|
|
.on_click(move |_, _, cx| match halt {
|
|
Some(_) => {
|
|
set(&reset, Instant::now(), cx);
|
|
set(&toggle, None, cx);
|
|
}
|
|
None => set(&toggle, Some(Instant::now()), cx),
|
|
}),
|
|
),
|
|
cx,
|
|
)),
|
|
)
|
|
.when(*over.read(cx), |section| {
|
|
section.child(Caption::new("The ten seconds ran out; on_done ran once."))
|
|
})
|
|
}
|