Align settings and workspaces native surfaces

This commit is contained in:
ZacharyZhang-NY committed 2026-04-19 14:16:01 -04:00
1 parent e1b7f52444
commit 0a5be5ea97
3 files changed
+245 -94

No files matched your search

@@ -220,37 +220,6 @@ impl AppRoot {
Err(error) => self.set_status(error.to_string(), cx), Err(error) => self.set_status(error.to_string(), cx),
} }
} }
fn metric_card(
&self,
label: impl Into<String>,
value: String,
detail: impl Into<String>,
) -> impl IntoElement + '_ {
let label = label.into();
let detail = detail.into();
div()
.flex_1()
.p_4()
.gap_2()
.border_1()
.rounded_md()
.border_color(theme::panel_border())
.bg(theme::panel())
.child(div().text_sm().text_color(theme::text_muted()).child(label))
.child(
div()
.text_3xl()
.text_color(theme::text_primary())
.child(value),
)
.child(
div()
.text_sm()
.text_color(theme::text_muted())
.child(detail),
)
}
} }
impl Render for AppRoot { impl Render for AppRoot {
@@ -6,29 +6,113 @@ impl AppRoot {
let execution_mode = current let execution_mode = current
.map(|item| item.execution_mode.clone()) .map(|item| item.execution_mode.clone())
.unwrap_or_else(|| "ai_ceo".to_string()); .unwrap_or_else(|| "ai_ceo".to_string());
div() div()
.flex() .flex()
.flex_col() .gap_6()
.gap_4() .child(
.child(self.metric_card( div()
"Profile path", .w(px(220.0))
self.snapshot.profile_path.clone(), .p_4()
"Electron-compatible local profile", .gap_2()
)) .rounded_lg()
.child(self.metric_card( .border_1()
"Theme", .border_color(theme::panel_border())
self.snapshot.theme.clone(), .bg(theme::panel())
"Stored in the shared settings table", .child(settings_nav_row("General", true))
)) .child(settings_nav_row("Execution mode", false))
.child(self.metric_card( .child(settings_nav_row("Providers", false))
"Locale", .child(settings_nav_row("Shortcuts", false))
self.snapshot.locale.clone(), .child(settings_nav_row("Data", false))
"Read from settings or the OS locale", .child(settings_nav_row("Secrets", false)),
)) )
.child(self.metric_card( .child(
"Connectors", div()
self.snapshot.connectors.len().to_string(), .flex_1()
execution_mode, .gap_6()
)) .child(settings_section(
"General",
vec![
settings_row("Profile path", self.snapshot.profile_path.clone()),
settings_row("Theme", self.snapshot.theme.clone()),
settings_row("Locale", self.snapshot.locale.clone()),
],
))
.child(settings_section(
"Execution mode",
vec![settings_row("Company mode", execution_mode)],
))
.child(settings_section(
"Providers",
vec![settings_row(
"Configured connectors",
self.snapshot.connectors.len().to_string(),
)],
))
.child(settings_section(
"Data",
vec![
settings_row("Stored profile", "Local SQLite".to_string()),
settings_row("Current company", current_company_name(current)),
],
)),
)
} }
} }
fn settings_nav_row(label: &'static str, active: bool) -> impl IntoElement {
div()
.px_4()
.py_3()
.rounded_lg()
.bg(if active {
theme::selection_fill()
} else {
theme::panel()
})
.text_color(if active {
theme::sidebar_text_active()
} else {
theme::text_secondary()
})
.child(label)
}
fn settings_section(label: &'static str, rows: Vec<gpui::Div>) -> impl IntoElement {
div()
.p_5()
.rounded_lg()
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel())
.child(
div()
.text_lg()
.text_color(theme::text_primary())
.child(label),
)
.child(div().mt(px(16.0)).children(rows))
}
fn settings_row(label: &'static str, value: String) -> gpui::Div {
div()
.flex()
.justify_between()
.items_center()
.py_3()
.border_b_1()
.border_color(theme::panel_border())
.child(div().text_sm().text_color(theme::text_muted()).child(label))
.child(
div()
.text_sm()
.text_color(theme::text_primary())
.child(value),
)
}
fn current_company_name(company: Option<&domain::Company>) -> String {
company
.map(|item| item.name.clone())
.unwrap_or_else(|| "No company".to_string())
}
@@ -5,50 +5,148 @@ impl AppRoot {
div() div()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_6()
.children(self.snapshot.workspaces.iter().map(|workspace| { .child(
let active =
self.state.selected_workspace_id.as_deref() == Some(workspace.id.as_str());
let workspace_id = workspace.id.clone();
div() div()
.id(SharedString::from(format!("workspace-{}", workspace.id))) .flex()
.p_4() .justify_between()
.gap_2() .items_start()
.border_1()
.rounded_md()
.border_color(theme::panel_border())
.bg(if active {
theme::panel_inset()
} else {
theme::panel()
})
.cursor_pointer()
.hover(|style| style.bg(theme::panel_hover()))
.on_click(cx.listener(move |this, _, _, cx| {
this.state.selected_workspace_id = Some(workspace_id.clone());
cx.notify();
}))
.child(div().text_lg().child(workspace.name.clone()))
.child( .child(
div() div()
.text_sm() .child(
.text_color(theme::text_muted()) div()
.child(workspace.local_path.clone()), .text_2xl()
) .text_color(theme::text_primary())
.child( .child("Workspaces"),
div() )
.text_sm() .child(
.text_color(theme::text_muted()) div()
.child(format!( .mt(px(6.0))
"{} · {}", .text_sm()
workspace.state, .text_color(theme::text_muted())
if workspace.is_primary { .child(format!("{} linked workspaces", self.snapshot.workspaces.len())),
"primary" ),
),
)
.child(div().h(px(1.0)).bg(theme::panel_border()))
.when(self.snapshot.workspaces.is_empty(), |container| {
container.child(
div()
.min_h(px(260.0))
.flex()
.items_center()
.justify_center()
.rounded_lg()
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel())
.child(
div()
.w(px(420.0))
.text_center()
.child(
div()
.text_xl()
.text_color(theme::text_primary())
.child("No workspaces linked"),
)
.child(
div()
.mt(px(10.0))
.text_base()
.text_color(theme::text_muted())
.child("The active company will show its local workspaces here."),
),
),
)
})
.when(!self.snapshot.workspaces.is_empty(), |container| {
container.child(
div()
.children(self.snapshot.workspaces.iter().map(|workspace| {
let active = self.state.selected_workspace_id.as_deref()
== Some(workspace.id.as_str());
let workspace_id = workspace.id.clone();
div()
.id(SharedString::from(format!("workspace-{}", workspace.id)))
.flex()
.justify_between()
.items_center()
.px_6()
.py_5()
.border_b_1()
.border_color(theme::panel_border())
.cursor_pointer()
.bg(if active {
theme::selection_fill()
} else { } else {
"linked" theme::window_bg()
} })
)), .hover(|style| style.bg(theme::panel_hover()))
) .on_click(cx.listener(move |this, _, _, cx| {
})) this.state.selected_workspace_id = Some(workspace_id.clone());
cx.notify();
}))
.child(
div()
.flex_1()
.child(
div()
.text_base()
.text_color(theme::text_primary())
.child(workspace.name.clone()),
)
.child(
div()
.mt(px(8.0))
.text_sm()
.text_color(theme::text_muted())
.child(workspace.local_path.clone()),
)
.child(
div()
.mt(px(8.0))
.text_xs()
.text_color(theme::text_subtle())
.child(format!(
"{} · {}",
workspace.state,
workspace_kind(workspace.is_primary)
)),
),
)
.child(workspace_state_badge(
workspace.state.as_str(),
workspace.is_primary,
))
})),
)
})
} }
} }
fn workspace_kind(is_primary: bool) -> &'static str {
if is_primary { "primary" } else { "linked" }
}
fn workspace_state_badge(state: &str, is_primary: bool) -> impl IntoElement {
div()
.flex()
.items_center()
.gap_2()
.child(div().w(px(8.0)).h(px(8.0)).rounded_full().bg(match state {
"ready" => theme::accent_teal(),
"blocked" | "missing" | "error" => theme::danger(),
_ => theme::accent_blue(),
}))
.child(
div()
.text_xs()
.text_color(theme::text_secondary())
.child(if is_primary {
format!("{} · PRIMARY", state.to_uppercase())
} else {
state.to_uppercase()
}),
)
}