diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 14b8f464ca..636e519827 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -151,10 +151,10 @@ actions!( // ] // ); -// #[derive(Debug, Copy, Clone, PartialEq, Eq)] -// struct ChannelMoveClipboard { -// channel_id: ChannelId, -// } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +struct ChannelMoveClipboard { + channel_id: ChannelId, +} const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel"; @@ -168,10 +168,11 @@ use editor::Editor; use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt}; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ - actions, div, img, prelude::*, serde_json, Action, AppContext, AsyncWindowContext, - ClipboardItem, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement, - IntoElement, Model, ParentElement, Pixels, Point, PromptLevel, Render, RenderOnce, - SharedString, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, + actions, div, img, overlay, prelude::*, px, rems, serde_json, Action, AppContext, + AsyncWindowContext, ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle, Focusable, + FocusableView, InteractiveElement, IntoElement, Model, MouseDownEvent, ParentElement, Pixels, + Point, PromptLevel, Render, RenderOnce, SharedString, Styled, Subscription, Task, View, + ViewContext, VisualContext, WeakView, }; use project::Fs; use serde_derive::{Deserialize, Serialize}; @@ -286,7 +287,7 @@ pub struct CollabPanel { width: Option, fs: Arc, focus_handle: FocusHandle, - // channel_clipboard: Option, + channel_clipboard: Option, pending_serialization: Task>, context_menu: Option<(View, Point, Subscription)>, filter_editor: View, @@ -569,7 +570,7 @@ impl CollabPanel { let mut this = Self { width: None, focus_handle: cx.focus_handle(), - // channel_clipboard: None, + channel_clipboard: None, fs: workspace.app_state().fs.clone(), pending_serialization: Task::ready(None), context_menu: None, @@ -1665,46 +1666,44 @@ impl CollabPanel { // .into_any() // } - // fn has_subchannels(&self, ix: usize) -> bool { - // self.entries.get(ix).map_or(false, |entry| { - // if let ListEntry::Channel { has_children, .. } = entry { - // *has_children - // } else { - // false - // } - // }) - // } + fn has_subchannels(&self, ix: usize) -> bool { + self.entries.get(ix).map_or(false, |entry| { + if let ListEntry::Channel { has_children, .. } = entry { + *has_children + } else { + false + } + }) + } fn deploy_channel_context_menu( &mut self, position: Point, - channel: &Channel, + channel_id: ChannelId, ix: usize, cx: &mut ViewContext, ) { // self.context_menu_on_selected = position.is_none(); - // let clipboard_channel_name = self.channel_clipboard.as_ref().and_then(|clipboard| { - // self.channel_store - // .read(cx) - // .channel_for_id(clipboard.channel_id) - // .map(|channel| channel.name.clone()) - // }); - let this = cx.view(); - let has_subchannels = self.has_subchannels(ix); - let is_channel_collapsed = self.is_channel_collapsed(channel_id); + let clipboard_channel_name = self.channel_clipboard.as_ref().and_then(|clipboard| { + self.channel_store + .read(cx) + .channel_for_id(clipboard.channel_id) + .map(|channel| channel.name.clone()) + }); + let this = cx.view().clone(); - let menu = ContextMenu::build(cx, |context_menu, cx| { - if has_subchannels { - let expand_action_name = if is_channel_collapsed { + let context_menu = ContextMenu::build(cx, |mut context_menu, cx| { + if self.has_subchannels(ix) { + let expand_action_name = if self.is_channel_collapsed(channel_id) { "Expand Subchannels" } else { "Collapse Subchannels" }; context_menu = context_menu.entry( expand_action_name, - cx.handler_for(&this, |this, cx| { - this.toggle_channel_collapsed(channel.id, cx) + cx.handler_for(&this, move |this, cx| { + this.toggle_channel_collapsed(channel_id, cx) }), ); } @@ -1712,80 +1711,75 @@ impl CollabPanel { context_menu = context_menu .entry( "Open Notes", - cx.handler_for(&this, |this, cx| this.open_channel_notes(channel.id, cx)), + cx.handler_for(&this, move |this, cx| { + this.open_channel_notes(channel_id, cx) + }), ) .entry( "Open Chat", - cx.handler_for(&this, |this, cx| this.join_channel_chat(channel.id, cx)), + cx.handler_for(&this, move |this, cx| { + this.join_channel_chat(channel_id, cx) + }), ) .entry( "Copy Channel Link", - cx.handler_for(&this, |this, cx| this.copy_channel_link(channel.id, cx)), + cx.handler_for(&this, move |this, cx| { + this.copy_channel_link(channel_id, cx) + }), ); - if self.channel_store.read(cx).is_channel_admin(channel.id) { + if self.channel_store.read(cx).is_channel_admin(channel_id) { context_menu = context_menu .separator() .entry( "New Subchannel", - cx.handler_for(&this, |this, cx| this.new_subchannel(channel.id, cx)), + cx.handler_for(&this, move |this, cx| this.new_subchannel(channel_id, cx)), ) .entry( "Rename", - cx.handler_for(&this, |this, cx| this.rename_channel(channel.id, cx)), + cx.handler_for(&this, move |this, cx| this.rename_channel(channel_id, cx)), ) .entry( "Move this channel", - cx.handler_for(&this, |this, cx| this.start_move_channel(channel.id, cx)), + cx.handler_for(&this, move |this, cx| { + this.start_move_channel(channel_id, cx) + }), ); - // if let Some(channel_name) = clipboard_channel_name { - // items.push(ContextMenuItem::Separator); - // items.push(ContextMenuItem::action( - // format!("Move '#{}' here", channel_name), - // MoveChannel { to: channel.id }, - // )); - // } + if let Some(channel_name) = clipboard_channel_name { + context_menu = context_menu.separator().entry( + format!("Move '#{}' here", channel_name), + cx.handler_for(&this, move |this, cx| { + this.move_channel_on_clipboard(channel_id, cx) + }), + ); + } - // items.extend([ - // ContextMenuItem::Separator, - // ContextMenuItem::action( - // "Invite Members", - // InviteMembers { - // channel_id: channel.id, - // }, - // ), - // ContextMenuItem::action( - // "Manage Members", - // ManageMembers { - // channel_id: channel.id, - // }, - // ), - // ContextMenuItem::Separator, - // ContextMenuItem::action( - // "Delete", - // RemoveChannel { - // channel_id: channel.id, - // }, - // ), - // ]); + context_menu = context_menu + .separator() + .entry( + "Invite Members", + cx.handler_for(&this, move |this, cx| this.invite_members(channel_id, cx)), + ) + .entry( + "Manage Members", + cx.handler_for(&this, move |this, cx| this.manage_members(channel_id, cx)), + ) + .entry( + "Delete", + cx.handler_for(&this, move |this, cx| this.remove_channel(channel_id, cx)), + ); } - // context_menu.show( - // position.unwrap_or_default(), - // if self.context_menu_on_selected { - // gpui::elements::AnchorCorner::TopRight - // } else { - // gpui::elements::AnchorCorner::BottomLeft - // }, - // items, - // cx, - // ); - context_menu }); - self.context_menu = Some((menu, (), ())); + cx.focus_view(&context_menu); + let subscription = cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| { + this.context_menu.take(); + cx.notify(); + }); + self.context_menu = Some((context_menu, position, subscription)); cx.notify(); } @@ -2074,55 +2068,52 @@ impl CollabPanel { self.collapsed_channels .retain(|channel| *channel != channel_id); self.channel_editing_state = Some(ChannelEditingState::Create { - location: Some(action.location.to_owned()), + location: Some(channel_id), pending_name: None, }); self.update_entries(false, cx); self.select_channel_editor(); - cx.focus(self.channel_name_editor.as_any()); + cx.focus_view(&self.channel_name_editor); cx.notify(); } - // fn invite_members(&mut self, action: &InviteMembers, cx: &mut ViewContext) { - // self.show_channel_modal(action.channel_id, channel_modal::Mode::InviteMembers, cx); - // } + fn invite_members(&mut self, channel_id: ChannelId, cx: &mut ViewContext) { + todo!(); + // self.show_channel_modal(channel_id, channel_modal::Mode::InviteMembers, cx); + } - // fn manage_members(&mut self, action: &ManageMembers, cx: &mut ViewContext) { - // self.show_channel_modal(action.channel_id, channel_modal::Mode::ManageMembers, cx); - // } + fn manage_members(&mut self, channel_id: ChannelId, cx: &mut ViewContext) { + todo!(); + // self.show_channel_modal(channel_id, channel_modal::Mode::ManageMembers, cx); + } - // fn remove(&mut self, _: &Remove, cx: &mut ViewContext) { - // if let Some(channel) = self.selected_channel() { - // self.remove_channel(channel.id, cx) - // } - // } + fn remove_selected_channel(&mut self, _: &Remove, cx: &mut ViewContext) { + if let Some(channel) = self.selected_channel() { + self.remove_channel(channel.id, cx) + } + } - // fn rename_selected_channel(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext) { - // if let Some(channel) = self.selected_channel() { - // self.rename_channel( - // &RenameChannel { - // channel_id: channel.id, - // }, - // cx, - // ); - // } - // } + fn rename_selected_channel(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext) { + if let Some(channel) = self.selected_channel() { + self.rename_channel(channel.id, cx); + } + } - fn rename_channel(&mut self, action: &RenameChannel, cx: &mut ViewContext) { + fn rename_channel(&mut self, channel_id: ChannelId, cx: &mut ViewContext) { let channel_store = self.channel_store.read(cx); - if !channel_store.is_channel_admin(action.channel_id) { + if !channel_store.is_channel_admin(channel_id) { return; } - if let Some(channel) = channel_store.channel_for_id(action.channel_id).cloned() { + if let Some(channel) = channel_store.channel_for_id(channel_id).cloned() { self.channel_editing_state = Some(ChannelEditingState::Rename { - location: action.channel_id.to_owned(), + location: channel_id, pending_name: None, }); self.channel_name_editor.update(cx, |editor, cx| { editor.set_text(channel.name.clone(), cx); editor.select_all(&Default::default(), cx); }); - cx.focus(self.channel_name_editor.as_any()); + cx.focus_view(&self.channel_name_editor); self.update_entries(false, cx); self.select_channel_editor(); } @@ -2140,7 +2131,21 @@ impl CollabPanel { } } - fn open_channel_notes(&mut self, action: &OpenChannelNotes, cx: &mut ViewContext) { + fn move_channel_on_clipboard( + &mut self, + to_channel_id: ChannelId, + cx: &mut ViewContext, + ) { + if let Some(clipboard) = self.channel_clipboard.take() { + self.channel_store.update(cx, |channel_store, cx| { + channel_store + .move_channel(clipboard.channel_id, Some(to_channel_id), cx) + .detach_and_log_err(cx) + }) + } + } + + fn open_channel_notes(&mut self, channel_id: ChannelId, cx: &mut ViewContext) { if let Some(workspace) = self.workspace.upgrade() { todo!(); // ChannelView::open(action.channel_id, workspace, cx).detach(); @@ -2201,35 +2206,29 @@ impl CollabPanel { // self.remove_channel(action.channel_id, cx) // } - // fn remove_channel(&mut self, channel_id: ChannelId, cx: &mut ViewContext) { - // let channel_store = self.channel_store.clone(); - // if let Some(channel) = channel_store.read(cx).channel_for_id(channel_id) { - // let prompt_message = format!( - // "Are you sure you want to remove the channel \"{}\"?", - // channel.name - // ); - // let mut answer = - // cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]); - // let window = cx.window(); - // cx.spawn(|this, mut cx| async move { - // if answer.next().await == Some(0) { - // if let Err(e) = channel_store - // .update(&mut cx, |channels, _| channels.remove_channel(channel_id)) - // .await - // { - // window.prompt( - // PromptLevel::Info, - // &format!("Failed to remove channel: {}", e), - // &["Ok"], - // &mut cx, - // ); - // } - // this.update(&mut cx, |_, cx| cx.focus_self()).ok(); - // } - // }) - // .detach(); - // } - // } + fn remove_channel(&mut self, channel_id: ChannelId, cx: &mut ViewContext) { + let channel_store = self.channel_store.clone(); + if let Some(channel) = channel_store.read(cx).channel_for_id(channel_id) { + let prompt_message = format!( + "Are you sure you want to remove the channel \"{}\"?", + channel.name + ); + let mut answer = + cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]); + let window = cx.window(); + cx.spawn(|this, mut cx| async move { + if answer.await? == 0 { + channel_store + .update(&mut cx, |channels, _| channels.remove_channel(channel_id))? + .await + .notify_async_err(&mut cx); + this.update(&mut cx, |_, cx| cx.focus_self()).ok(); + } + anyhow::Ok(()) + }) + .detach(); + } + } // // Should move to the filter editor if clicking on it // // Should move selection to the channel editor if activating it @@ -2314,15 +2313,16 @@ impl CollabPanel { let Some(workspace) = self.workspace.upgrade() else { return; }; - cx.defer(move |cx| { + cx.window_context().defer(move |cx| { workspace.update(cx, |workspace, cx| { - if let Some(panel) = workspace.focus_panel::(cx) { - panel.update(cx, |panel, cx| { - panel - .select_channel(channel_id, None, cx) - .detach_and_log_err(cx); - }); - } + todo!(); + // if let Some(panel) = workspace.focus_panel::(cx) { + // panel.update(cx, |panel, cx| { + // panel + // .select_channel(channel_id, None, cx) + // .detach_and_log_err(cx); + // }); + // } }); }); } @@ -2354,37 +2354,41 @@ impl CollabPanel { fn render_signed_in(&mut self, cx: &mut ViewContext) -> List { let is_selected = false; // todo!() this.selection == Some(ix); - List::new().children(self.entries.clone().into_iter().map(|entry| { - match entry { - ListEntry::Header(section) => { - let is_collapsed = self.collapsed_sections.contains(§ion); - self.render_header(section, is_selected, is_collapsed, cx) - .into_any_element() - } - ListEntry::Contact { contact, calling } => self - .render_contact(&*contact, calling, is_selected, cx) - .into_any_element(), - ListEntry::ContactPlaceholder => self - .render_contact_placeholder(is_selected, cx) - .into_any_element(), - ListEntry::IncomingRequest(user) => self - .render_contact_request(user, true, is_selected, cx) - .into_any_element(), - ListEntry::OutgoingRequest(user) => self - .render_contact_request(user, false, is_selected, cx) - .into_any_element(), - ListEntry::Channel { - channel, - depth, - has_children, - } => self - .render_channel(&*channel, depth, has_children, is_selected, cx) - .into_any_element(), - ListEntry::ChannelEditor { depth } => { - self.render_channel_editor(depth, cx).into_any_element() - } - } - })) + List::new().children( + self.entries + .clone() + .into_iter() + .enumerate() + .map(|(ix, entry)| match entry { + ListEntry::Header(section) => { + let is_collapsed = self.collapsed_sections.contains(§ion); + self.render_header(section, is_selected, is_collapsed, cx) + .into_any_element() + } + ListEntry::Contact { contact, calling } => self + .render_contact(&*contact, calling, is_selected, cx) + .into_any_element(), + ListEntry::ContactPlaceholder => self + .render_contact_placeholder(is_selected, cx) + .into_any_element(), + ListEntry::IncomingRequest(user) => self + .render_contact_request(user, true, is_selected, cx) + .into_any_element(), + ListEntry::OutgoingRequest(user) => self + .render_contact_request(user, false, is_selected, cx) + .into_any_element(), + ListEntry::Channel { + channel, + depth, + has_children, + } => self + .render_channel(&*channel, depth, has_children, is_selected, ix, cx) + .into_any_element(), + ListEntry::ChannelEditor { depth } => { + self.render_channel_editor(depth, cx).into_any_element() + } + }), + ) } fn render_header( @@ -2713,6 +2717,7 @@ impl CollabPanel { depth: usize, has_children: bool, is_selected: bool, + ix: usize, cx: &mut ViewContext, ) -> impl IntoElement { let channel_id = channel.id; @@ -2769,6 +2774,7 @@ impl CollabPanel { div().group("").child( ListItem::new(channel_id as usize) .indent_level(depth) + .indent_step_size(cx.rem_size() * 14.0 / 16.0) // @todo()! @nate this is to step over the disclosure toggle .left_icon(if is_public { Icon::Public } else { Icon::Hash }) .selected(is_selected || is_active) .child( @@ -2829,14 +2835,14 @@ impl CollabPanel { .on_click(cx.listener(move |this, _, cx| { if this.drag_target_channel == ChannelDragTarget::None { if is_active { - this.open_channel_notes(&OpenChannelNotes { channel_id }, cx) + this.open_channel_notes(channel_id, cx) } else { this.join_channel(channel_id, cx) } } })) - .on_secondary_mouse_down(cx.listener(|this, _, cx| { - todo!() // open context menu + .on_secondary_mouse_down(cx.listener(move |this, event: &MouseDownEvent, cx| { + this.deploy_channel_context_menu(event.position, channel_id, ix, cx) })), ) @@ -3235,6 +3241,12 @@ impl Render for CollabPanel { el.child(self.render_signed_in(cx)) } }) + .children(self.context_menu.as_ref().map(|(menu, position, _)| { + overlay() + .position(*position) + .anchor(gpui::AnchorCorner::TopLeft) + .child(menu.clone()) + })) } } diff --git a/crates/project_panel2/src/project_panel.rs b/crates/project_panel2/src/project_panel.rs index e4846af76c..dc584d52ff 100644 --- a/crates/project_panel2/src/project_panel.rs +++ b/crates/project_panel2/src/project_panel.rs @@ -1480,7 +1480,7 @@ impl Render for ProjectPanel { .children(self.context_menu.as_ref().map(|(menu, position, _)| { overlay() .position(*position) - .anchor(gpui::AnchorCorner::BottomLeft) + .anchor(gpui::AnchorCorner::TopLeft) .child(menu.clone()) })) } else {