Channel Context Menu

This commit is contained in:
Conrad Irwin 2023-11-29 11:39:26 -07:00
parent ee260a5e24
commit 41e7653906
2 changed files with 195 additions and 183 deletions

View file

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

View file

@ -1480,7 +1480,7 @@ impl Render for ProjectPanel {
.children(self.context_menu.as_ref().map(|(menu, position, _)| { .children(self.context_menu.as_ref().map(|(menu, position, _)| {
overlay() overlay()
.position(*position) .position(*position)
.anchor(gpui::AnchorCorner::BottomLeft) .anchor(gpui::AnchorCorner::TopLeft)
.child(menu.clone()) .child(menu.clone())
})) }))
} else { } else {