Add private window entry point

This commit is contained in:
2026-05-09 00:31:40 -04:00
parent 97a06d7b52
commit 7797203bbf
6 changed files with 139 additions and 29 deletions
+55 -24
View File
@@ -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);
}
}
+13 -1
View File
@@ -77,6 +77,18 @@ pub struct ElyShell {
impl ElyShell {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
Self::new_with_config(InitialBrowserConfig::ely_defaults(), window, cx)
}
pub fn new_private(window: &mut Window, cx: &mut Context<Self>) -> Self {
Self::new_with_config(InitialBrowserConfig::private_window(), window, cx)
}
fn new_with_config(
config: Result<InitialBrowserConfig, ely_domain::DomainError>,
window: &mut Window,
cx: &mut Context<Self>,
) -> 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,
@@ -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)?);
+27 -2
View File
@@ -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::<Vec<_>>();
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)
+21 -2
View File
@@ -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<Self, DomainError> {
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<Self, CoreError> {
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,
);
+20
View File
@@ -24,6 +24,26 @@ fn new_private_profile_command_creates_private_profile() -> Result<(), Box<dyn E
Ok(())
}
#[test]
fn private_window_config_starts_with_private_profile() -> Result<(), Box<dyn Error>> {
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<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;