From 7797203bbf7296b1d70316075da78883d68b27a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Sat, 9 May 2026 00:31:40 -0400 Subject: [PATCH] Add private window entry point --- crates/ely_app/src/main.rs | 79 +++++++++++++------ crates/ely_app/src/shell/mod.rs | 14 +++- .../src/shell/web_surface_permissions.rs | 3 + crates/ely_app/src/shortcuts.rs | 29 ++++++- crates/ely_browser_core/src/state.rs | 23 +++++- crates/ely_browser_core/tests/profiles.rs | 20 +++++ 6 files changed, 139 insertions(+), 29 deletions(-) diff --git a/crates/ely_app/src/main.rs b/crates/ely_app/src/main.rs index 4df6347..6fe0926 100644 --- a/crates/ely_app/src/main.rs +++ b/crates/ely_app/src/main.rs @@ -19,6 +19,7 @@ actions!( OpenDownloads, OpenHistory, OpenNewTab, + OpenPrivateWindow, OpenSettings, OpenTaskManager, Quit, @@ -39,6 +40,7 @@ fn main() { gpui_component::init(cx); cx.on_action(quit); bind_shortcuts(cx); + cx.on_action(open_private_window); cx.set_menus(vec![ Menu { name: "ELY Browser".into(), @@ -52,6 +54,7 @@ fn main() { name: "File".into(), items: vec![ MenuItem::action("New Tab", OpenNewTab), + MenuItem::action("New Private Window", OpenPrivateWindow), MenuItem::action("Split Right", SplitRight), MenuItem::separator(), MenuItem::action("Command Mode", FocusCommandMode), @@ -78,30 +81,7 @@ fn main() { }, ]); - let bounds = Bounds::centered(None, size(px(1240.0), px(780.0)), cx); - let opened = cx.open_window( - WindowOptions { - titlebar: Some(TitlebarOptions { - title: Some("ELY Browser".into()), - appears_transparent: true, - traffic_light_position: Some(point(px(18.0), px(24.0))), - }), - window_bounds: Some(WindowBounds::Windowed(bounds)), - ..WindowOptions::default() - }, - |window, cx| { - let shell = cx.new(|cx| ElyShell::new(window, cx)); - let focus_handle = shell.focus_handle(cx); - window.defer(cx, move |window, cx| { - if window.focused(cx).is_none() { - focus_handle.focus(window); - } - }); - cx.new(|cx| gpui_component::Root::new(shell, window, cx)) - }, - ); - - if opened.is_ok() { + if open_browser_window(cx, BrowserWindowMode::Standard) { cx.activate(true); } else { cx.quit(); @@ -109,6 +89,57 @@ fn main() { }); } +#[derive(Clone, Copy)] +enum BrowserWindowMode { + Standard, + Private, +} + +impl BrowserWindowMode { + fn title(self) -> &'static str { + match self { + Self::Standard => "ELY Browser", + Self::Private => "ELY Browser - Private", + } + } +} + +fn open_browser_window(cx: &mut App, mode: BrowserWindowMode) -> bool { + let bounds = Bounds::centered(None, size(px(1240.0), px(780.0)), cx); + let opened = cx.open_window( + WindowOptions { + titlebar: Some(TitlebarOptions { + title: Some(mode.title().into()), + appears_transparent: true, + traffic_light_position: Some(point(px(18.0), px(24.0))), + }), + window_bounds: Some(WindowBounds::Windowed(bounds)), + ..WindowOptions::default() + }, + |window, cx| { + let shell = cx.new(|cx| match mode { + BrowserWindowMode::Standard => ElyShell::new(window, cx), + BrowserWindowMode::Private => ElyShell::new_private(window, cx), + }); + let focus_handle = shell.focus_handle(cx); + window.defer(cx, move |window, cx| { + if window.focused(cx).is_none() { + focus_handle.focus(window); + } + }); + cx.new(|cx| gpui_component::Root::new(shell, window, cx)) + }, + ); + + opened.is_ok() +} + fn quit(_: &Quit, cx: &mut App) { cx.quit(); } + +fn open_private_window(_: &OpenPrivateWindow, cx: &mut App) { + if open_browser_window(cx, BrowserWindowMode::Private) { + cx.activate(true); + } +} diff --git a/crates/ely_app/src/shell/mod.rs b/crates/ely_app/src/shell/mod.rs index 195af18..b2099a8 100644 --- a/crates/ely_app/src/shell/mod.rs +++ b/crates/ely_app/src/shell/mod.rs @@ -77,6 +77,18 @@ pub struct ElyShell { impl ElyShell { pub fn new(window: &mut Window, cx: &mut Context) -> Self { + Self::new_with_config(InitialBrowserConfig::ely_defaults(), window, cx) + } + + pub fn new_private(window: &mut Window, cx: &mut Context) -> Self { + Self::new_with_config(InitialBrowserConfig::private_window(), window, cx) + } + + fn new_with_config( + config: Result, + window: &mut Window, + cx: &mut Context, + ) -> Self { let command_input = cx.new(|cx| InputState::new(window, cx).placeholder("Search or enter address")); @@ -112,7 +124,7 @@ impl ElyShell { }, ); - let state = match InitialBrowserConfig::ely_defaults().and_then(|config| { + let state = match config.and_then(|config| { BrowserCore::new(config).map_err(|error| match error { ely_browser_core::CoreError::Domain(source) => source, _ => ely_domain::DomainError::InvalidCommand, diff --git a/crates/ely_app/src/shell/web_surface_permissions.rs b/crates/ely_app/src/shell/web_surface_permissions.rs index 60a20ec..ff264a0 100644 --- a/crates/ely_app/src/shell/web_surface_permissions.rs +++ b/crates/ely_app/src/shell/web_surface_permissions.rs @@ -74,7 +74,10 @@ mod tests { let mut core = BrowserCore::new(InitialBrowserConfig { space_name: "Work".to_string(), space_icon: "W".to_string(), + space_color_hex: 0xf54e00, profile_name: "Default".to_string(), + profile_color_hex: 0x26251e, + profile_kind: ely_domain::ProfileKind::Standard, new_tab_destination: Default::default(), })?; core.open_tab(UrlText::parse(url)?); diff --git a/crates/ely_app/src/shortcuts.rs b/crates/ely_app/src/shortcuts.rs index 46bcede..8fa1e95 100644 --- a/crates/ely_app/src/shortcuts.rs +++ b/crates/ely_app/src/shortcuts.rs @@ -4,8 +4,9 @@ use gpui::{App, KeyBinding}; use crate::{ CloseCurrentTab, FocusAddressBar, FocusCommandMode, OpenDownloads, OpenHistory, OpenNewTab, - OpenSettings, OpenTaskManager, Quit, RestoreClosedTab, SelectNextSpace, SelectNextTab, - SelectPreviousSpace, SelectPreviousTab, SplitRight, ToggleFavoriteTab, ToggleSidebar, + OpenPrivateWindow, OpenSettings, OpenTaskManager, Quit, RestoreClosedTab, SelectNextSpace, + SelectNextTab, SelectPreviousSpace, SelectPreviousTab, SplitRight, ToggleFavoriteTab, + ToggleSidebar, }; #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] @@ -28,6 +29,7 @@ pub(crate) enum ShortcutAction { FocusAddressBar, FocusCommandMode, OpenNewTab, + OpenPrivateWindow, CloseCurrentTab, RestoreClosedTab, SelectNextSpace, @@ -50,6 +52,7 @@ impl ShortcutAction { Self::FocusAddressBar => "Command Bar", Self::FocusCommandMode => "Command Mode", Self::OpenNewTab => "New Tab", + Self::OpenPrivateWindow => "New Private Window", Self::CloseCurrentTab => "Close Tab", Self::RestoreClosedTab => "Restore Closed Tab", Self::SelectNextSpace => "Next Space", @@ -71,6 +74,7 @@ impl ShortcutAction { match self { Self::FocusAddressBar | Self::FocusCommandMode => "Command", Self::OpenNewTab + | Self::OpenPrivateWindow | Self::CloseCurrentTab | Self::RestoreClosedTab | Self::SelectNextSpace @@ -91,6 +95,7 @@ impl ShortcutAction { Self::FocusAddressBar => None, Self::FocusCommandMode => None, Self::OpenNewTab => Some(">new-tab"), + Self::OpenPrivateWindow => None, Self::CloseCurrentTab => Some(">close-tab"), Self::RestoreClosedTab => Some(">restore-tab"), Self::SelectNextSpace => None, @@ -133,6 +138,7 @@ pub(crate) const SHORTCUT_ACTIONS: &[ShortcutAction] = &[ ShortcutAction::FocusAddressBar, ShortcutAction::FocusCommandMode, ShortcutAction::OpenNewTab, + ShortcutAction::OpenPrivateWindow, ShortcutAction::CloseCurrentTab, ShortcutAction::RestoreClosedTab, ShortcutAction::SelectNextSpace, @@ -152,6 +158,8 @@ pub(crate) const SHORTCUT_ACTIONS: &[ShortcutAction] = &[ pub(crate) const SHORTCUT_BINDINGS: &[ShortcutBinding] = &[ shortcut(ShortcutAction::OpenNewTab, ShortcutPlatform::Macos, "cmd-t"), shortcut(ShortcutAction::OpenNewTab, ShortcutPlatform::WindowsLinux, "ctrl-t"), + shortcut(ShortcutAction::OpenPrivateWindow, ShortcutPlatform::Macos, "cmd-shift-n"), + shortcut(ShortcutAction::OpenPrivateWindow, ShortcutPlatform::WindowsLinux, "ctrl-shift-n"), shortcut(ShortcutAction::SplitRight, ShortcutPlatform::Macos, "cmd-\\"), shortcut(ShortcutAction::SplitRight, ShortcutPlatform::WindowsLinux, "ctrl-\\"), shortcut(ShortcutAction::ToggleSidebar, ShortcutPlatform::Macos, "cmd-b"), @@ -245,6 +253,9 @@ impl ShortcutBinding { ShortcutAction::OpenDownloads => KeyBinding::new(self.keystroke, OpenDownloads, None), ShortcutAction::OpenHistory => KeyBinding::new(self.keystroke, OpenHistory, None), ShortcutAction::OpenNewTab => KeyBinding::new(self.keystroke, OpenNewTab, None), + ShortcutAction::OpenPrivateWindow => { + KeyBinding::new(self.keystroke, OpenPrivateWindow, None) + } ShortcutAction::OpenSettings => KeyBinding::new(self.keystroke, OpenSettings, None), ShortcutAction::OpenTaskManager => { KeyBinding::new(self.keystroke, OpenTaskManager, None) @@ -317,6 +328,20 @@ mod tests { assert_eq!(bindings, vec!["Cmd + ,".to_string(), "Ctrl + ,".to_string()]); } + #[test] + fn private_window_shortcut_has_platform_bindings() { + let bindings = + bindings_for_action(ShortcutAction::OpenPrivateWindow, ShortcutPlatform::Macos) + .chain(bindings_for_action( + ShortcutAction::OpenPrivateWindow, + ShortcutPlatform::WindowsLinux, + )) + .map(|binding| binding.display_keystroke()) + .collect::>(); + + assert_eq!(bindings, vec!["Cmd + Shift + N".to_string(), "Ctrl + Shift + N".to_string()]); + } + #[test] fn toggle_sidebar_shortcut_has_platform_bindings() { let bindings = bindings_for_action(ShortcutAction::ToggleSidebar, ShortcutPlatform::Macos) diff --git a/crates/ely_browser_core/src/state.rs b/crates/ely_browser_core/src/state.rs index d0a074e..9f1fd47 100644 --- a/crates/ely_browser_core/src/state.rs +++ b/crates/ely_browser_core/src/state.rs @@ -43,7 +43,10 @@ pub use spaces::TrashedSpace; pub struct InitialBrowserConfig { pub space_name: String, pub space_icon: String, + pub space_color_hex: u32, pub profile_name: String, + pub profile_color_hex: u32, + pub profile_kind: ProfileKind, pub new_tab_destination: NewTabDestination, } @@ -52,7 +55,22 @@ impl InitialBrowserConfig { Ok(Self { space_name: "Work".to_string(), space_icon: "W".to_string(), + space_color_hex: 0xf54e00, profile_name: "Default".to_string(), + profile_color_hex: 0x26251e, + profile_kind: ProfileKind::Standard, + new_tab_destination: NewTabDestination::default(), + }) + } + + pub fn private_window() -> Result { + Ok(Self { + space_name: "Private".to_string(), + space_icon: "P".to_string(), + space_color_hex: 0x807d72, + profile_name: "Private".to_string(), + profile_color_hex: 0x807d72, + profile_kind: ProfileKind::Private, new_tab_destination: NewTabDestination::default(), }) } @@ -127,12 +145,13 @@ pub struct BrowserCore { impl BrowserCore { pub fn new(config: InitialBrowserConfig) -> Result { - let profile = Profile::new(config.profile_name, 0x26251e, ProfileKind::Standard); + let profile = + Profile::new(config.profile_name, config.profile_color_hex, config.profile_kind); let active_profile_id = profile.id().clone(); let space = Space::new( config.space_name, config.space_icon, - 0xf54e00, + config.space_color_hex, active_profile_id.clone(), 0, ); diff --git a/crates/ely_browser_core/tests/profiles.rs b/crates/ely_browser_core/tests/profiles.rs index 33b1e9f..4d57acc 100644 --- a/crates/ely_browser_core/tests/profiles.rs +++ b/crates/ely_browser_core/tests/profiles.rs @@ -24,6 +24,26 @@ fn new_private_profile_command_creates_private_profile() -> Result<(), Box Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::private_window()?)?; + let snapshot = core.snapshot()?; + let Some(private_profile) = + snapshot.profiles.iter().find(|profile| profile.id() == &snapshot.active_profile_id) + else { + return Err("missing active private profile".into()); + }; + + assert_eq!(snapshot.active_space_name, "Private"); + assert_eq!(snapshot.active_profile_name, "Private"); + assert_eq!(private_profile.kind(), &ProfileKind::Private); + assert_eq!(private_profile.sync_policy(), ProfileSyncPolicy::Paused); + + core.open_tab(UrlText::parse("https://example.com/private-window")?); + assert!(core.snapshot()?.history_entries.is_empty()); + Ok(()) +} + #[test] fn private_profiles_do_not_record_history() -> Result<(), Box> { let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;