Refine GPUI shell UX surfaces
This commit is contained in:
1 parent
22ba7a9004
commit
6809df15de
7 files changed
+617
-122
No files matched your search
@@ -14,6 +14,7 @@ mod approvals;
|
||||
mod documents;
|
||||
mod inbox;
|
||||
mod messages;
|
||||
mod messages_parts;
|
||||
mod onboarding;
|
||||
mod overview;
|
||||
mod runs;
|
||||
@@ -267,7 +268,7 @@ impl Render for AppRoot {
|
||||
div()
|
||||
.size_full()
|
||||
.flex()
|
||||
.bg(theme::canvas())
|
||||
.bg(theme::window_bg())
|
||||
.child(self.render_sidebar(cx))
|
||||
.child(
|
||||
div()
|
||||
@@ -276,30 +277,60 @@ impl Render for AppRoot {
|
||||
.flex_col()
|
||||
.child(
|
||||
div()
|
||||
.px_6()
|
||||
.py_4()
|
||||
.px_8()
|
||||
.py_5()
|
||||
.border_b_1()
|
||||
.border_color(theme::panel_border())
|
||||
.bg(theme::shell_header())
|
||||
.child(
|
||||
div()
|
||||
.text_xl()
|
||||
.text_color(theme::text_primary())
|
||||
.child(header),
|
||||
)
|
||||
.child(div().text_sm().text_color(theme::text_muted()).child(
|
||||
self.state.status_line.clone().unwrap_or_else(|| {
|
||||
"Native Rust shell is reading the shared local profile."
|
||||
.to_string()
|
||||
}),
|
||||
)),
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.child(
|
||||
div()
|
||||
.child(
|
||||
div()
|
||||
.text_3xl()
|
||||
.text_color(theme::text_primary())
|
||||
.child(self.state.section.label()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(theme::text_muted())
|
||||
.child(header),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(theme::text_subtle())
|
||||
.child(self.state.status_line.clone().unwrap_or_else(|| {
|
||||
"Native Rust shell is reading the shared local profile."
|
||||
.to_string()
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.px_4()
|
||||
.py_3()
|
||||
.rounded_lg()
|
||||
.border_1()
|
||||
.border_color(theme::panel_border())
|
||||
.bg(theme::panel())
|
||||
.text_sm()
|
||||
.text_color(theme::text_secondary())
|
||||
.child("Show console"),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.id("main-scroll")
|
||||
.flex_1()
|
||||
.overflow_scroll()
|
||||
.p_6()
|
||||
.p_8()
|
||||
.child(match self.state.section {
|
||||
SectionId::Overview => self.render_overview().into_any_element(),
|
||||
SectionId::Runs => self.render_runs(cx).into_any_element(),
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
messages_parts::{
|
||||
compact_message_card, composer_chip, composer_subject, filter_shell, message_section_label,
|
||||
search_shell, secondary_message, selected_message_card, selected_recipient,
|
||||
},
|
||||
*,
|
||||
};
|
||||
|
||||
impl AppRoot {
|
||||
pub(super) fn render_messages(&self, cx: &mut Context<Self>) -> impl IntoElement + '_ {
|
||||
@@ -8,36 +14,60 @@ impl AppRoot {
|
||||
.iter()
|
||||
.find(|item| &item.id == id)
|
||||
});
|
||||
let selected_subject = selected
|
||||
.map(|item| item.subject.clone())
|
||||
.unwrap_or_else(|| "Select a thread".to_string());
|
||||
let selected_body = selected
|
||||
.map(|item| item.body.clone())
|
||||
.unwrap_or_else(|| "No message selected.".to_string());
|
||||
let company_wide = self
|
||||
.snapshot
|
||||
.agent_messages
|
||||
.iter()
|
||||
.filter(|item| item.channel == "company")
|
||||
.collect::<Vec<_>>();
|
||||
let department = self
|
||||
.snapshot
|
||||
.agent_messages
|
||||
.iter()
|
||||
.filter(|item| item.channel == "department")
|
||||
.collect::<Vec<_>>();
|
||||
let direct = self
|
||||
.snapshot
|
||||
.agent_messages
|
||||
.iter()
|
||||
.filter(|item| item.channel == "direct")
|
||||
.collect::<Vec<_>>();
|
||||
let task_threads = self
|
||||
.snapshot
|
||||
.agent_messages
|
||||
.iter()
|
||||
.filter(|item| {
|
||||
item.channel != "company"
|
||||
&& item.channel != "department"
|
||||
&& item.channel != "direct"
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
div()
|
||||
.flex()
|
||||
.gap_4()
|
||||
.child(
|
||||
div()
|
||||
.id("messages-list")
|
||||
.w(px(320.0))
|
||||
.p_4()
|
||||
.gap_2()
|
||||
.w(px(356.0))
|
||||
.p_5()
|
||||
.gap_4()
|
||||
.border_1()
|
||||
.rounded_md()
|
||||
.rounded_lg()
|
||||
.border_color(theme::panel_border())
|
||||
.bg(theme::panel())
|
||||
.overflow_scroll()
|
||||
.child(div().text_lg().child("Channels"))
|
||||
.children(self.snapshot.agent_messages.iter().map(|item| {
|
||||
.child(message_section_label("COMPANY-WIDE"))
|
||||
.children(company_wide.iter().map(|message| {
|
||||
let active =
|
||||
self.state.selected_message_id.as_deref() == Some(item.id.as_str());
|
||||
let message_id = item.id.clone();
|
||||
self.state.selected_message_id.as_deref() == Some(message.id.as_str());
|
||||
let message_id = message.id.clone();
|
||||
div()
|
||||
.id(SharedString::from(format!("message-{}", item.id)))
|
||||
.px_3()
|
||||
.py_2()
|
||||
.rounded_md()
|
||||
.id(SharedString::from(format!("message-{}", message.id)))
|
||||
.flex()
|
||||
.justify_between()
|
||||
.items_center()
|
||||
.px_4()
|
||||
.py_3()
|
||||
.rounded_lg()
|
||||
.cursor_pointer()
|
||||
.bg(if active {
|
||||
theme::selection_fill()
|
||||
@@ -49,46 +79,247 @@ impl AppRoot {
|
||||
this.state.selected_message_id = Some(message_id.clone());
|
||||
cx.notify();
|
||||
}))
|
||||
.child(format!("{} · {}", item.subject, item.priority))
|
||||
})),
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(if active {
|
||||
theme::sidebar_text_active()
|
||||
} else {
|
||||
theme::text_secondary()
|
||||
})
|
||||
.truncate()
|
||||
.child(message.subject.clone()),
|
||||
)
|
||||
.when(message.priority == "urgent", |container| {
|
||||
container.child(
|
||||
div()
|
||||
.w(px(10.0))
|
||||
.h(px(10.0))
|
||||
.rounded_full()
|
||||
.bg(theme::danger()),
|
||||
)
|
||||
})
|
||||
}))
|
||||
.child(message_section_label("DEPARTMENT"))
|
||||
.children(department.iter().map(|message| {
|
||||
let active =
|
||||
self.state.selected_message_id.as_deref() == Some(message.id.as_str());
|
||||
let message_id = message.id.clone();
|
||||
div()
|
||||
.id(SharedString::from(format!("message-{}", message.id)))
|
||||
.flex()
|
||||
.justify_between()
|
||||
.items_center()
|
||||
.px_4()
|
||||
.py_3()
|
||||
.rounded_lg()
|
||||
.cursor_pointer()
|
||||
.bg(if active {
|
||||
theme::selection_fill()
|
||||
} else {
|
||||
theme::panel()
|
||||
})
|
||||
.hover(|style| style.bg(theme::panel_hover()))
|
||||
.on_click(cx.listener(move |this, _, _, cx| {
|
||||
this.state.selected_message_id = Some(message_id.clone());
|
||||
cx.notify();
|
||||
}))
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(if active {
|
||||
theme::sidebar_text_active()
|
||||
} else {
|
||||
theme::text_secondary()
|
||||
})
|
||||
.truncate()
|
||||
.child(message.subject.clone()),
|
||||
)
|
||||
}))
|
||||
.child(message_section_label("DIRECT MESSAGES"))
|
||||
.children(direct.iter().map(|message| {
|
||||
let active =
|
||||
self.state.selected_message_id.as_deref() == Some(message.id.as_str());
|
||||
let message_id = message.id.clone();
|
||||
div()
|
||||
.id(SharedString::from(format!("message-{}", message.id)))
|
||||
.flex()
|
||||
.justify_between()
|
||||
.items_center()
|
||||
.px_4()
|
||||
.py_3()
|
||||
.rounded_lg()
|
||||
.cursor_pointer()
|
||||
.bg(if active {
|
||||
theme::selection_fill()
|
||||
} else {
|
||||
theme::panel()
|
||||
})
|
||||
.hover(|style| style.bg(theme::panel_hover()))
|
||||
.on_click(cx.listener(move |this, _, _, cx| {
|
||||
this.state.selected_message_id = Some(message_id.clone());
|
||||
cx.notify();
|
||||
}))
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(if active {
|
||||
theme::sidebar_text_active()
|
||||
} else {
|
||||
theme::text_secondary()
|
||||
})
|
||||
.truncate()
|
||||
.child(message.subject.clone()),
|
||||
)
|
||||
.when(message.read_at.is_none(), |container| {
|
||||
container.child(
|
||||
div()
|
||||
.w(px(10.0))
|
||||
.h(px(10.0))
|
||||
.rounded_full()
|
||||
.bg(theme::danger()),
|
||||
)
|
||||
})
|
||||
}))
|
||||
.child(message_section_label("TASK DISCUSSIONS"))
|
||||
.children(task_threads.iter().map(|message| {
|
||||
let active =
|
||||
self.state.selected_message_id.as_deref() == Some(message.id.as_str());
|
||||
let message_id = message.id.clone();
|
||||
div()
|
||||
.id(SharedString::from(format!("message-{}", message.id)))
|
||||
.flex()
|
||||
.justify_between()
|
||||
.items_center()
|
||||
.px_4()
|
||||
.py_3()
|
||||
.rounded_lg()
|
||||
.cursor_pointer()
|
||||
.bg(if active {
|
||||
theme::selection_fill()
|
||||
} else {
|
||||
theme::panel()
|
||||
})
|
||||
.hover(|style| style.bg(theme::panel_hover()))
|
||||
.on_click(cx.listener(move |this, _, _, cx| {
|
||||
this.state.selected_message_id = Some(message_id.clone());
|
||||
cx.notify();
|
||||
}))
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(if active {
|
||||
theme::sidebar_text_active()
|
||||
} else {
|
||||
theme::text_secondary()
|
||||
})
|
||||
.truncate()
|
||||
.child(message.subject.clone()),
|
||||
)
|
||||
}))
|
||||
.child(div().flex_1())
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.justify_between()
|
||||
.text_xs()
|
||||
.text_color(theme::text_muted())
|
||||
.child(format!("{} channels", self.snapshot.agent_messages.len()))
|
||||
.child(format!("{} agents", self.snapshot.agents.len())),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.p_4()
|
||||
.gap_3()
|
||||
.border_1()
|
||||
.rounded_md()
|
||||
.rounded_lg()
|
||||
.border_color(theme::panel_border())
|
||||
.bg(theme::panel())
|
||||
.child(div().text_lg().child("Conversation"))
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(theme::text_muted())
|
||||
.child(selected_subject),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.min_h(px(220.0))
|
||||
.p_3()
|
||||
.border_1()
|
||||
.border_color(theme::panel_inset_border())
|
||||
.rounded_md()
|
||||
.bg(theme::panel_inset())
|
||||
.child(selected_body),
|
||||
)
|
||||
.child(self.message_input.clone())
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.gap_2()
|
||||
.child(primary_button("Send update", cx, |this, cx| {
|
||||
this.send_message(cx)
|
||||
}))
|
||||
.child(secondary_button("Mark read", cx, |this, cx| {
|
||||
this.mark_selected_message_read(cx)
|
||||
})),
|
||||
.justify_between()
|
||||
.items_center()
|
||||
.px_5()
|
||||
.py_4()
|
||||
.border_b_1()
|
||||
.border_color(theme::panel_border())
|
||||
.child(
|
||||
div().text_lg().text_color(theme::text_primary()).child(
|
||||
selected
|
||||
.map(|message| message.subject.clone())
|
||||
.unwrap_or_else(|| "Select a conversation".to_string()),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.gap_3()
|
||||
.child(search_shell("Search messages"))
|
||||
.child(filter_shell()),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.id("messages-stream")
|
||||
.flex_1()
|
||||
.p_4()
|
||||
.overflow_scroll()
|
||||
.child(selected_message_card(selected))
|
||||
.when_some(secondary_message(selected), |container, message| {
|
||||
container.child(compact_message_card(message))
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.px_4()
|
||||
.py_3()
|
||||
.border_t_1()
|
||||
.border_color(theme::panel_border())
|
||||
.bg(theme::panel())
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.gap_3()
|
||||
.child(composer_chip(selected_recipient(selected).as_str()))
|
||||
.child(composer_subject(selected)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.mt(px(12.0))
|
||||
.p_3()
|
||||
.rounded_lg()
|
||||
.bg(theme::panel_inset())
|
||||
.border_1()
|
||||
.border_color(theme::panel_inset_border())
|
||||
.child(self.message_input.clone()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.mt(px(10.0))
|
||||
.flex()
|
||||
.justify_between()
|
||||
.items_center()
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(theme::text_muted())
|
||||
.child("Press Cmd+Enter to send"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.gap_2()
|
||||
.child(primary_button("Send update", cx, |this, cx| {
|
||||
this.send_message(cx)
|
||||
}))
|
||||
.child(secondary_button(
|
||||
"Mark read",
|
||||
cx,
|
||||
|this, cx| this.mark_selected_message_read(cx),
|
||||
)),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
use super::*;
|
||||
|
||||
pub(super) fn message_section_label(label: &'static str) -> impl IntoElement {
|
||||
div().text_xs().text_color(theme::text_muted()).child(label)
|
||||
}
|
||||
|
||||
pub(super) fn selected_message_card(message: Option<&domain::AgentMessage>) -> impl IntoElement {
|
||||
let subject = message
|
||||
.map(|item| item.subject.clone())
|
||||
.unwrap_or_else(|| "No message selected".to_string());
|
||||
let body = message.map(|item| item.body.clone()).unwrap_or_else(|| {
|
||||
"Choose a company channel, direct thread, or task discussion from the left rail."
|
||||
.to_string()
|
||||
});
|
||||
div()
|
||||
.p_5()
|
||||
.rounded_lg()
|
||||
.border_1()
|
||||
.border_color(theme::panel_border())
|
||||
.bg(theme::panel())
|
||||
.child(message_header(message, false))
|
||||
.child(
|
||||
div()
|
||||
.mt(px(16.0))
|
||||
.text_lg()
|
||||
.text_color(theme::text_primary())
|
||||
.child(subject),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.mt(px(12.0))
|
||||
.text_base()
|
||||
.text_color(theme::text_secondary())
|
||||
.child(body),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.mt(px(16.0))
|
||||
.text_sm()
|
||||
.text_color(theme::text_muted())
|
||||
.child("↩ Reply"),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn compact_message_card(message: &domain::AgentMessage) -> impl IntoElement {
|
||||
div()
|
||||
.mt(px(16.0))
|
||||
.p_5()
|
||||
.rounded_lg()
|
||||
.border_1()
|
||||
.border_color(theme::panel_border())
|
||||
.bg(theme::panel())
|
||||
.child(message_header(Some(message), true))
|
||||
.child(
|
||||
div()
|
||||
.mt(px(12.0))
|
||||
.text_base()
|
||||
.text_color(theme::text_primary())
|
||||
.child(message.subject.clone()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.mt(px(8.0))
|
||||
.text_sm()
|
||||
.text_color(theme::text_muted())
|
||||
.line_clamp(2)
|
||||
.child(message.body.clone()),
|
||||
)
|
||||
}
|
||||
|
||||
fn message_header(message: Option<&domain::AgentMessage>, urgent: bool) -> impl IntoElement {
|
||||
let sender = message
|
||||
.map(|item| trim_sender(item.from_agent_id.as_str()))
|
||||
.unwrap_or("AC");
|
||||
let timestamp = message
|
||||
.and_then(|item| item.created_at.get(11..16))
|
||||
.unwrap_or("--:--")
|
||||
.to_string();
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_3()
|
||||
.child(
|
||||
div()
|
||||
.w(px(44.0))
|
||||
.h(px(44.0))
|
||||
.rounded_full()
|
||||
.bg(theme::accent_orange_soft())
|
||||
.text_color(theme::accent_orange())
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(sender.to_string()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.gap_2()
|
||||
.items_center()
|
||||
.child(
|
||||
div().text_sm().text_color(theme::accent_orange()).child(
|
||||
message
|
||||
.map(|item| item.sender_kind.clone())
|
||||
.unwrap_or_else(|| "human".to_string()),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div().text_sm().text_color(theme::text_muted()).child(
|
||||
message
|
||||
.map(|item| item.channel.clone())
|
||||
.unwrap_or_else(|| "company".to_string()),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.px_2()
|
||||
.py_1()
|
||||
.rounded_full()
|
||||
.bg(theme::accent_orange_soft())
|
||||
.text_xs()
|
||||
.text_color(theme::accent_orange())
|
||||
.child("OPERATIONS"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(theme::text_muted())
|
||||
.child(timestamp),
|
||||
)
|
||||
.when(urgent, |container| {
|
||||
container.child(
|
||||
div()
|
||||
.px_2()
|
||||
.py_1()
|
||||
.rounded_full()
|
||||
.bg(theme::danger_bg())
|
||||
.text_xs()
|
||||
.text_color(theme::danger())
|
||||
.child("URGENT"),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn secondary_message(
|
||||
selected: Option<&domain::AgentMessage>,
|
||||
) -> Option<&domain::AgentMessage> {
|
||||
selected
|
||||
}
|
||||
|
||||
fn trim_sender(sender: &str) -> &str {
|
||||
sender.get(..2).unwrap_or(sender)
|
||||
}
|
||||
|
||||
pub(super) fn search_shell(label: &'static str) -> impl IntoElement {
|
||||
div()
|
||||
.w(px(240.0))
|
||||
.px_4()
|
||||
.py_3()
|
||||
.rounded_lg()
|
||||
.border_1()
|
||||
.border_color(theme::panel_border())
|
||||
.bg(theme::panel())
|
||||
.text_sm()
|
||||
.text_color(theme::text_subtle())
|
||||
.child(label)
|
||||
}
|
||||
|
||||
pub(super) fn filter_shell() -> impl IntoElement {
|
||||
div()
|
||||
.px_4()
|
||||
.py_3()
|
||||
.rounded_lg()
|
||||
.border_1()
|
||||
.border_color(theme::panel_border())
|
||||
.bg(theme::panel())
|
||||
.text_sm()
|
||||
.text_color(theme::text_secondary())
|
||||
.child("Filters")
|
||||
}
|
||||
|
||||
pub(super) fn composer_chip(label: &str) -> impl IntoElement {
|
||||
div()
|
||||
.px_4()
|
||||
.py_3()
|
||||
.rounded_lg()
|
||||
.border_1()
|
||||
.border_color(theme::panel_border())
|
||||
.bg(theme::panel())
|
||||
.text_sm()
|
||||
.text_color(theme::text_secondary())
|
||||
.child(label.to_string())
|
||||
}
|
||||
|
||||
pub(super) fn composer_subject(message: Option<&domain::AgentMessage>) -> impl IntoElement {
|
||||
div()
|
||||
.flex_1()
|
||||
.px_4()
|
||||
.py_3()
|
||||
.rounded_lg()
|
||||
.border_1()
|
||||
.border_color(theme::panel_border())
|
||||
.bg(theme::panel())
|
||||
.text_sm()
|
||||
.text_color(theme::text_subtle())
|
||||
.child(
|
||||
message
|
||||
.map(|item| item.subject.clone())
|
||||
.unwrap_or_else(|| "Subject (optional)".to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn selected_recipient(message: Option<&domain::AgentMessage>) -> String {
|
||||
message
|
||||
.and_then(|item| item.to_agent_id.clone())
|
||||
.unwrap_or_else(|| "CEO (CEO)".to_string())
|
||||
}
|
||||
@@ -130,7 +130,12 @@ fn eyebrow(label: &'static str) -> impl IntoElement {
|
||||
.gap_3()
|
||||
.items_center()
|
||||
.child(div().w(px(48.0)).h(px(1.0)).bg(theme::text_subtle()))
|
||||
.child(div().text_xs().text_color(theme::text_subtle()).child(label))
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(theme::text_subtle())
|
||||
.child(label),
|
||||
)
|
||||
}
|
||||
|
||||
fn onboarding_steps() -> impl IntoElement {
|
||||
@@ -168,7 +173,12 @@ fn readiness_panel() -> impl IntoElement {
|
||||
div()
|
||||
.w(px(520.0))
|
||||
.gap_3()
|
||||
.child(div().text_xs().text_color(theme::text_muted()).child("CONNECTOR READINESS"))
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(theme::text_muted())
|
||||
.child("CONNECTOR READINESS"),
|
||||
)
|
||||
.child(readiness_row("Claude Code", "ready", true))
|
||||
.child(readiness_row("Codex", "ready", true))
|
||||
.child(readiness_row("Gemini CLI", "auth required", false))
|
||||
@@ -186,18 +196,17 @@ fn readiness_row(label: &'static str, state: &'static str, ready: bool) -> impl
|
||||
.flex()
|
||||
.gap_3()
|
||||
.items_center()
|
||||
.child(div().w(px(16.0)).h(px(16.0)).rounded_full().bg(if ready {
|
||||
theme::accent_teal()
|
||||
} else {
|
||||
theme::accent_orange()
|
||||
}))
|
||||
.child(
|
||||
div()
|
||||
.w(px(16.0))
|
||||
.h(px(16.0))
|
||||
.rounded_full()
|
||||
.bg(if ready {
|
||||
theme::accent_teal()
|
||||
} else {
|
||||
theme::accent_orange()
|
||||
}),
|
||||
.text_sm()
|
||||
.text_color(theme::text_primary())
|
||||
.child(label),
|
||||
)
|
||||
.child(div().text_sm().text_color(theme::text_primary()).child(label))
|
||||
.child(div().text_sm().text_color(theme::text_muted()).child(state))
|
||||
}
|
||||
|
||||
@@ -225,7 +234,12 @@ fn launch_tile(label: &'static str, value: &'static str) -> impl IntoElement {
|
||||
.rounded_lg()
|
||||
.bg(theme::panel_muted())
|
||||
.child(div().text_xs().text_color(theme::text_muted()).child(label))
|
||||
.child(div().text_sm().text_color(theme::text_primary()).child(value))
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(theme::text_primary())
|
||||
.child(value),
|
||||
)
|
||||
}
|
||||
|
||||
fn rgb_hex(hex: u32) -> gpui::Hsla {
|
||||
|
||||
@@ -43,9 +43,15 @@ impl AppRoot {
|
||||
.child(format!("{} agents active", self.snapshot.agents.len()))
|
||||
.child(div().child("•"))
|
||||
.child(metadata_dot(theme::badge_dark()))
|
||||
.child(format!("{} tasks in progress", self.snapshot.active_tasks()))
|
||||
.child(format!(
|
||||
"{} tasks in progress",
|
||||
self.snapshot.active_tasks()
|
||||
))
|
||||
.child(div().child("•"))
|
||||
.child(format!("{} completed", completed_task_count(&self.snapshot))),
|
||||
.child(format!(
|
||||
"{} completed",
|
||||
completed_task_count(&self.snapshot)
|
||||
)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
@@ -149,7 +155,11 @@ impl AppRoot {
|
||||
div()
|
||||
.text_base()
|
||||
.text_color(theme::text_primary())
|
||||
.child(format!("{} {}", item.actor, item.action.replace('_', " "))),
|
||||
.child(format!(
|
||||
"{} {}",
|
||||
item.actor,
|
||||
item.action.replace('_', " ")
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
@@ -192,12 +202,7 @@ fn section_header(label: &'static str, action: Option<&'static str>) -> impl Int
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(theme::text_muted())
|
||||
.child(label),
|
||||
)
|
||||
.child(div().text_xs().text_color(theme::text_muted()).child(label))
|
||||
.when_some(action, |container, action_label| {
|
||||
container.child(
|
||||
div()
|
||||
@@ -223,14 +228,13 @@ fn metric_panel(
|
||||
.rounded_lg()
|
||||
.border_color(theme::panel_border())
|
||||
.bg(theme::panel())
|
||||
.child(
|
||||
div()
|
||||
.w(px(56.0))
|
||||
.h(px(56.0))
|
||||
.rounded_lg()
|
||||
.bg(tile_bg),
|
||||
)
|
||||
.child(div().w(px(56.0)).h(px(56.0)).rounded_lg().bg(tile_bg))
|
||||
.child(div().text_xs().text_color(theme::text_muted()).child(label))
|
||||
.child(div().text_3xl().text_color(value_color).child(value))
|
||||
.child(div().text_sm().text_color(theme::text_muted()).child(detail))
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(theme::text_muted())
|
||||
.child(detail),
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use gpui::Window;
|
||||
|
||||
impl AppRoot {
|
||||
pub(super) fn render_sidebar(&self, cx: &mut Context<Self>) -> impl IntoElement + '_ {
|
||||
@@ -7,7 +8,10 @@ impl AppRoot {
|
||||
(SectionId::Runs, Some(self.snapshot.active_runs())),
|
||||
(SectionId::Inbox, Some(self.snapshot.active_tasks())),
|
||||
(SectionId::Messages, Some(self.snapshot.unread_messages())),
|
||||
(SectionId::Approvals, Some(self.snapshot.pending_approvals())),
|
||||
(
|
||||
SectionId::Approvals,
|
||||
Some(self.snapshot.pending_approvals()),
|
||||
),
|
||||
(SectionId::Documents, None),
|
||||
(SectionId::Workspaces, None),
|
||||
];
|
||||
@@ -57,15 +61,12 @@ impl AppRoot {
|
||||
.border_color(theme::panel_border())
|
||||
.bg(theme::panel())
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(theme::text_primary())
|
||||
.child(
|
||||
self.snapshot
|
||||
.current_company()
|
||||
.map(|company| company.name.clone())
|
||||
.unwrap_or_else(|| "AgentCompany".to_string()),
|
||||
),
|
||||
div().text_sm().text_color(theme::text_primary()).child(
|
||||
self.snapshot
|
||||
.current_company()
|
||||
.map(|company| company.name.clone())
|
||||
.unwrap_or_else(|| "AgentCompany".to_string()),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
@@ -91,7 +92,8 @@ impl AppRoot {
|
||||
),
|
||||
)
|
||||
.children(self.snapshot.companies.iter().map(|company| {
|
||||
let active = self.snapshot.current_company_id.as_deref() == Some(company.id.as_str());
|
||||
let active =
|
||||
self.snapshot.current_company_id.as_deref() == Some(company.id.as_str());
|
||||
let company_id = company.id.clone();
|
||||
div()
|
||||
.id(SharedString::from(format!("company-{}", company.id)))
|
||||
@@ -189,10 +191,15 @@ impl AppRoot {
|
||||
}))
|
||||
.child(div().flex_1())
|
||||
.child(div().h(px(1.0)).bg(theme::sidebar_divider()))
|
||||
.child(sidebar_footer_row("Settings", self.state.section == SectionId::Settings, cx, |this, cx| {
|
||||
this.state.section = SectionId::Settings;
|
||||
cx.notify();
|
||||
}))
|
||||
.child(sidebar_footer_row(
|
||||
"Settings",
|
||||
self.state.section == SectionId::Settings,
|
||||
cx,
|
||||
|this, cx| {
|
||||
this.state.section = SectionId::Settings;
|
||||
cx.notify();
|
||||
},
|
||||
))
|
||||
.child(
|
||||
div()
|
||||
.px_4()
|
||||
@@ -223,6 +230,7 @@ where
|
||||
V: 'static,
|
||||
{
|
||||
div()
|
||||
.id(SharedString::from(format!("sidebar-footer-{label}")))
|
||||
.px_4()
|
||||
.py_3()
|
||||
.rounded_lg()
|
||||
@@ -238,7 +246,9 @@ where
|
||||
theme::sidebar_text()
|
||||
})
|
||||
.hover(|style| style.bg(theme::sidebar_hover()))
|
||||
.on_click(cx.listener(move |this, _, _, cx| on_click(this, cx)))
|
||||
.on_click(
|
||||
cx.listener(move |this, _: &gpui::ClickEvent, _: &mut Window, cx| on_click(this, cx)),
|
||||
)
|
||||
.child(label)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,6 @@ pub fn window_bg() -> Hsla {
|
||||
rgb(0xf7f8fb).into()
|
||||
}
|
||||
|
||||
pub fn canvas() -> Hsla {
|
||||
rgb(0xf7f8fb).into()
|
||||
}
|
||||
|
||||
pub fn shell_header() -> Hsla {
|
||||
rgb(0xffffff).into()
|
||||
}
|
||||
@@ -108,18 +104,10 @@ pub fn sidebar_bg() -> Hsla {
|
||||
rgb(0xffffff).into()
|
||||
}
|
||||
|
||||
pub fn sidebar_surface() -> Hsla {
|
||||
rgb(0xffffff).into()
|
||||
}
|
||||
|
||||
pub fn sidebar_hover() -> Hsla {
|
||||
rgb(0xf4f6fa).into()
|
||||
}
|
||||
|
||||
pub fn sidebar_surface_hover() -> Hsla {
|
||||
rgb(0xf4f6fa).into()
|
||||
}
|
||||
|
||||
pub fn sidebar_divider() -> Hsla {
|
||||
rgb(0xe7eaf0).into()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user