diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index b7785b0f7f..a0d80d4b94 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -473,8 +473,11 @@ impl Database { ) -> Result { self.transaction(|tx| async move { let channel = self.get_channel_internal(channel_id, &*tx).await?; - self.check_user_is_channel_admin(&channel, admin_id, &*tx) - .await?; + + if member_id != admin_id { + self.check_user_is_channel_admin(&channel, admin_id, &*tx) + .await?; + } let result = channel_member::Entity::delete_many() .filter( diff --git a/crates/collab/src/tests/channel_tests.rs b/crates/collab/src/tests/channel_tests.rs index 950d096df2..87987f7602 100644 --- a/crates/collab/src/tests/channel_tests.rs +++ b/crates/collab/src/tests/channel_tests.rs @@ -1306,6 +1306,28 @@ async fn test_invite_access( }) } +#[gpui::test] +async fn test_leave_channel(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { + let (_server, client_a, client_b, channel_id) = TestServer::start2(cx_a, cx_b).await; + + client_b + .channel_store() + .update(cx_b, |channel_store, cx| { + channel_store.remove_member(channel_id, client_b.user_id().unwrap(), cx) + }) + .await + .unwrap(); + + cx_a.run_until_parked(); + + assert_eq!( + client_b + .channel_store() + .read_with(cx_b, |store, _| store.channels().count()), + 0 + ); +} + #[gpui::test] async fn test_channel_moving( executor: BackgroundExecutor, diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 584de35e29..f4ec70d0a9 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -1575,7 +1575,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut #[gpui::test] async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { - let (client_a, client_b, channel_id) = TestServer::start2(cx_a, cx_b).await; + let (_, client_a, client_b, channel_id) = TestServer::start2(cx_a, cx_b).await; let (workspace_a, cx_a) = client_a.build_test_workspace(cx_a).await; client_a diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 009561d9ae..757add782c 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -118,7 +118,7 @@ impl TestServer { pub async fn start2( cx_a: &mut TestAppContext, cx_b: &mut TestAppContext, - ) -> (TestClient, TestClient, u64) { + ) -> (TestServer, TestClient, TestClient, u64) { let mut server = Self::start(cx_a.executor()).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; @@ -127,7 +127,7 @@ impl TestServer { .await; cx_a.run_until_parked(); - (client_a, client_b, channel_id) + (server, client_a, client_b, channel_id) } pub async fn start1<'a>(cx: &'a mut TestAppContext) -> TestClient { diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 8a552224ca..0d743f1e15 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -1122,7 +1122,9 @@ impl CollabPanel { }), ); + let mut has_destructive_actions = false; if self.channel_store.read(cx).is_channel_admin(channel_id) { + has_destructive_actions = true; context_menu = context_menu .separator() .entry( @@ -1194,6 +1196,17 @@ impl CollabPanel { ); } + if self.channel_store.read(cx).is_root_channel(channel_id) { + if !has_destructive_actions { + context_menu = context_menu.separator() + } + context_menu = context_menu.entry( + "Leave Channel", + None, + cx.handler_for(&this, move |this, cx| this.leave_channel(channel_id, cx)), + ); + } + context_menu }); @@ -1667,6 +1680,34 @@ impl CollabPanel { .detach(); } + fn leave_channel(&self, channel_id: ChannelId, cx: &mut ViewContext) { + let Some(user_id) = self.user_store.read(cx).current_user().map(|u| u.id) else { + return; + }; + let Some(channel) = self.channel_store.read(cx).channel_for_id(channel_id) else { + return; + }; + let prompt_message = format!("Are you sure you want to leave \"#{}\"?", channel.name); + let answer = cx.prompt( + PromptLevel::Warning, + &prompt_message, + None, + &["Leave", "Cancel"], + ); + cx.spawn(|this, mut cx| async move { + if answer.await? != 0 { + return Ok(()); + } + this.update(&mut cx, |this, cx| { + this.channel_store.update(cx, |channel_store, cx| { + channel_store.remove_member(channel_id, user_id, cx) + }) + })? + .await + }) + .detach_and_prompt_err("Failed to leave channel", cx, |_, _| None) + } + 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) {