Add spaces settings page
This commit is contained in:
@@ -12,6 +12,7 @@ mod settings;
|
|||||||
mod shortcuts;
|
mod shortcuts;
|
||||||
mod sidebar_tabs;
|
mod sidebar_tabs;
|
||||||
mod site_settings;
|
mod site_settings;
|
||||||
|
mod spaces;
|
||||||
mod sync;
|
mod sync;
|
||||||
mod task_manager;
|
mod task_manager;
|
||||||
|
|
||||||
@@ -50,6 +51,7 @@ impl ElyShell {
|
|||||||
"ely://about" => self.render_about_page(snapshot),
|
"ely://about" => self.render_about_page(snapshot),
|
||||||
"ely://settings" => self.render_settings_page(snapshot, cx),
|
"ely://settings" => self.render_settings_page(snapshot, cx),
|
||||||
"ely://settings/sidebar-tabs" => self.render_sidebar_tabs_page(snapshot, cx),
|
"ely://settings/sidebar-tabs" => self.render_sidebar_tabs_page(snapshot, cx),
|
||||||
|
"ely://settings/spaces" => self.render_spaces_page(snapshot, cx),
|
||||||
"ely://settings/shortcuts" => self.render_shortcuts_page(snapshot),
|
"ely://settings/shortcuts" => self.render_shortcuts_page(snapshot),
|
||||||
"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),
|
"ely://settings/profiles" => self.render_profiles_page(snapshot, cx),
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ const SETTINGS_ROUTES: &[SettingsRoute] = &[
|
|||||||
detail: "Vertical tabs, pinned area, and auto archive policy.",
|
detail: "Vertical tabs, pinned area, and auto archive policy.",
|
||||||
route: "ely://settings/sidebar-tabs",
|
route: "ely://settings/sidebar-tabs",
|
||||||
},
|
},
|
||||||
|
SettingsRoute {
|
||||||
|
icon: IconName::GalleryVerticalEnd,
|
||||||
|
title: "Spaces",
|
||||||
|
detail: "Space identity, accent color, and active context.",
|
||||||
|
route: "ely://settings/spaces",
|
||||||
|
},
|
||||||
SettingsRoute {
|
SettingsRoute {
|
||||||
icon: IconName::CircleUser,
|
icon: IconName::CircleUser,
|
||||||
title: "Profiles",
|
title: "Profiles",
|
||||||
|
|||||||
@@ -0,0 +1,251 @@
|
|||||||
|
use ely_browser_core::BrowserSnapshot;
|
||||||
|
use ely_design_system::colors;
|
||||||
|
use ely_domain::{ArchivePolicy, Space, SpaceId};
|
||||||
|
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_spaces_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_spaces_header(snapshot))
|
||||||
|
.child(render_active_space_summary(snapshot))
|
||||||
|
.child(render_spaces_list(snapshot, cx)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_spaces_header(snapshot: &BrowserSnapshot) -> AnyElement {
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_end()
|
||||||
|
.justify_between()
|
||||||
|
.gap_4()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.min_w_0()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_2()
|
||||||
|
.child(div().text_size(px(26.0)).text_color(rgb(colors::INK)).child("Spaces"))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_sm()
|
||||||
|
.truncate()
|
||||||
|
.text_color(rgb(colors::MUTED))
|
||||||
|
.child(format!("Profile: {}", snapshot.active_profile_name)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_2()
|
||||||
|
.text_xs()
|
||||||
|
.font_semibold()
|
||||||
|
.text_color(rgb(colors::MUTED))
|
||||||
|
.child(IconName::GalleryVerticalEnd)
|
||||||
|
.child(format!("{} spaces", snapshot.spaces.len())),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_active_space_summary(snapshot: &BrowserSnapshot) -> AnyElement {
|
||||||
|
let Some(active_space) =
|
||||||
|
snapshot.spaces.iter().find(|space| space.id() == &snapshot.active_space_id)
|
||||||
|
else {
|
||||||
|
return div()
|
||||||
|
.rounded_md()
|
||||||
|
.border_1()
|
||||||
|
.border_color(rgb(colors::ERROR))
|
||||||
|
.px_4()
|
||||||
|
.py_3()
|
||||||
|
.text_sm()
|
||||||
|
.text_color(rgb(colors::ERROR))
|
||||||
|
.child("Active Space is unavailable.")
|
||||||
|
.into_any_element();
|
||||||
|
};
|
||||||
|
|
||||||
|
div()
|
||||||
|
.rounded_md()
|
||||||
|
.border_1()
|
||||||
|
.border_color(rgb(colors::HAIRLINE))
|
||||||
|
.bg(rgb(colors::CANVAS_SOFT))
|
||||||
|
.px_4()
|
||||||
|
.py_3()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.justify_between()
|
||||||
|
.gap_4()
|
||||||
|
.child(
|
||||||
|
div().min_w_0().flex().items_center().gap_3().child(space_avatar(active_space)).child(
|
||||||
|
div()
|
||||||
|
.min_w_0()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_sm()
|
||||||
|
.font_semibold()
|
||||||
|
.text_color(rgb(colors::INK))
|
||||||
|
.child(active_space.name().to_string()),
|
||||||
|
)
|
||||||
|
.child(div().text_xs().truncate().text_color(rgb(colors::MUTED)).child(
|
||||||
|
format!(
|
||||||
|
"{} - {}",
|
||||||
|
accent_label(active_space.accent_hex()),
|
||||||
|
archive_policy_label(active_space.archive_policy())
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(div().text_xs().font_semibold().text_color(rgb(colors::SUCCESS)).child("Active"))
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_spaces_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.spaces.iter().enumerate().map(|(index, space)| {
|
||||||
|
render_space_row(index, space, space.id() == &snapshot.active_space_id, cx)
|
||||||
|
}))
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_space_row(
|
||||||
|
index: usize,
|
||||||
|
space: &Space,
|
||||||
|
active: bool,
|
||||||
|
cx: &mut Context<ElyShell>,
|
||||||
|
) -> AnyElement {
|
||||||
|
let space_id = space.id().clone();
|
||||||
|
|
||||||
|
div()
|
||||||
|
.id(SharedString::from(format!("settings-space-{}", space.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(space_avatar(space)).child(
|
||||||
|
div()
|
||||||
|
.min_w_0()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_sm()
|
||||||
|
.font_semibold()
|
||||||
|
.truncate()
|
||||||
|
.text_color(rgb(colors::INK))
|
||||||
|
.child(space.name().to_string()),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_xs()
|
||||||
|
.truncate()
|
||||||
|
.text_color(rgb(colors::MUTED))
|
||||||
|
.child(space_detail_label(space)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(render_space_action(index, space_id, active, cx))
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_space_action(
|
||||||
|
index: usize,
|
||||||
|
space_id: SpaceId,
|
||||||
|
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-space", index))
|
||||||
|
.small()
|
||||||
|
.primary()
|
||||||
|
.icon(IconName::Check)
|
||||||
|
.label("Switch")
|
||||||
|
.tooltip("Switch Space")
|
||||||
|
.on_click(cx.listener(move |shell, _, window, cx| {
|
||||||
|
shell.select_space(&space_id, window, cx);
|
||||||
|
}))
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn space_avatar(space: &Space) -> AnyElement {
|
||||||
|
div()
|
||||||
|
.w(px(28.0))
|
||||||
|
.h(px(28.0))
|
||||||
|
.rounded_md()
|
||||||
|
.border_1()
|
||||||
|
.border_color(rgb(colors::HAIRLINE_STRONG))
|
||||||
|
.bg(rgb(space.accent_hex()))
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.justify_center()
|
||||||
|
.text_xs()
|
||||||
|
.font_semibold()
|
||||||
|
.text_color(rgb(colors::CANVAS))
|
||||||
|
.child(space.icon().to_string())
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn space_detail_label(space: &Space) -> String {
|
||||||
|
format!(
|
||||||
|
"{} - {}",
|
||||||
|
accent_label(space.accent_hex()),
|
||||||
|
archive_policy_label(space.archive_policy())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accent_label(accent_hex: u32) -> String {
|
||||||
|
format!("#{accent_hex:06X}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn archive_policy_label(policy: &ArchivePolicy) -> &'static str {
|
||||||
|
match policy {
|
||||||
|
ArchivePolicy::Manual => "Manual archive",
|
||||||
|
ArchivePolicy::IdleDays(0) => "Archive today",
|
||||||
|
ArchivePolicy::IdleDays(1) => "Archive after 1 day",
|
||||||
|
ArchivePolicy::IdleDays(7) => "Archive after 7 days",
|
||||||
|
ArchivePolicy::IdleDays(30) => "Archive after 30 days",
|
||||||
|
ArchivePolicy::IdleDays(_) => "Custom archive policy",
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ fn internal_page_title(url: &str) -> Option<&'static str> {
|
|||||||
"ely://about" => Some("About ELY Browser"),
|
"ely://about" => Some("About ELY Browser"),
|
||||||
"ely://settings" => Some("Settings"),
|
"ely://settings" => Some("Settings"),
|
||||||
"ely://settings/sidebar-tabs" => Some("Sidebar & Tabs Settings"),
|
"ely://settings/sidebar-tabs" => Some("Sidebar & Tabs Settings"),
|
||||||
|
"ely://settings/spaces" => Some("Space Settings"),
|
||||||
"ely://settings/shortcuts" => Some("Shortcut Settings"),
|
"ely://settings/shortcuts" => Some("Shortcut Settings"),
|
||||||
"ely://settings/plugins" => Some("Plugin Settings"),
|
"ely://settings/plugins" => Some("Plugin Settings"),
|
||||||
"ely://settings/profiles" => Some("Profile Settings"),
|
"ely://settings/profiles" => Some("Profile Settings"),
|
||||||
@@ -162,6 +163,7 @@ fn settings_page_route(query: &str) -> Option<&'static str> {
|
|||||||
"sidebar" | "tabs" | "sidebar tabs" | "sidebar & tabs" => {
|
"sidebar" | "tabs" | "sidebar tabs" | "sidebar & tabs" => {
|
||||||
Some("ely://settings/sidebar-tabs")
|
Some("ely://settings/sidebar-tabs")
|
||||||
}
|
}
|
||||||
|
"space" | "spaces" | "space settings" | "spaces settings" => Some("ely://settings/spaces"),
|
||||||
"shortcut" | "shortcuts" | "keyboard" | "keyboard shortcuts" => {
|
"shortcut" | "shortcuts" | "keyboard" | "keyboard shortcuts" => {
|
||||||
Some("ely://settings/shortcuts")
|
Some("ely://settings/shortcuts")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,3 +44,24 @@ fn settings_scoped_search_opens_shortcuts_page() -> Result<(), Box<dyn Error>> {
|
|||||||
assert_eq!(core.snapshot()?.command_query, "");
|
assert_eq!(core.snapshot()?.command_query, "");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn settings_scoped_search_opens_spaces_page() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||||
|
|
||||||
|
core.set_command_query("@settings spaces");
|
||||||
|
let intent = core.submit_command()?;
|
||||||
|
let active_tab = core.active_tab()?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
intent,
|
||||||
|
Some(CommandIntent::ScopedSearch {
|
||||||
|
scope: CommandScope::Settings,
|
||||||
|
query: "spaces".to_string(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert_eq!(active_tab.title(), "Space Settings");
|
||||||
|
assert_eq!(active_tab.url().as_str(), "ely://settings/spaces");
|
||||||
|
assert_eq!(core.snapshot()?.command_query, "");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user