diff --git a/crates/ely_app/src/shell/internal_pages/sidebar_tabs.rs b/crates/ely_app/src/shell/internal_pages/sidebar_tabs.rs index 91059e0..5900c02 100644 --- a/crates/ely_app/src/shell/internal_pages/sidebar_tabs.rs +++ b/crates/ely_app/src/shell/internal_pages/sidebar_tabs.rs @@ -103,7 +103,8 @@ fn render_sidebar_tabs_header(snapshot: &BrowserSnapshot, active_space: &Space) .text_color(rgb(colors::MUTED)) .child(IconName::LayoutDashboard) .child(format!( - "{} / {}", + "{} / {} / {}", + sidebar_width_label(active_space), archive_policy_label(active_space.archive_policy()), snapshot.favorite_limit.label() )), @@ -127,10 +128,54 @@ fn render_sidebar_tabs_settings( .flex_col() .gap_4() .child(render_archive_policy_section(active_space, cx)) + .child(render_sidebar_width_section(active_space)) .child(render_favorite_limit_section(snapshot.favorite_limit, cx)) .into_any_element() } +fn render_sidebar_width_section(active_space: &Space) -> AnyElement { + div() + .flex() + .flex_col() + .gap_3() + .pt_4() + .child( + div() + .flex() + .items_center() + .justify_between() + .gap_4() + .child( + div() + .min_w_0() + .flex() + .flex_col() + .gap_1() + .child( + div() + .text_sm() + .font_semibold() + .text_color(rgb(colors::INK)) + .child("Sidebar Width"), + ) + .child( + div() + .text_xs() + .text_color(rgb(colors::MUTED)) + .child("Current Space sidebar width."), + ), + ) + .child( + div() + .text_xs() + .font_semibold() + .text_color(rgb(colors::MUTED)) + .child(sidebar_width_label(active_space)), + ), + ) + .into_any_element() +} + fn render_archive_policy_section(active_space: &Space, cx: &mut Context) -> AnyElement { div() .flex() @@ -233,6 +278,10 @@ fn render_favorite_limit_section( .into_any_element() } +fn sidebar_width_label(space: &Space) -> String { + format!("{} px", space.sidebar_width_px()) +} + fn render_archive_policy_option( index: usize, option: &'static ArchivePolicyOption, diff --git a/crates/ely_app/src/shell/render.rs b/crates/ely_app/src/shell/render.rs index b737880..2935b90 100644 --- a/crates/ely_app/src/shell/render.rs +++ b/crates/ely_app/src/shell/render.rs @@ -32,6 +32,11 @@ impl ElyShell { active_tab: BrowserTab, cx: &mut Context, ) -> AnyElement { + let sidebar_width = match active_sidebar_width(&snapshot) { + Ok(sidebar_width) => sidebar_width, + Err(message) => return render_error(message), + }; + div() .size_full() .track_focus(&self.focus_handle) @@ -53,13 +58,13 @@ impl ElyShell { .text_color(rgb(ELY_THEME.ink)) .flex() .flex_col() - .child(self.render_command_bar(&snapshot, &active_tab, cx)) + .child(self.render_command_bar(&snapshot, &active_tab, sidebar_width, cx)) .child( div() .flex() .flex_1() .overflow_hidden() - .child(self.render_sidebar(&snapshot, cx)) + .child(self.render_sidebar(&snapshot, sidebar_width, cx)) .child(self.render_content_area(&snapshot, &active_tab, cx)), ) .into_any_element() @@ -69,6 +74,7 @@ impl ElyShell { &mut self, snapshot: &BrowserSnapshot, active_tab: &BrowserTab, + sidebar_width: f32, cx: &mut Context, ) -> AnyElement { let favorite_icon = @@ -87,7 +93,7 @@ impl ElyShell { .border_color(rgb(colors::HAIRLINE)) .child( div() - .w(px(spacing::SIDEBAR_WIDTH - spacing::XL)) + .w(px(sidebar_width - spacing::XL)) .flex() .items_center() .gap_2() @@ -139,9 +145,14 @@ impl ElyShell { .into_any_element() } - fn render_sidebar(&mut self, snapshot: &BrowserSnapshot, cx: &mut Context) -> AnyElement { + fn render_sidebar( + &mut self, + snapshot: &BrowserSnapshot, + sidebar_width: f32, + cx: &mut Context, + ) -> AnyElement { div() - .w(px(spacing::SIDEBAR_WIDTH)) + .w(px(sidebar_width)) .h_full() .flex() .flex_col() @@ -420,6 +431,16 @@ fn section_label(label: &'static str) -> impl IntoElement { div().text_xs().font_semibold().text_color(rgb(colors::MUTED)).child(label) } +fn active_sidebar_width(snapshot: &BrowserSnapshot) -> Result { + let Some(active_space) = + snapshot.spaces.iter().find(|space| space.id() == &snapshot.active_space_id) + else { + return Err("Active Space is unavailable.".to_string()); + }; + + Ok(f32::from(active_space.sidebar_width_px())) +} + fn archive_source_label(source: &ArchiveSource) -> &'static str { match source { ArchiveSource::ManualClose => "Closed", diff --git a/crates/ely_browser_core/src/state.rs b/crates/ely_browser_core/src/state.rs index f75500b..0ecb620 100644 --- a/crates/ely_browser_core/src/state.rs +++ b/crates/ely_browser_core/src/state.rs @@ -252,6 +252,20 @@ impl BrowserCore { Ok(()) } + pub fn set_space_sidebar_width( + &mut self, + space_id: &SpaceId, + sidebar_width_px: u16, + ) -> Result<(), CoreError> { + let space = self + .spaces + .iter_mut() + .find(|space| space.id() == space_id) + .ok_or_else(|| CoreError::SpaceNotFound { id: space_id.clone() })?; + space.set_sidebar_width_px(sidebar_width_px); + Ok(()) + } + pub fn set_search_engine(&mut self, search_engine: SearchEngine) { self.search_engine = search_engine; } diff --git a/crates/ely_browser_core/tests/spaces.rs b/crates/ely_browser_core/tests/spaces.rs index a496ef9..7e68822 100644 --- a/crates/ely_browser_core/tests/spaces.rs +++ b/crates/ely_browser_core/tests/spaces.rs @@ -1,7 +1,7 @@ use std::error::Error; use ely_browser_core::{BrowserCore, CoreError, InitialBrowserConfig}; -use ely_domain::{ArchivePolicy, ProfileId, ProfileKind}; +use ely_domain::{ArchivePolicy, DEFAULT_SIDEBAR_WIDTH_PX, ProfileId, ProfileKind}; #[test] fn created_space_binds_current_profile_as_default() -> Result<(), Box> { @@ -36,6 +36,21 @@ fn created_space_records_creation_timestamps() -> Result<(), Box> { Ok(()) } +#[test] +fn created_space_uses_default_sidebar_width() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + let research_space_id = core.create_space("Research", "R", 0x9fc9a2)?; + let snapshot = core.snapshot()?; + let Some(research_space) = + snapshot.spaces.iter().find(|space| space.id() == &research_space_id) + else { + return Err("missing research space".into()); + }; + + assert_eq!(research_space.sidebar_width_px(), DEFAULT_SIDEBAR_WIDTH_PX); + Ok(()) +} + #[test] fn space_default_profile_updates_with_profile_validation() -> Result<(), Box> { let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; @@ -73,8 +88,27 @@ fn space_settings_refresh_updated_at() -> Result<(), Box> { core.set_space_default_profile(&work_space_id, &research_profile_id)?; let after_profile_update = active_space_updated_at(&core, &work_space_id)?; + core.set_space_sidebar_width(&work_space_id, 320)?; + let after_sidebar_update = active_space_updated_at(&core, &work_space_id)?; + assert!(after_archive_update > before_update); assert!(after_profile_update > after_archive_update); + assert!(after_sidebar_update > after_profile_update); + Ok(()) +} + +#[test] +fn space_sidebar_width_updates_selected_space() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + let work_space_id = core.snapshot()?.active_space_id; + + core.set_space_sidebar_width(&work_space_id, 320)?; + let snapshot = core.snapshot()?; + let Some(work_space) = snapshot.spaces.iter().find(|space| space.id() == &work_space_id) else { + return Err("missing work space".into()); + }; + + assert_eq!(work_space.sidebar_width_px(), 320); Ok(()) } diff --git a/crates/ely_domain/src/lib.rs b/crates/ely_domain/src/lib.rs index caf9652..12d2141 100644 --- a/crates/ely_domain/src/lib.rs +++ b/crates/ely_domain/src/lib.rs @@ -45,7 +45,7 @@ pub use site_permission::{ SiteOrigin, SitePermissionAuditAction, SitePermissionAuditEvent, SitePermissionDecision, SitePermissionEntry, SitePermissionFeature, }; -pub use space::{ArchivePolicy, Space}; +pub use space::{ArchivePolicy, DEFAULT_SIDEBAR_WIDTH_PX, Space}; pub use split::{MAX_SPLIT_PANES, SplitAxis, SplitLayout, SplitPane}; pub use sync::{ SyncConnectionState, SyncObjectKind, SyncObjectPolicy, SyncObjectState, SyncObjectStatus, diff --git a/crates/ely_domain/src/space.rs b/crates/ely_domain/src/space.rs index 92773bf..43d0a3c 100644 --- a/crates/ely_domain/src/space.rs +++ b/crates/ely_domain/src/space.rs @@ -2,6 +2,8 @@ use std::time::{Duration, SystemTime}; use crate::{ProfileId, SpaceId}; +pub const DEFAULT_SIDEBAR_WIDTH_PX: u16 = 280; + #[derive(Clone, Debug, Eq, PartialEq)] pub enum ArchivePolicy { Manual, @@ -16,6 +18,7 @@ pub struct Space { accent_hex: u32, default_profile_id: ProfileId, archive_policy: ArchivePolicy, + sidebar_width_px: u16, created_at: SystemTime, updated_at: SystemTime, } @@ -36,6 +39,7 @@ impl Space { accent_hex, default_profile_id, archive_policy: ArchivePolicy::Manual, + sidebar_width_px: DEFAULT_SIDEBAR_WIDTH_PX, created_at, updated_at: created_at, } @@ -81,6 +85,16 @@ impl Space { self.record_update(); } + #[must_use] + pub fn sidebar_width_px(&self) -> u16 { + self.sidebar_width_px + } + + pub fn set_sidebar_width_px(&mut self, sidebar_width_px: u16) { + self.sidebar_width_px = sidebar_width_px; + self.record_update(); + } + #[must_use] pub fn created_at(&self) -> SystemTime { self.created_at