From ada0a82da8a1f391f880bd36d5a5a1202ae233a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Fri, 8 May 2026 03:44:36 -0400 Subject: [PATCH] Add profile sync policy controls --- .../src/shell/internal_pages/profiles.rs | 43 +++++++++++++++--- crates/ely_app/src/shell/mod.rs | 17 ++++++- crates/ely_browser_core/src/state/profiles.rs | 17 ++++++- crates/ely_browser_core/tests/profiles.rs | 26 ++++++++++- crates/ely_domain/src/lib.rs | 2 +- crates/ely_domain/src/profile.rs | 44 +++++++++++++++++++ 6 files changed, 139 insertions(+), 10 deletions(-) diff --git a/crates/ely_app/src/shell/internal_pages/profiles.rs b/crates/ely_app/src/shell/internal_pages/profiles.rs index a9c0e67..07807a0 100644 --- a/crates/ely_app/src/shell/internal_pages/profiles.rs +++ b/crates/ely_app/src/shell/internal_pages/profiles.rs @@ -1,12 +1,14 @@ use ely_browser_core::BrowserSnapshot; use ely_design_system::colors; -use ely_domain::{DownloadDestination, DownloadPolicy, Profile, ProfileId, ProfileKind}; +use ely_domain::{ + DownloadDestination, DownloadPolicy, Profile, ProfileId, ProfileKind, ProfileSyncPolicy, +}; use gpui::{ AnyElement, Context, InteractiveElement, IntoElement, ParentElement, SharedString, Styled, div, px, rgb, }; use gpui_component::{ - IconName, Sizable, StyledExt, + IconName, Selectable, Sizable, StyledExt, button::{Button, ButtonVariants}, scroll::ScrollableElement, }; @@ -81,6 +83,8 @@ fn render_profile_row( cx: &mut Context, ) -> AnyElement { let profile_id = profile.id().clone(); + let sync_profile_id = profile.id().clone(); + let sync_policy = profile.sync_policy(); div() .id(SharedString::from(format!("profile-{}", profile.id().as_str()))) @@ -121,7 +125,14 @@ fn render_profile_row( ), ), ) - .child(render_profile_action(index, profile_id, active, cx)) + .child( + div() + .flex() + .items_center() + .gap_3() + .child(render_profile_sync_action(index, sync_profile_id, sync_policy, cx)) + .child(render_profile_action(index, profile_id, active, cx)), + ) .into_any_element() } @@ -163,11 +174,33 @@ fn render_profile_action( .into_any_element() } +fn render_profile_sync_action( + index: usize, + profile_id: ProfileId, + sync_policy: ProfileSyncPolicy, + cx: &mut Context, +) -> AnyElement { + let next_policy = sync_policy.toggled(); + let paused = sync_policy == ProfileSyncPolicy::Paused; + + Button::new(("profile-sync-policy", index)) + .ghost() + .xsmall() + .selected(paused) + .label(sync_policy.action_label()) + .tooltip(sync_policy.label()) + .on_click(cx.listener(move |shell, _, _, cx| { + shell.set_profile_sync_policy(&profile_id, next_policy, cx); + })) + .into_any_element() +} + fn profile_detail_label(profile: &Profile) -> String { format!( - "{} - {}", + "{} - {} - {}", profile_kind_label(profile.kind()), - download_policy_label(profile.download_policy()) + download_policy_label(profile.download_policy()), + profile.sync_policy().label() ) } diff --git a/crates/ely_app/src/shell/mod.rs b/crates/ely_app/src/shell/mod.rs index 8f5fbb5..c718ab4 100644 --- a/crates/ely_app/src/shell/mod.rs +++ b/crates/ely_app/src/shell/mod.rs @@ -8,8 +8,8 @@ mod splits; use ely_browser_core::{BrowserCore, InitialBrowserConfig}; use ely_domain::{ ArchivePolicy, CommandIntent, DownloadPolicy, FavoriteLimit, HistoryRecordingPolicy, - NewTabDestination, ProfileId, SearchEngine, SpaceId, SyncObjectKind, SyncObjectPolicy, TabId, - UrlText, + NewTabDestination, ProfileId, ProfileSyncPolicy, SearchEngine, SpaceId, SyncObjectKind, + SyncObjectPolicy, TabId, UrlText, }; use gpui::{App, AppContext, Context, Entity, FocusHandle, Focusable, Subscription, Window}; use gpui_component::input::{InputEvent, InputState, SelectAll}; @@ -311,6 +311,19 @@ impl ElyShell { } } + fn set_profile_sync_policy( + &mut self, + profile_id: &ProfileId, + sync_policy: ProfileSyncPolicy, + cx: &mut Context, + ) { + if let ShellState::Ready(core) = &mut self.state + && core.set_profile_sync_policy(profile_id, sync_policy).is_ok() + { + cx.notify(); + } + } + fn set_favorite_limit(&mut self, favorite_limit: FavoriteLimit, cx: &mut Context) { if let ShellState::Ready(core) = &mut self.state { core.set_favorite_limit(favorite_limit); diff --git a/crates/ely_browser_core/src/state/profiles.rs b/crates/ely_browser_core/src/state/profiles.rs index 05923f3..cdb89dd 100644 --- a/crates/ely_browser_core/src/state/profiles.rs +++ b/crates/ely_browser_core/src/state/profiles.rs @@ -1,4 +1,4 @@ -use ely_domain::{DownloadPolicy, Profile, ProfileId, ProfileKind, TabId}; +use ely_domain::{DownloadPolicy, Profile, ProfileId, ProfileKind, ProfileSyncPolicy, TabId}; use crate::CoreError; @@ -89,4 +89,19 @@ impl BrowserCore { let profile_id = self.active_profile_id.clone(); self.set_profile_download_policy(&profile_id, download_policy) } + + pub fn set_profile_sync_policy( + &mut self, + profile_id: &ProfileId, + sync_policy: ProfileSyncPolicy, + ) -> Result<(), CoreError> { + let profile = self + .profiles + .iter_mut() + .find(|profile| profile.id() == profile_id) + .ok_or_else(|| CoreError::ProfileNotFound { id: profile_id.clone() })?; + + profile.set_sync_policy(sync_policy); + Ok(()) + } } diff --git a/crates/ely_browser_core/tests/profiles.rs b/crates/ely_browser_core/tests/profiles.rs index 4d73413..5fb07f2 100644 --- a/crates/ely_browser_core/tests/profiles.rs +++ b/crates/ely_browser_core/tests/profiles.rs @@ -1,7 +1,7 @@ use std::error::Error; use ely_browser_core::{BrowserCore, InitialBrowserConfig}; -use ely_domain::{CommandIntent, ProfileKind, UrlText}; +use ely_domain::{CommandIntent, ProfileKind, ProfileSyncPolicy, UrlText}; #[test] fn new_private_profile_command_creates_private_profile() -> Result<(), Box> { @@ -45,3 +45,27 @@ fn private_profiles_do_not_record_history() -> Result<(), Box> { assert!(private_snapshot.history_entries.is_empty()); Ok(()) } + +#[test] +fn profile_sync_policy_can_pause_one_profile() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + let default_profile_id = core.snapshot()?.active_profile_id; + let research_profile_id = core.create_profile("Research", 0x9fc9a2, ProfileKind::Standard)?; + + core.set_profile_sync_policy(&research_profile_id, ProfileSyncPolicy::Paused)?; + let snapshot = core.snapshot()?; + let Some(default_profile) = + snapshot.profiles.iter().find(|profile| profile.id() == &default_profile_id) + else { + return Err("missing default profile".into()); + }; + let Some(research_profile) = + snapshot.profiles.iter().find(|profile| profile.id() == &research_profile_id) + else { + return Err("missing research profile".into()); + }; + + assert_eq!(default_profile.sync_policy(), ProfileSyncPolicy::Enabled); + assert_eq!(research_profile.sync_policy(), ProfileSyncPolicy::Paused); + Ok(()) +} diff --git a/crates/ely_domain/src/lib.rs b/crates/ely_domain/src/lib.rs index 843c484..caf9652 100644 --- a/crates/ely_domain/src/lib.rs +++ b/crates/ely_domain/src/lib.rs @@ -38,7 +38,7 @@ pub use plugin::{ PluginSignature, PluginSignatureAlgorithm, }; pub use privacy::HistoryRecordingPolicy; -pub use profile::{Profile, ProfileKind}; +pub use profile::{Profile, ProfileKind, ProfileSyncPolicy}; pub use reading_list::{ReadingListEntry, ReadingProgress}; pub use search::SearchEngine; pub use site_permission::{ diff --git a/crates/ely_domain/src/profile.rs b/crates/ely_domain/src/profile.rs index 20385a5..58b8721 100644 --- a/crates/ely_domain/src/profile.rs +++ b/crates/ely_domain/src/profile.rs @@ -6,6 +6,39 @@ pub enum ProfileKind { Private, } +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub enum ProfileSyncPolicy { + #[default] + Enabled, + Paused, +} + +impl ProfileSyncPolicy { + #[must_use] + pub fn label(self) -> &'static str { + match self { + Self::Enabled => "Sync on", + Self::Paused => "Sync paused", + } + } + + #[must_use] + pub fn action_label(self) -> &'static str { + match self { + Self::Enabled => "Pause Sync", + Self::Paused => "Resume Sync", + } + } + + #[must_use] + pub fn toggled(self) -> Self { + match self { + Self::Enabled => Self::Paused, + Self::Paused => Self::Enabled, + } + } +} + #[derive(Clone, Debug, Eq, PartialEq)] pub struct Profile { id: ProfileId, @@ -13,6 +46,7 @@ pub struct Profile { color_hex: u32, kind: ProfileKind, download_policy: DownloadPolicy, + sync_policy: ProfileSyncPolicy, } impl Profile { @@ -24,6 +58,7 @@ impl Profile { color_hex, kind, download_policy: DownloadPolicy::ask_every_time(), + sync_policy: ProfileSyncPolicy::default(), } } @@ -52,7 +87,16 @@ impl Profile { &self.download_policy } + #[must_use] + pub fn sync_policy(&self) -> ProfileSyncPolicy { + self.sync_policy + } + pub fn set_download_policy(&mut self, download_policy: DownloadPolicy) { self.download_policy = download_policy; } + + pub fn set_sync_policy(&mut self, sync_policy: ProfileSyncPolicy) { + self.sync_policy = sync_policy; + } }