diff --git a/assets/settings/default.json b/assets/settings/default.json index c40ed4e8da..08faedbed6 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -122,13 +122,17 @@ // Amount of indentation for nested items. "indent_size": 20 }, - "channels_panel": { + "collaboration_panel": { + // Whether to show the collaboration panel button in the status bar. + "button": true, // Where to dock channels panel. Can be 'left' or 'right'. "dock": "left", // Default width of the channels panel. "default_width": 240 }, "assistant": { + // Whether to show the assistant panel button in the status bar. + "button": true, // Where to dock the assistant. Can be 'left', 'right' or 'bottom'. "dock": "right", // Default width when the assistant is docked to the left or right. @@ -220,7 +224,9 @@ "copilot": { // The set of glob patterns for which copilot should be disabled // in any matching file. - "disabled_globs": [".env"] + "disabled_globs": [ + ".env" + ] }, // Settings specific to journaling "journal": { diff --git a/crates/ai/src/assistant.rs b/crates/ai/src/assistant.rs index 957c5e1c06..35d3c9f7ef 100644 --- a/crates/ai/src/assistant.rs +++ b/crates/ai/src/assistant.rs @@ -192,6 +192,7 @@ impl AssistantPanel { old_dock_position = new_dock_position; cx.emit(AssistantPanelEvent::DockPositionChanged); } + cx.notify(); })]; this @@ -790,8 +791,10 @@ impl Panel for AssistantPanel { } } - fn icon_path(&self) -> &'static str { - "icons/robot_14.svg" + fn icon_path(&self, cx: &WindowContext) -> Option<&'static str> { + settings::get::(cx) + .button + .then(|| "icons/robot_14.svg") } fn icon_tooltip(&self) -> (String, Option>) { diff --git a/crates/ai/src/assistant_settings.rs b/crates/ai/src/assistant_settings.rs index eb92e0f6e8..04ba8fb946 100644 --- a/crates/ai/src/assistant_settings.rs +++ b/crates/ai/src/assistant_settings.rs @@ -13,6 +13,7 @@ pub enum AssistantDockPosition { #[derive(Deserialize, Debug)] pub struct AssistantSettings { + pub button: bool, pub dock: AssistantDockPosition, pub default_width: f32, pub default_height: f32, @@ -20,6 +21,7 @@ pub struct AssistantSettings { #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] pub struct AssistantSettingsContent { + pub button: Option, pub dock: Option, pub default_width: Option, pub default_height: Option, diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 382381dba1..d8e2682316 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -27,7 +27,7 @@ use gpui::{ Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use menu::{Confirm, SelectNext, SelectPrev}; -use panel_settings::{ChannelsPanelDockPosition, ChannelsPanelSettings}; +use panel_settings::{CollaborationPanelDockPosition, CollaborationPanelSettings}; use project::{Fs, Project}; use serde_derive::{Deserialize, Serialize}; use settings::SettingsStore; @@ -65,7 +65,7 @@ impl_actions!(collab_panel, [RemoveChannel, NewChannel, AddMember]); const CHANNELS_PANEL_KEY: &'static str = "ChannelsPanel"; pub fn init(_client: Arc, cx: &mut AppContext) { - settings::register::(cx); + settings::register::(cx); contact_finder::init(cx); channel_modal::init(cx); @@ -95,6 +95,7 @@ pub struct CollabPanel { entries: Vec, selection: Option, user_store: ModelHandle, + client: Arc, channel_store: ModelHandle, project: ModelHandle, match_candidates: Vec, @@ -320,6 +321,7 @@ impl CollabPanel { match_candidates: Vec::default(), collapsed_sections: Vec::default(), workspace: workspace.weak_handle(), + client: workspace.app_state().client.clone(), list_state, }; this.update_entries(cx); @@ -334,6 +336,7 @@ impl CollabPanel { old_dock_position = new_dock_position; cx.emit(Event::DockPositionChanged); } + cx.notify(); }), ); @@ -1862,6 +1865,31 @@ impl View for CollabPanel { fn render(&mut self, cx: &mut gpui::ViewContext<'_, '_, Self>) -> gpui::AnyElement { let theme = &theme::current(cx).collab_panel; + if self.user_store.read(cx).current_user().is_none() { + enum LogInButton {} + + return Flex::column() + .with_child( + MouseEventHandler::::new(0, cx, |state, _| { + let button = theme.log_in_button.style_for(state); + Label::new("Sign in to collaborate", button.text.clone()) + .contained() + .with_style(button.container) + }) + .on_click(MouseButton::Left, |_, this, cx| { + let client = this.client.clone(); + cx.spawn(|_, cx| async move { + client.authenticate_and_connect(true, &cx).await.log_err() + }) + .detach(); + }) + .with_cursor_style(CursorStyle::PointingHand), + ) + .contained() + .with_style(theme.container) + .into_any(); + } + enum PanelFocus {} MouseEventHandler::::new(0, cx, |_, cx| { Stack::new() @@ -1901,9 +1929,9 @@ impl View for CollabPanel { impl Panel for CollabPanel { fn position(&self, cx: &gpui::WindowContext) -> DockPosition { - match settings::get::(cx).dock { - ChannelsPanelDockPosition::Left => DockPosition::Left, - ChannelsPanelDockPosition::Right => DockPosition::Right, + match settings::get::(cx).dock { + CollaborationPanelDockPosition::Left => DockPosition::Left, + CollaborationPanelDockPosition::Right => DockPosition::Right, } } @@ -1912,13 +1940,15 @@ impl Panel for CollabPanel { } fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { - settings::update_settings_file::( + settings::update_settings_file::( self.fs.clone(), cx, move |settings| { let dock = match position { - DockPosition::Left | DockPosition::Bottom => ChannelsPanelDockPosition::Left, - DockPosition::Right => ChannelsPanelDockPosition::Right, + DockPosition::Left | DockPosition::Bottom => { + CollaborationPanelDockPosition::Left + } + DockPosition::Right => CollaborationPanelDockPosition::Right, }; settings.dock = Some(dock); }, @@ -1927,7 +1957,7 @@ impl Panel for CollabPanel { fn size(&self, cx: &gpui::WindowContext) -> f32 { self.width - .unwrap_or_else(|| settings::get::(cx).default_width) + .unwrap_or_else(|| settings::get::(cx).default_width) } fn set_size(&mut self, size: f32, cx: &mut ViewContext) { @@ -1936,8 +1966,10 @@ impl Panel for CollabPanel { cx.notify(); } - fn icon_path(&self) -> &'static str { - "icons/radix/person.svg" + fn icon_path(&self, cx: &gpui::WindowContext) -> Option<&'static str> { + settings::get::(cx) + .button + .then(|| "icons/radix/person.svg") } fn icon_tooltip(&self) -> (String, Option>) { diff --git a/crates/collab_ui/src/collab_panel/panel_settings.rs b/crates/collab_ui/src/collab_panel/panel_settings.rs index fe3484b782..5e2954b915 100644 --- a/crates/collab_ui/src/collab_panel/panel_settings.rs +++ b/crates/collab_ui/src/collab_panel/panel_settings.rs @@ -5,27 +5,29 @@ use settings::Setting; #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] -pub enum ChannelsPanelDockPosition { +pub enum CollaborationPanelDockPosition { Left, Right, } #[derive(Deserialize, Debug)] -pub struct ChannelsPanelSettings { - pub dock: ChannelsPanelDockPosition, +pub struct CollaborationPanelSettings { + pub button: bool, + pub dock: CollaborationPanelDockPosition, pub default_width: f32, } #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] -pub struct ChannelsPanelSettingsContent { - pub dock: Option, +pub struct CollaborationPanelSettingsContent { + pub button: Option, + pub dock: Option, pub default_width: Option, } -impl Setting for ChannelsPanelSettings { - const KEY: Option<&'static str> = Some("channels_panel"); +impl Setting for CollaborationPanelSettings { + const KEY: Option<&'static str> = Some("collaboration_panel"); - type FileContent = ChannelsPanelSettingsContent; + type FileContent = CollaborationPanelSettingsContent; fn load( default_value: &Self::FileContent, diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 0383117de8..4d84a1c638 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1657,8 +1657,8 @@ impl workspace::dock::Panel for ProjectPanel { cx.notify(); } - fn icon_path(&self) -> &'static str { - "icons/folder_tree_16.svg" + fn icon_path(&self, _: &WindowContext) -> Option<&'static str> { + Some("icons/folder_tree_16.svg") } fn icon_tooltip(&self) -> (String, Option>) { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 6ad321c735..34752ad3c4 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -396,8 +396,8 @@ impl Panel for TerminalPanel { } } - fn icon_path(&self) -> &'static str { - "icons/terminal_12.svg" + fn icon_path(&self, _: &WindowContext) -> Option<&'static str> { + Some("icons/terminal_12.svg") } fn icon_tooltip(&self) -> (String, Option>) { diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index b3640f538f..c554f77fe4 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -220,6 +220,7 @@ pub struct CopilotAuthAuthorized { pub struct CollabPanel { #[serde(flatten)] pub container: ContainerStyle, + pub log_in_button: Interactive, pub channel_hash: Icon, pub channel_modal: ChannelModal, pub user_query_editor: FieldEditor, diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 6c88e5032c..e447a43d55 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -14,7 +14,7 @@ pub trait Panel: View { fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); fn size(&self, cx: &WindowContext) -> f32; fn set_size(&mut self, size: f32, cx: &mut ViewContext); - fn icon_path(&self) -> &'static str; + fn icon_path(&self, cx: &WindowContext) -> Option<&'static str>; fn icon_tooltip(&self) -> (String, Option>); fn icon_label(&self, _: &WindowContext) -> Option { None @@ -51,7 +51,7 @@ pub trait PanelHandle { fn set_active(&self, active: bool, cx: &mut WindowContext); fn size(&self, cx: &WindowContext) -> f32; fn set_size(&self, size: f32, cx: &mut WindowContext); - fn icon_path(&self, cx: &WindowContext) -> &'static str; + fn icon_path(&self, cx: &WindowContext) -> Option<&'static str>; fn icon_tooltip(&self, cx: &WindowContext) -> (String, Option>); fn icon_label(&self, cx: &WindowContext) -> Option; fn has_focus(&self, cx: &WindowContext) -> bool; @@ -98,8 +98,8 @@ where self.update(cx, |this, cx| this.set_active(active, cx)) } - fn icon_path(&self, cx: &WindowContext) -> &'static str { - self.read(cx).icon_path() + fn icon_path(&self, cx: &WindowContext) -> Option<&'static str> { + self.read(cx).icon_path(cx) } fn icon_tooltip(&self, cx: &WindowContext) -> (String, Option>) { @@ -490,8 +490,9 @@ impl View for PanelButtons { .map(|item| (item.panel.clone(), item.context_menu.clone())) .collect::>(); Flex::row() - .with_children(panels.into_iter().enumerate().map( + .with_children(panels.into_iter().enumerate().filter_map( |(panel_ix, (view, context_menu))| { + let icon_path = view.icon_path(cx)?; let is_active = is_open && panel_ix == active_ix; let (tooltip, tooltip_action) = if is_active { ( @@ -505,94 +506,96 @@ impl View for PanelButtons { } else { view.icon_tooltip(cx) }; - Stack::new() - .with_child( - MouseEventHandler::::new(panel_ix, cx, |state, cx| { - let style = button_style.in_state(is_active); + Some( + Stack::new() + .with_child( + MouseEventHandler::::new(panel_ix, cx, |state, cx| { + let style = button_style.in_state(is_active); - let style = style.style_for(state); - Flex::row() - .with_child( - Svg::new(view.icon_path(cx)) - .with_color(style.icon_color) - .constrained() - .with_width(style.icon_size) - .aligned(), - ) - .with_children(if let Some(label) = view.icon_label(cx) { - Some( - Label::new(label, style.label.text.clone()) - .contained() - .with_style(style.label.container) + let style = style.style_for(state); + Flex::row() + .with_child( + Svg::new(icon_path) + .with_color(style.icon_color) + .constrained() + .with_width(style.icon_size) .aligned(), ) - } else { - None - }) - .constrained() - .with_height(style.icon_size) - .contained() - .with_style(style.container) - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, { - let tooltip_action = - tooltip_action.as_ref().map(|action| action.boxed_clone()); - move |_, this, cx| { - if let Some(tooltip_action) = &tooltip_action { - let window_id = cx.window_id(); - let view_id = this.workspace.id(); - let tooltip_action = tooltip_action.boxed_clone(); - cx.spawn(|_, mut cx| async move { - cx.dispatch_action( - window_id, - view_id, - &*tooltip_action, + .with_children(if let Some(label) = view.icon_label(cx) { + Some( + Label::new(label, style.label.text.clone()) + .contained() + .with_style(style.label.container) + .aligned(), ) - .ok(); + } else { + None }) - .detach(); - } - } - }) - .on_click(MouseButton::Right, { - let view = view.clone(); - let menu = context_menu.clone(); - move |_, _, cx| { - const POSITIONS: [DockPosition; 3] = [ - DockPosition::Left, - DockPosition::Right, - DockPosition::Bottom, - ]; - - menu.update(cx, |menu, cx| { - let items = POSITIONS - .into_iter() - .filter(|position| { - *position != dock_position - && view.position_is_valid(*position, cx) - }) - .map(|position| { - let view = view.clone(); - ContextMenuItem::handler( - format!("Dock {}", position.to_label()), - move |cx| view.set_position(position, cx), + .constrained() + .with_height(style.icon_size) + .contained() + .with_style(style.container) + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, { + let tooltip_action = + tooltip_action.as_ref().map(|action| action.boxed_clone()); + move |_, this, cx| { + if let Some(tooltip_action) = &tooltip_action { + let window_id = cx.window_id(); + let view_id = this.workspace.id(); + let tooltip_action = tooltip_action.boxed_clone(); + cx.spawn(|_, mut cx| async move { + cx.dispatch_action( + window_id, + view_id, + &*tooltip_action, ) + .ok(); }) - .collect(); - menu.show(Default::default(), menu_corner, items, cx); - }) - } - }) - .with_tooltip::( - panel_ix, - tooltip, - tooltip_action, - tooltip_style.clone(), - cx, - ), - ) - .with_child(ChildView::new(&context_menu, cx)) + .detach(); + } + } + }) + .on_click(MouseButton::Right, { + let view = view.clone(); + let menu = context_menu.clone(); + move |_, _, cx| { + const POSITIONS: [DockPosition; 3] = [ + DockPosition::Left, + DockPosition::Right, + DockPosition::Bottom, + ]; + + menu.update(cx, |menu, cx| { + let items = POSITIONS + .into_iter() + .filter(|position| { + *position != dock_position + && view.position_is_valid(*position, cx) + }) + .map(|position| { + let view = view.clone(); + ContextMenuItem::handler( + format!("Dock {}", position.to_label()), + move |cx| view.set_position(position, cx), + ) + }) + .collect(); + menu.show(Default::default(), menu_corner, items, cx); + }) + } + }) + .with_tooltip::( + panel_ix, + tooltip, + tooltip_action, + tooltip_style.clone(), + cx, + ), + ) + .with_child(ChildView::new(&context_menu, cx)), + ) }, )) .contained() @@ -702,8 +705,8 @@ pub mod test { self.size = size; } - fn icon_path(&self) -> &'static str { - "icons/test_panel.svg" + fn icon_path(&self, _: &WindowContext) -> Option<&'static str> { + Some("icons/test_panel.svg") } fn icon_tooltip(&self) -> (String, Option>) { diff --git a/styles/src/style_tree/collab_panel.ts b/styles/src/style_tree/collab_panel.ts index f24468dca6..2c543356b0 100644 --- a/styles/src/style_tree/collab_panel.ts +++ b/styles/src/style_tree/collab_panel.ts @@ -67,6 +67,37 @@ export default function contacts_panel(): any { return { channel_modal: channel_modal(), + log_in_button: interactive({ + base: { + background: background(theme.middle), + border: border(theme.middle, "active"), + corner_radius: 4, + margin: { + top: 16, + left: 16, + right: 16, + }, + padding: { + top: 3, + bottom: 3, + left: 7, + right: 7, + }, + ...text(theme.middle, "sans", "default", { size: "sm" }), + }, + state: { + hovered: { + ...text(theme.middle, "sans", "default", { size: "sm" }), + background: background(theme.middle, "hovered"), + border: border(theme.middle, "active"), + }, + clicked: { + ...text(theme.middle, "sans", "default", { size: "sm" }), + background: background(theme.middle, "pressed"), + border: border(theme.middle, "active"), + }, + }, + }), background: background(layer), padding: { top: 12,