From 32367eba14f3b08464e2e39c7331d353b2e69a4b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 25 Oct 2023 15:39:02 +0200 Subject: [PATCH] Set up UI to allow dragging a channel to the root --- crates/channel/src/channel_store.rs | 2 +- crates/collab/src/db/queries/channels.rs | 24 ++--- crates/collab/src/db/tests/channel_tests.rs | 2 +- crates/collab/src/rpc.rs | 2 +- crates/collab/src/tests/channel_tests.rs | 6 +- crates/collab_ui/src/collab_panel.rs | 111 ++++++++++++++------ crates/rpc/proto/zed.proto | 2 +- crates/theme/src/theme.rs | 1 + styles/src/style_tree/collab_panel.ts | 10 +- styles/src/style_tree/search.ts | 5 +- 10 files changed, 107 insertions(+), 58 deletions(-) diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index 9757bb8092..efa05d51a9 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -501,7 +501,7 @@ impl ChannelStore { pub fn move_channel( &mut self, channel_id: ChannelId, - to: ChannelId, + to: Option, cx: &mut ModelContext, ) -> Task> { let client = self.client.clone(); diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index b65e677764..a5c6e4dcfc 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -1205,37 +1205,37 @@ impl Database { pub async fn move_channel( &self, channel_id: ChannelId, - new_parent_id: ChannelId, + new_parent_id: Option, admin_id: UserId, ) -> Result> { - // check you're an admin of source and target (and maybe current channel) - // change parent_path on current channel - // change parent_path on all children - self.transaction(|tx| async move { + let Some(new_parent_id) = new_parent_id else { + return Err(anyhow!("not supported"))?; + }; + let new_parent = self.get_channel_internal(new_parent_id, &*tx).await?; + self.check_user_is_channel_admin(&new_parent, admin_id, &*tx) + .await?; let channel = self.get_channel_internal(channel_id, &*tx).await?; self.check_user_is_channel_admin(&channel, admin_id, &*tx) .await?; - self.check_user_is_channel_admin(&new_parent, admin_id, &*tx) - .await?; let previous_participants = self .get_channel_participant_details_internal(&channel, &*tx) .await?; let old_path = format!("{}{}/", channel.parent_path, channel.id); - let new_parent_path = format!("{}{}/", new_parent.parent_path, new_parent_id); + let new_parent_path = format!("{}{}/", new_parent.parent_path, new_parent.id); let new_path = format!("{}{}/", new_parent_path, channel.id); if old_path == new_path { return Ok(None); } - let mut channel = channel.into_active_model(); - channel.parent_path = ActiveValue::Set(new_parent_path); - channel.save(&*tx).await?; + let mut model = channel.into_active_model(); + model.parent_path = ActiveValue::Set(new_parent_path); + model.update(&*tx).await?; let descendent_ids = ChannelId::find_by_statement::(Statement::from_sql_and_values( @@ -1250,7 +1250,7 @@ impl Database { .all(&*tx) .await?; - let participants_to_update: HashMap = self + let participants_to_update: HashMap<_, _> = self .participants_to_notify_for_channel_change(&new_parent, &*tx) .await? .into_iter() diff --git a/crates/collab/src/db/tests/channel_tests.rs b/crates/collab/src/db/tests/channel_tests.rs index 936765b8c9..0d486003bc 100644 --- a/crates/collab/src/db/tests/channel_tests.rs +++ b/crates/collab/src/db/tests/channel_tests.rs @@ -424,7 +424,7 @@ async fn test_db_channel_moving_bugs(db: &Arc) { // Move to same parent should be a no-op assert!(db - .move_channel(projects_id, zed_id, user_id) + .move_channel(projects_id, Some(zed_id), user_id) .await .unwrap() .is_none()); diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 01e8530e67..a0ec7da392 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -2476,7 +2476,7 @@ async fn move_channel( session: Session, ) -> Result<()> { let channel_id = ChannelId::from_proto(request.channel_id); - let to = ChannelId::from_proto(request.to); + let to = request.to.map(ChannelId::from_proto); let result = session .db() diff --git a/crates/collab/src/tests/channel_tests.rs b/crates/collab/src/tests/channel_tests.rs index d2c5e1cec3..a33ded6492 100644 --- a/crates/collab/src/tests/channel_tests.rs +++ b/crates/collab/src/tests/channel_tests.rs @@ -1016,7 +1016,7 @@ async fn test_channel_link_notifications( client_a .channel_store() .update(cx_a, |channel_store, cx| { - channel_store.move_channel(vim_channel, active_channel, cx) + channel_store.move_channel(vim_channel, Some(active_channel), cx) }) .await .unwrap(); @@ -1051,7 +1051,7 @@ async fn test_channel_link_notifications( client_a .channel_store() .update(cx_a, |channel_store, cx| { - channel_store.move_channel(helix_channel, vim_channel, cx) + channel_store.move_channel(helix_channel, Some(vim_channel), cx) }) .await .unwrap(); @@ -1424,7 +1424,7 @@ async fn test_channel_moving( client_a .channel_store() .update(cx_a, |channel_store, cx| { - channel_store.move_channel(channel_d_id, channel_b_id, cx) + channel_store.move_channel(channel_d_id, Some(channel_b_id), cx) }) .await .unwrap(); diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 16f8fb5d02..8d68ee12c0 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -226,7 +226,7 @@ pub fn init(cx: &mut AppContext) { panel .channel_store .update(cx, |channel_store, cx| { - channel_store.move_channel(clipboard.channel_id, selected_channel.id, cx) + channel_store.move_channel(clipboard.channel_id, Some(selected_channel.id), cx) }) .detach_and_log_err(cx) }, @@ -237,7 +237,7 @@ pub fn init(cx: &mut AppContext) { if let Some(clipboard) = panel.channel_clipboard.take() { panel.channel_store.update(cx, |channel_store, cx| { channel_store - .move_channel(clipboard.channel_id, action.to, cx) + .move_channel(clipboard.channel_id, Some(action.to), cx) .detach_and_log_err(cx) }) } @@ -287,11 +287,18 @@ pub struct CollabPanel { subscriptions: Vec, collapsed_sections: Vec
, collapsed_channels: Vec, - drag_target_channel: Option, + drag_target_channel: ChannelDragTarget, workspace: WeakViewHandle, context_menu_on_selected: bool, } +#[derive(PartialEq, Eq)] +enum ChannelDragTarget { + None, + Root, + Channel(ChannelId), +} + #[derive(Serialize, Deserialize)] struct SerializedCollabPanel { width: Option, @@ -577,7 +584,7 @@ impl CollabPanel { workspace: workspace.weak_handle(), client: workspace.app_state().client.clone(), context_menu_on_selected: true, - drag_target_channel: None, + drag_target_channel: ChannelDragTarget::None, list_state, }; @@ -1450,6 +1457,7 @@ impl CollabPanel { let mut channel_link = None; let mut channel_tooltip_text = None; let mut channel_icon = None; + let mut is_dragged_over = false; let text = match section { Section::ActiveCall => { @@ -1533,26 +1541,37 @@ impl CollabPanel { cx, ), ), - Section::Channels => Some( - MouseEventHandler::new::(0, cx, |state, _| { - render_icon_button( - theme - .collab_panel - .add_contact_button - .style_for(is_selected, state), - "icons/plus.svg", - ) - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, |_, this, cx| this.new_root_channel(cx)) - .with_tooltip::( - 0, - "Create a channel", - None, - tooltip_style.clone(), - cx, - ), - ), + Section::Channels => { + if cx + .global::>() + .currently_dragged::(cx.window()) + .is_some() + && self.drag_target_channel == ChannelDragTarget::Root + { + is_dragged_over = true; + } + + Some( + MouseEventHandler::new::(0, cx, |state, _| { + render_icon_button( + theme + .collab_panel + .add_contact_button + .style_for(is_selected, state), + "icons/plus.svg", + ) + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, |_, this, cx| this.new_root_channel(cx)) + .with_tooltip::( + 0, + "Create a channel", + None, + tooltip_style.clone(), + cx, + ), + ) + } _ => None, }; @@ -1623,9 +1642,37 @@ impl CollabPanel { .constrained() .with_height(theme.collab_panel.row_height) .contained() - .with_style(header_style.container) + .with_style(if is_dragged_over { + theme.collab_panel.dragged_over_header + } else { + header_style.container + }) }); + result = result + .on_move(move |_, this, cx| { + if cx + .global::>() + .currently_dragged::(cx.window()) + .is_some() + { + this.drag_target_channel = ChannelDragTarget::Root; + cx.notify() + } + }) + .on_up(MouseButton::Left, move |_, this, cx| { + if let Some((_, dragged_channel)) = cx + .global::>() + .currently_dragged::(cx.window()) + { + this.channel_store + .update(cx, |channel_store, cx| { + channel_store.move_channel(dragged_channel.id, None, cx) + }) + .detach_and_log_err(cx) + } + }); + if can_collapse { result = result .with_cursor_style(CursorStyle::PointingHand) @@ -1917,13 +1964,7 @@ impl CollabPanel { .global::>() .currently_dragged::(cx.window()) .is_some() - && self - .drag_target_channel - .as_ref() - .filter(|channel_id| { - channel.parent_path.contains(channel_id) || channel.id == **channel_id - }) - .is_some() + && self.drag_target_channel == ChannelDragTarget::Channel(channel_id) { is_dragged_over = true; } @@ -2126,7 +2167,7 @@ impl CollabPanel { ) }) .on_click(MouseButton::Left, move |_, this, cx| { - if this.drag_target_channel.take().is_none() { + if this.drag_target_channel == ChannelDragTarget::None { if is_active { this.open_channel_notes(&OpenChannelNotes { channel_id }, cx) } else { @@ -2147,7 +2188,7 @@ impl CollabPanel { { this.channel_store .update(cx, |channel_store, cx| { - channel_store.move_channel(dragged_channel.id, channel_id, cx) + channel_store.move_channel(dragged_channel.id, Some(channel_id), cx) }) .detach_and_log_err(cx) } @@ -2160,7 +2201,7 @@ impl CollabPanel { .currently_dragged::(cx.window()) { if channel.id != dragged_channel.id { - this.drag_target_channel = Some(channel.id); + this.drag_target_channel = ChannelDragTarget::Channel(channel.id); } cx.notify() } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index fddbf1e50d..206777879b 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -1130,7 +1130,7 @@ message GetChannelMessagesById { message MoveChannel { uint64 channel_id = 1; - uint64 to = 2; + optional uint64 to = 2; } message JoinChannelBuffer { diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 3f4264886f..e4b8c02eca 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -250,6 +250,7 @@ pub struct CollabPanel { pub add_contact_button: Toggleable>, pub add_channel_button: Toggleable>, pub header_row: ContainedText, + pub dragged_over_header: ContainerStyle, pub subheader_row: Toggleable>, pub leave_call: Interactive, pub contact_row: Toggleable>, diff --git a/styles/src/style_tree/collab_panel.ts b/styles/src/style_tree/collab_panel.ts index 2a7702842a..272b6055ed 100644 --- a/styles/src/style_tree/collab_panel.ts +++ b/styles/src/style_tree/collab_panel.ts @@ -210,6 +210,14 @@ export default function contacts_panel(): any { right: SPACING, }, }, + dragged_over_header: { + margin: { top: SPACING }, + padding: { + left: SPACING, + right: SPACING, + }, + background: background(layer, "hovered"), + }, subheader_row, leave_call: interactive({ base: { @@ -279,7 +287,7 @@ export default function contacts_panel(): any { margin: { left: CHANNEL_SPACING, }, - } + }, }, list_empty_label_container: { margin: { diff --git a/styles/src/style_tree/search.ts b/styles/src/style_tree/search.ts index b0ac023c09..2317108bde 100644 --- a/styles/src/style_tree/search.ts +++ b/styles/src/style_tree/search.ts @@ -2,7 +2,6 @@ import { with_opacity } from "../theme/color" import { background, border, foreground, text } from "./components" import { interactive, toggleable } from "../element" import { useTheme } from "../theme" -import { text_button } from "../component/text_button" const search_results = () => { const theme = useTheme() @@ -36,7 +35,7 @@ export default function search(): any { left: 10, right: 4, }, - margin: { right: SEARCH_ROW_SPACING } + margin: { right: SEARCH_ROW_SPACING }, } const include_exclude_editor = { @@ -378,7 +377,7 @@ export default function search(): any { modes_container: { padding: { right: SEARCH_ROW_SPACING, - } + }, }, replace_icon: { icon: {