Add profiles settings page

This commit is contained in:
2026-05-08 00:09:03 -04:00
parent 57b3beef57
commit ec0ba6b758
5 changed files with 209 additions and 1 deletions
@@ -2,6 +2,7 @@ mod download_actions;
mod download_labels; mod download_labels;
mod downloads; mod downloads;
mod plugins; mod plugins;
mod profiles;
use ely_browser_core::BrowserSnapshot; use ely_browser_core::BrowserSnapshot;
use ely_design_system::{colors, spacing}; use ely_design_system::{colors, spacing};
@@ -26,6 +27,7 @@ impl ElyShell {
"ely://history" => self.render_history_page(snapshot, cx), "ely://history" => self.render_history_page(snapshot, cx),
"ely://archive" => self.render_archive_page(snapshot, cx), "ely://archive" => self.render_archive_page(snapshot, cx),
"ely://settings/plugins" => self.render_plugins_page(snapshot, cx), "ely://settings/plugins" => self.render_plugins_page(snapshot, cx),
"ely://settings/profiles" => self.render_profiles_page(snapshot, cx),
_ => render_default_page(tab), _ => render_default_page(tab),
} }
} }
@@ -0,0 +1,186 @@
use ely_browser_core::BrowserSnapshot;
use ely_design_system::colors;
use ely_domain::{DownloadDestination, DownloadPolicy, Profile, ProfileId, ProfileKind};
use gpui::{
AnyElement, Context, InteractiveElement, IntoElement, ParentElement, SharedString, Styled, div,
px, rgb,
};
use gpui_component::{
IconName, Sizable, StyledExt,
button::{Button, ButtonVariants},
scroll::ScrollableElement,
};
use super::{ElyShell, render_canvas_surface};
impl ElyShell {
pub(super) fn render_profiles_page(
&mut self,
snapshot: &BrowserSnapshot,
cx: &mut Context<Self>,
) -> AnyElement {
render_canvas_surface(
div()
.size_full()
.p_8()
.flex()
.flex_col()
.gap_5()
.child(render_profiles_header(snapshot))
.child(render_profile_list(snapshot, cx)),
)
}
}
fn render_profiles_header(snapshot: &BrowserSnapshot) -> AnyElement {
div()
.flex()
.items_end()
.justify_between()
.child(
div()
.flex()
.flex_col()
.gap_2()
.child(div().text_size(px(26.0)).text_color(rgb(colors::INK)).child("Profiles"))
.child(
div()
.text_sm()
.text_color(rgb(colors::MUTED))
.child(format!("Active Space: {}", snapshot.active_space_name)),
),
)
.child(
div()
.text_xs()
.text_color(rgb(colors::MUTED))
.child(format!("{} profiles", snapshot.profiles.len())),
)
.into_any_element()
}
fn render_profile_list(snapshot: &BrowserSnapshot, cx: &mut Context<ElyShell>) -> AnyElement {
div()
.flex_1()
.min_h_0()
.flex()
.flex_col()
.overflow_y_scrollbar()
.border_t_1()
.border_color(rgb(colors::HAIRLINE))
.children(snapshot.profiles.iter().enumerate().map(|(index, profile)| {
render_profile_row(index, profile, profile.id() == &snapshot.active_profile_id, cx)
}))
.into_any_element()
}
fn render_profile_row(
index: usize,
profile: &Profile,
active: bool,
cx: &mut Context<ElyShell>,
) -> AnyElement {
let profile_id = profile.id().clone();
div()
.id(SharedString::from(format!("profile-{}", profile.id().as_str())))
.py_3()
.border_b_1()
.border_color(rgb(colors::HAIRLINE))
.flex()
.items_center()
.justify_between()
.gap_4()
.child(
div()
.min_w_0()
.flex()
.items_center()
.gap_3()
.child(profile_color_swatch(profile.color_hex()))
.child(
div()
.min_w_0()
.flex()
.flex_col()
.gap_1()
.child(
div()
.text_sm()
.font_semibold()
.truncate()
.text_color(rgb(colors::INK))
.child(profile.name().to_string()),
)
.child(
div()
.text_xs()
.truncate()
.text_color(rgb(colors::MUTED))
.child(profile_detail_label(profile)),
),
),
)
.child(render_profile_action(index, profile_id, active, cx))
.into_any_element()
}
fn profile_color_swatch(color_hex: u32) -> AnyElement {
div()
.w(px(14.0))
.h(px(14.0))
.rounded_full()
.border_1()
.border_color(rgb(colors::HAIRLINE_STRONG))
.bg(rgb(color_hex))
.into_any_element()
}
fn render_profile_action(
index: usize,
profile_id: ProfileId,
active: bool,
cx: &mut Context<ElyShell>,
) -> AnyElement {
if active {
return div()
.text_xs()
.font_semibold()
.text_color(rgb(colors::SUCCESS))
.child("Active")
.into_any_element();
}
Button::new(("switch-profile", index))
.small()
.primary()
.icon(IconName::Check)
.label("Switch")
.tooltip("Switch Profile")
.on_click(cx.listener(move |shell, _, window, cx| {
shell.select_profile(&profile_id, window, cx);
}))
.into_any_element()
}
fn profile_detail_label(profile: &Profile) -> String {
format!(
"{} - {}",
profile_kind_label(profile.kind()),
download_policy_label(profile.download_policy())
)
}
fn profile_kind_label(kind: &ProfileKind) -> &'static str {
match kind {
ProfileKind::Standard => "Standard",
ProfileKind::Private => "Private",
}
}
fn download_policy_label(policy: &DownloadPolicy) -> String {
match policy.destination() {
DownloadDestination::AskEveryTime => "Ask every time".to_string(),
DownloadDestination::FixedDirectory(path) => path.display().to_string(),
}
}
+15 -1
View File
@@ -4,7 +4,7 @@ mod plugins;
mod render; mod render;
use ely_browser_core::{BrowserCore, InitialBrowserConfig}; use ely_browser_core::{BrowserCore, InitialBrowserConfig};
use ely_domain::{CommandIntent, SpaceId, TabId, UrlText}; use ely_domain::{CommandIntent, ProfileId, SpaceId, TabId, UrlText};
use gpui::{App, AppContext, Context, Entity, FocusHandle, Focusable, Subscription, Window}; use gpui::{App, AppContext, Context, Entity, FocusHandle, Focusable, Subscription, Window};
use gpui_component::input::{InputEvent, InputState, SelectAll}; use gpui_component::input::{InputEvent, InputState, SelectAll};
@@ -166,6 +166,20 @@ impl ElyShell {
} }
} }
fn select_profile(
&mut self,
profile_id: &ProfileId,
window: &mut Window,
cx: &mut Context<Self>,
) {
if let ShellState::Ready(core) = &mut self.state
&& core.select_profile(profile_id).is_ok()
{
self.sync_address_input(window, cx);
cx.notify();
}
}
fn select_next_tab(&mut self, window: &mut Window, cx: &mut Context<Self>) { fn select_next_tab(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if let ShellState::Ready(core) = &mut self.state if let ShellState::Ready(core) = &mut self.state
&& core.select_next_tab().is_ok() && core.select_next_tab().is_ok()
+4
View File
@@ -46,8 +46,10 @@ pub struct BrowserSnapshot {
pub installed_plugins: Vec<InstalledPlugin>, pub installed_plugins: Vec<InstalledPlugin>,
pub plugin_audit_events: Vec<PluginAuditEvent>, pub plugin_audit_events: Vec<PluginAuditEvent>,
pub spaces: Vec<Space>, pub spaces: Vec<Space>,
pub profiles: Vec<Profile>,
pub active_tab_id: TabId, pub active_tab_id: TabId,
pub active_space_id: SpaceId, pub active_space_id: SpaceId,
pub active_profile_id: ProfileId,
pub active_space_name: String, pub active_space_name: String,
pub active_profile_name: String, pub active_profile_name: String,
pub active_download_policy: DownloadPolicy, pub active_download_policy: DownloadPolicy,
@@ -198,9 +200,11 @@ impl BrowserCore {
installed_plugins: self.installed_plugins.clone(), installed_plugins: self.installed_plugins.clone(),
plugin_audit_events: self.plugin_audit_events.clone(), plugin_audit_events: self.plugin_audit_events.clone(),
spaces: self.spaces.clone(), spaces: self.spaces.clone(),
profiles: self.profiles.clone(),
tabs: self.visible_tabs(), tabs: self.visible_tabs(),
active_tab_id: self.active_tab_id.clone(), active_tab_id: self.active_tab_id.clone(),
active_space_id: self.active_space_id.clone(), active_space_id: self.active_space_id.clone(),
active_profile_id: self.active_profile_id.clone(),
active_space_name: active_space.name().to_string(), active_space_name: active_space.name().to_string(),
active_profile_name: active_profile.name().to_string(), active_profile_name: active_profile.name().to_string(),
active_download_policy: active_profile.download_policy().clone(), active_download_policy: active_profile.download_policy().clone(),
@@ -168,7 +168,9 @@ fn new_profile_command_creates_and_selects_named_profile() -> Result<(), Box<dyn
let active_tab = core.active_tab()?; let active_tab = core.active_tab()?;
assert_eq!(intent, Some(CommandIntent::Command("new-profile Personal".to_string()))); assert_eq!(intent, Some(CommandIntent::Command("new-profile Personal".to_string())));
assert_eq!(snapshot.profiles.len(), 2);
assert_eq!(snapshot.active_profile_name, "Personal"); assert_eq!(snapshot.active_profile_name, "Personal");
assert_eq!(snapshot.active_profile_id, active_tab.profile_id().clone());
assert_ne!(active_tab.profile_id(), &default_profile_id); assert_ne!(active_tab.profile_id(), &default_profile_id);
assert_eq!(active_tab.url().as_str(), "ely://new-tab"); assert_eq!(active_tab.url().as_str(), "ely://new-tab");
assert_eq!(snapshot.command_query, ""); assert_eq!(snapshot.command_query, "");