diff --git a/crates/client/src/channel_store.rs b/crates/client/src/channel_store.rs index 8fb005a262..b9aa4268cd 100644 --- a/crates/client/src/channel_store.rs +++ b/crates/client/src/channel_store.rs @@ -16,6 +16,7 @@ pub struct ChannelStore { channels: Vec>, channel_invitations: Vec>, channel_participants: HashMap>>, + channels_with_admin_privileges: HashSet, outgoing_invites: HashSet<(ChannelId, UserId)>, client: Arc, user_store: ModelHandle, @@ -28,7 +29,6 @@ pub struct Channel { pub id: ChannelId, pub name: String, pub parent_id: Option, - pub user_is_admin: bool, pub depth: usize, } @@ -79,6 +79,7 @@ impl ChannelStore { channels: vec![], channel_invitations: vec![], channel_participants: Default::default(), + channels_with_admin_privileges: Default::default(), outgoing_invites: Default::default(), client, user_store, @@ -100,17 +101,18 @@ impl ChannelStore { } pub fn is_user_admin(&self, mut channel_id: ChannelId) -> bool { - while let Some(channel) = self.channel_for_id(channel_id) { - if channel.user_is_admin { + loop { + if self.channels_with_admin_privileges.contains(&channel_id) { return true; } - if let Some(parent_id) = channel.parent_id { - channel_id = parent_id; - } else { - break; + if let Some(channel) = self.channel_for_id(channel_id) { + if let Some(parent_id) = channel.parent_id { + channel_id = parent_id; + continue; + } } + return false; } - false } pub fn channel_participants(&self, channel_id: ChannelId) -> &[Arc] { @@ -228,6 +230,22 @@ impl ChannelStore { }) } + pub fn rename( + &mut self, + channel_id: ChannelId, + new_name: &str, + cx: &mut ModelContext, + ) -> Task> { + let client = self.client.clone(); + let name = new_name.to_string(); + cx.spawn(|_this, _cx| async move { + client + .request(proto::RenameChannel { channel_id, name }) + .await?; + Ok(()) + }) + } + pub fn respond_to_channel_invite( &mut self, channel_id: ChannelId, @@ -315,6 +333,8 @@ impl ChannelStore { .retain(|channel| !payload.remove_channel_invitations.contains(&channel.id)); self.channel_participants .retain(|channel_id, _| !payload.remove_channels.contains(channel_id)); + self.channels_with_admin_privileges + .retain(|channel_id| !payload.remove_channels.contains(channel_id)); for channel in payload.channel_invitations { if let Some(existing_channel) = self @@ -324,7 +344,6 @@ impl ChannelStore { { let existing_channel = Arc::make_mut(existing_channel); existing_channel.name = channel.name; - existing_channel.user_is_admin = channel.user_is_admin; continue; } @@ -333,7 +352,6 @@ impl ChannelStore { Arc::new(Channel { id: channel.id, name: channel.name, - user_is_admin: false, parent_id: None, depth: 0, }), @@ -344,7 +362,6 @@ impl ChannelStore { if let Some(existing_channel) = self.channels.iter_mut().find(|c| c.id == channel.id) { let existing_channel = Arc::make_mut(existing_channel); existing_channel.name = channel.name; - existing_channel.user_is_admin = channel.user_is_admin; continue; } @@ -357,7 +374,6 @@ impl ChannelStore { Arc::new(Channel { id: channel.id, name: channel.name, - user_is_admin: channel.user_is_admin, parent_id: Some(parent_id), depth, }), @@ -369,7 +385,6 @@ impl ChannelStore { Arc::new(Channel { id: channel.id, name: channel.name, - user_is_admin: channel.user_is_admin, parent_id: None, depth: 0, }), @@ -377,6 +392,16 @@ impl ChannelStore { } } + for permission in payload.channel_permissions { + if permission.is_admin { + self.channels_with_admin_privileges + .insert(permission.channel_id); + } else { + self.channels_with_admin_privileges + .remove(&permission.channel_id); + } + } + let mut all_user_ids = Vec::new(); let channel_participants = payload.channel_participants; for entry in &channel_participants { diff --git a/crates/client/src/channel_store_tests.rs b/crates/client/src/channel_store_tests.rs index 69d5fed70d..4ee54d3eca 100644 --- a/crates/client/src/channel_store_tests.rs +++ b/crates/client/src/channel_store_tests.rs @@ -18,13 +18,11 @@ fn test_update_channels(cx: &mut AppContext) { id: 1, name: "b".to_string(), parent_id: None, - user_is_admin: true, }, proto::Channel { id: 2, name: "a".to_string(), parent_id: None, - user_is_admin: false, }, ], ..Default::default() @@ -49,13 +47,11 @@ fn test_update_channels(cx: &mut AppContext) { id: 3, name: "x".to_string(), parent_id: Some(1), - user_is_admin: false, }, proto::Channel { id: 4, name: "y".to_string(), parent_id: Some(2), - user_is_admin: false, }, ], ..Default::default() @@ -92,7 +88,7 @@ fn assert_channels( let actual = store .channels() .iter() - .map(|c| (c.depth, c.name.as_str(), c.user_is_admin)) + .map(|c| (c.depth, c.name.as_str(), store.is_user_admin(c.id))) .collect::>(); assert_eq!(actual, expected_channels); }); diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index d830938497..8faea0e402 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -3155,7 +3155,7 @@ impl Database { live_kit_room: &str, creator_id: UserId, ) -> Result { - let name = name.trim().trim_start_matches('#'); + let name = Self::sanitize_channel_name(name)?; self.transaction(move |tx| async move { if let Some(parent) = parent { self.check_user_is_channel_admin(parent, creator_id, &*tx) @@ -3303,6 +3303,39 @@ impl Database { .await } + fn sanitize_channel_name(name: &str) -> Result<&str> { + let new_name = name.trim().trim_start_matches('#'); + if new_name == "" { + Err(anyhow!("channel name can't be blank"))?; + } + Ok(new_name) + } + + pub async fn rename_channel( + &self, + channel_id: ChannelId, + user_id: UserId, + new_name: &str, + ) -> Result { + self.transaction(move |tx| async move { + let new_name = Self::sanitize_channel_name(new_name)?.to_string(); + + self.check_user_is_channel_admin(channel_id, user_id, &*tx) + .await?; + + channel::ActiveModel { + id: ActiveValue::Unchanged(channel_id), + name: ActiveValue::Set(new_name.clone()), + ..Default::default() + } + .update(&*tx) + .await?; + + Ok(new_name) + }) + .await + } + pub async fn respond_to_channel_invite( &self, channel_id: ChannelId, @@ -3400,7 +3433,6 @@ impl Database { .map(|channel| Channel { id: channel.id, name: channel.name, - user_is_admin: false, parent_id: None, }) .collect(); @@ -3426,10 +3458,6 @@ impl Database { .all(&*tx) .await?; - let admin_channel_ids = channel_memberships - .iter() - .filter_map(|m| m.admin.then_some(m.channel_id)) - .collect::>(); let parents_by_child_id = self .get_channel_descendants(channel_memberships.iter().map(|m| m.channel_id), &*tx) .await?; @@ -3445,7 +3473,6 @@ impl Database { channels.push(Channel { id: row.id, name: row.name, - user_is_admin: admin_channel_ids.contains(&row.id), parent_id: parents_by_child_id.get(&row.id).copied().flatten(), }); } @@ -3758,15 +3785,14 @@ impl Database { .one(&*tx) .await?; - let (user_is_admin, is_accepted) = channel_membership - .map(|membership| (membership.admin, membership.accepted)) - .unwrap_or((false, false)); + let is_accepted = channel_membership + .map(|membership| membership.accepted) + .unwrap_or(false); Ok(Some(( Channel { id: channel.id, name: channel.name, - user_is_admin, parent_id: None, }, is_accepted, @@ -4043,7 +4069,6 @@ pub struct NewUserResult { pub struct Channel { pub id: ChannelId, pub name: String, - pub user_is_admin: bool, pub parent_id: Option, } diff --git a/crates/collab/src/db/tests.rs b/crates/collab/src/db/tests.rs index cdcde3332c..a659f3d164 100644 --- a/crates/collab/src/db/tests.rs +++ b/crates/collab/src/db/tests.rs @@ -962,43 +962,36 @@ test_both_dbs!(test_channels_postgres, test_channels_sqlite, db, { id: zed_id, name: "zed".to_string(), parent_id: None, - user_is_admin: true, }, Channel { id: crdb_id, name: "crdb".to_string(), parent_id: Some(zed_id), - user_is_admin: true, }, Channel { id: livestreaming_id, name: "livestreaming".to_string(), parent_id: Some(zed_id), - user_is_admin: true, }, Channel { id: replace_id, name: "replace".to_string(), parent_id: Some(zed_id), - user_is_admin: true, }, Channel { id: rust_id, name: "rust".to_string(), parent_id: None, - user_is_admin: true, }, Channel { id: cargo_id, name: "cargo".to_string(), parent_id: Some(rust_id), - user_is_admin: true, }, Channel { id: cargo_ra_id, name: "cargo-ra".to_string(), parent_id: Some(cargo_id), - user_is_admin: true, } ] ); @@ -1011,25 +1004,21 @@ test_both_dbs!(test_channels_postgres, test_channels_sqlite, db, { id: zed_id, name: "zed".to_string(), parent_id: None, - user_is_admin: false, }, Channel { id: crdb_id, name: "crdb".to_string(), parent_id: Some(zed_id), - user_is_admin: false, }, Channel { id: livestreaming_id, name: "livestreaming".to_string(), parent_id: Some(zed_id), - user_is_admin: false, }, Channel { id: replace_id, name: "replace".to_string(), parent_id: Some(zed_id), - user_is_admin: false, }, ] ); @@ -1048,25 +1037,21 @@ test_both_dbs!(test_channels_postgres, test_channels_sqlite, db, { id: zed_id, name: "zed".to_string(), parent_id: None, - user_is_admin: true, }, Channel { id: crdb_id, name: "crdb".to_string(), parent_id: Some(zed_id), - user_is_admin: false, }, Channel { id: livestreaming_id, name: "livestreaming".to_string(), parent_id: Some(zed_id), - user_is_admin: false, }, Channel { id: replace_id, name: "replace".to_string(), parent_id: Some(zed_id), - user_is_admin: false, }, ] ); @@ -1296,6 +1281,66 @@ test_both_dbs!( } ); +test_both_dbs!( + test_channel_renames_postgres, + test_channel_renames_sqlite, + db, + { + db.create_server("test").await.unwrap(); + + let user_1 = db + .create_user( + "user1@example.com", + false, + NewUserParams { + github_login: "user1".into(), + github_user_id: 5, + invite_count: 0, + }, + ) + .await + .unwrap() + .user_id; + + let user_2 = db + .create_user( + "user2@example.com", + false, + NewUserParams { + github_login: "user2".into(), + github_user_id: 6, + invite_count: 0, + }, + ) + .await + .unwrap() + .user_id; + + let zed_id = db.create_root_channel("zed", "1", user_1).await.unwrap(); + + db.rename_channel(zed_id, user_1, "#zed-archive") + .await + .unwrap(); + + let zed_archive_id = zed_id; + + let (channel, _) = db + .get_channel(zed_archive_id, user_1) + .await + .unwrap() + .unwrap(); + assert_eq!(channel.name, "zed-archive"); + + let non_permissioned_rename = db + .rename_channel(zed_archive_id, user_2, "hacked-lol") + .await; + assert!(non_permissioned_rename.is_err()); + + let bad_name_rename = db.rename_channel(zed_id, user_1, "#").await; + assert!(bad_name_rename.is_err()) + } +); + #[gpui::test] async fn test_multiple_signup_overwrite() { let test_db = TestDb::postgres(build_background_executor()); diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index a24db6be81..0f52c8c03a 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -247,6 +247,7 @@ impl Server { .add_request_handler(invite_channel_member) .add_request_handler(remove_channel_member) .add_request_handler(set_channel_member_admin) + .add_request_handler(rename_channel) .add_request_handler(get_channel_members) .add_request_handler(respond_to_channel_invite) .add_request_handler(join_channel) @@ -2151,7 +2152,6 @@ async fn create_channel( id: id.to_proto(), name: request.name, parent_id: request.parent_id, - user_is_admin: false, }); let user_ids_to_notify = if let Some(parent_id) = parent_id { @@ -2165,7 +2165,10 @@ async fn create_channel( for connection_id in connection_pool.user_connection_ids(user_id) { let mut update = update.clone(); if user_id == session.user_id { - update.channels[0].user_is_admin = true; + update.channel_permissions.push(proto::ChannelPermission { + channel_id: id.to_proto(), + is_admin: true, + }); } session.peer.send(connection_id, update)?; } @@ -2224,7 +2227,6 @@ async fn invite_channel_member( id: channel.id.to_proto(), name: channel.name, parent_id: None, - user_is_admin: false, }); for connection_id in session .connection_pool() @@ -2283,18 +2285,9 @@ async fn set_channel_member_admin( let mut update = proto::UpdateChannels::default(); if has_accepted { - update.channels.push(proto::Channel { - id: channel.id.to_proto(), - name: channel.name, - parent_id: None, - user_is_admin: request.admin, - }); - } else { - update.channel_invitations.push(proto::Channel { - id: channel.id.to_proto(), - name: channel.name, - parent_id: None, - user_is_admin: request.admin, + update.channel_permissions.push(proto::ChannelPermission { + channel_id: channel.id.to_proto(), + is_admin: request.admin, }); } @@ -2310,6 +2303,38 @@ async fn set_channel_member_admin( Ok(()) } +async fn rename_channel( + request: proto::RenameChannel, + response: Response, + session: Session, +) -> Result<()> { + let db = session.db().await; + let channel_id = ChannelId::from_proto(request.channel_id); + let new_name = db + .rename_channel(channel_id, session.user_id, &request.name) + .await?; + + response.send(proto::Ack {})?; + + let mut update = proto::UpdateChannels::default(); + update.channels.push(proto::Channel { + id: request.channel_id, + name: new_name, + parent_id: None, + }); + + let member_ids = db.get_channel_members(channel_id).await?; + + let connection_pool = session.connection_pool().await; + for member_id in member_ids { + for connection_id in connection_pool.user_connection_ids(member_id) { + session.peer.send(connection_id, update.clone())?; + } + } + + Ok(()) +} + async fn get_channel_members( request: proto::GetChannelMembers, response: Response, @@ -2345,7 +2370,6 @@ async fn respond_to_channel_invite( .extend(channels.into_iter().map(|channel| proto::Channel { id: channel.id.to_proto(), name: channel.name, - user_is_admin: channel.user_is_admin, parent_id: channel.parent_id.map(ChannelId::to_proto), })); update @@ -2505,7 +2529,6 @@ fn build_initial_channels_update( update.channels.push(proto::Channel { id: channel.id.to_proto(), name: channel.name, - user_is_admin: channel.user_is_admin, parent_id: channel.parent_id.map(|id| id.to_proto()), }); } @@ -2523,7 +2546,6 @@ fn build_initial_channels_update( update.channel_invitations.push(proto::Channel { id: channel.id.to_proto(), name: channel.name, - user_is_admin: false, parent_id: None, }); } diff --git a/crates/collab/src/tests/channel_tests.rs b/crates/collab/src/tests/channel_tests.rs index 9723b18394..b2e9cae08a 100644 --- a/crates/collab/src/tests/channel_tests.rs +++ b/crates/collab/src/tests/channel_tests.rs @@ -40,14 +40,12 @@ async fn test_core_channels( id: channel_a_id, name: "channel-a".to_string(), parent_id: None, - user_is_admin: true, depth: 0, }), Arc::new(Channel { id: channel_b_id, name: "channel-b".to_string(), parent_id: Some(channel_a_id), - user_is_admin: true, depth: 1, }) ] @@ -82,7 +80,6 @@ async fn test_core_channels( id: channel_a_id, name: "channel-a".to_string(), parent_id: None, - user_is_admin: false, depth: 0, })] ) @@ -131,14 +128,13 @@ async fn test_core_channels( id: channel_a_id, name: "channel-a".to_string(), parent_id: None, - user_is_admin: false, + depth: 0, }), Arc::new(Channel { id: channel_b_id, name: "channel-b".to_string(), parent_id: Some(channel_a_id), - user_is_admin: false, depth: 1, }) ] @@ -162,21 +158,18 @@ async fn test_core_channels( id: channel_a_id, name: "channel-a".to_string(), parent_id: None, - user_is_admin: false, depth: 0, }), Arc::new(Channel { id: channel_b_id, name: "channel-b".to_string(), parent_id: Some(channel_a_id), - user_is_admin: false, depth: 1, }), Arc::new(Channel { id: channel_c_id, name: "channel-c".to_string(), parent_id: Some(channel_b_id), - user_is_admin: false, depth: 2, }), ] @@ -204,21 +197,18 @@ async fn test_core_channels( id: channel_a_id, name: "channel-a".to_string(), parent_id: None, - user_is_admin: true, depth: 0, }), Arc::new(Channel { id: channel_b_id, name: "channel-b".to_string(), parent_id: Some(channel_a_id), - user_is_admin: false, depth: 1, }), Arc::new(Channel { id: channel_c_id, name: "channel-c".to_string(), parent_id: Some(channel_b_id), - user_is_admin: false, depth: 2, }), ] @@ -244,7 +234,7 @@ async fn test_core_channels( id: channel_a_id, name: "channel-a".to_string(), parent_id: None, - user_is_admin: true, + depth: 0, })] ) @@ -256,7 +246,7 @@ async fn test_core_channels( id: channel_a_id, name: "channel-a".to_string(), parent_id: None, - user_is_admin: true, + depth: 0, })] ) @@ -281,7 +271,6 @@ async fn test_core_channels( id: channel_a_id, name: "channel-a".to_string(), parent_id: None, - user_is_admin: true, depth: 0, })] ) @@ -395,7 +384,6 @@ async fn test_channel_room( id: zed_id, name: "zed".to_string(), parent_id: None, - user_is_admin: false, depth: 0, })] ) @@ -617,7 +605,7 @@ async fn test_permissions_update_while_invited( id: rust_id, name: "rust".to_string(), parent_id: None, - user_is_admin: false, + depth: 0, })], ); @@ -643,7 +631,7 @@ async fn test_permissions_update_while_invited( id: rust_id, name: "rust".to_string(), parent_id: None, - user_is_admin: true, + depth: 0, })], ); @@ -651,3 +639,59 @@ async fn test_permissions_update_while_invited( assert_eq!(channels.channels(), &[],); }); } + +#[gpui::test] +async fn test_channel_rename( + deterministic: Arc, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + deterministic.forbid_parking(); + let mut server = TestServer::start(&deterministic).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + + let rust_id = server + .make_channel("rust", (&client_a, cx_a), &mut [(&client_b, cx_b)]) + .await; + + // Rename the channel + client_a + .channel_store() + .update(cx_a, |channel_store, cx| { + channel_store.rename(rust_id, "#rust-archive", cx) + }) + .await + .unwrap(); + + let rust_archive_id = rust_id; + deterministic.run_until_parked(); + + // Client A sees the channel with its new name. + client_a.channel_store().read_with(cx_a, |channels, _| { + assert_eq!( + channels.channels(), + &[Arc::new(Channel { + id: rust_archive_id, + name: "rust-archive".to_string(), + parent_id: None, + + depth: 0, + })], + ); + }); + + // Client B sees the channel with its new name. + client_b.channel_store().read_with(cx_b, |channels, _| { + assert_eq!( + channels.channels(), + &[Arc::new(Channel { + id: rust_archive_id, + name: "rust-archive".to_string(), + parent_id: None, + + depth: 0, + })], + ); + }); +} diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index dd2a0db243..7bf2290622 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -64,11 +64,22 @@ struct ManageMembers { channel_id: u64, } +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +struct RenameChannel { + channel_id: u64, +} + actions!(collab_panel, [ToggleFocus, Remove, Secondary]); impl_actions!( collab_panel, - [RemoveChannel, NewChannel, InviteMembers, ManageMembers] + [ + RemoveChannel, + NewChannel, + InviteMembers, + ManageMembers, + RenameChannel + ] ); const CHANNELS_PANEL_KEY: &'static str = "ChannelsPanel"; @@ -83,16 +94,19 @@ pub fn init(_client: Arc, cx: &mut AppContext) { cx.add_action(CollabPanel::select_prev); cx.add_action(CollabPanel::confirm); cx.add_action(CollabPanel::remove); - cx.add_action(CollabPanel::remove_channel_action); + cx.add_action(CollabPanel::remove_selected_channel); cx.add_action(CollabPanel::show_inline_context_menu); cx.add_action(CollabPanel::new_subchannel); cx.add_action(CollabPanel::invite_members); cx.add_action(CollabPanel::manage_members); + cx.add_action(CollabPanel::rename_selected_channel); + cx.add_action(CollabPanel::rename_channel); } -#[derive(Debug, Default)] -pub struct ChannelEditingState { - parent_id: Option, +#[derive(Debug)] +pub enum ChannelEditingState { + Create { parent_id: Option }, + Rename { channel_id: u64 }, } pub struct CollabPanel { @@ -581,19 +595,32 @@ impl CollabPanel { executor.clone(), )); if let Some(state) = &self.channel_editing_state { - if state.parent_id.is_none() { + if matches!(state, ChannelEditingState::Create { parent_id: None }) { self.entries.push(ListEntry::ChannelEditor { depth: 0 }); } } for mat in matches { let channel = &channels[mat.candidate_id]; - self.entries.push(ListEntry::Channel(channel.clone())); - if let Some(state) = &self.channel_editing_state { - if state.parent_id == Some(channel.id) { + + match &self.channel_editing_state { + Some(ChannelEditingState::Create { parent_id }) + if *parent_id == Some(channel.id) => + { + self.entries.push(ListEntry::Channel(channel.clone())); self.entries.push(ListEntry::ChannelEditor { depth: channel.depth + 1, }); } + Some(ChannelEditingState::Rename { channel_id }) + if *channel_id == channel.id => + { + self.entries.push(ListEntry::ChannelEditor { + depth: channel.depth + 1, + }); + } + _ => { + self.entries.push(ListEntry::Channel(channel.clone())); + } } } } @@ -1065,15 +1092,15 @@ impl CollabPanel { &mut self, cx: &mut ViewContext, ) -> Option<(ChannelEditingState, String)> { - let result = self - .channel_editing_state - .take() - .map(|state| (state, self.channel_name_editor.read(cx).text(cx))); - - self.channel_name_editor - .update(cx, |editor, cx| editor.set_text("", cx)); - - result + if let Some(state) = self.channel_editing_state.take() { + self.channel_name_editor.update(cx, |editor, cx| { + let name = editor.text(cx); + editor.set_text("", cx); + Some((state, name)) + }) + } else { + None + } } fn render_header( @@ -1646,6 +1673,7 @@ impl CollabPanel { ContextMenuItem::action("Remove Channel", RemoveChannel { channel_id }), ContextMenuItem::action("Manage members", ManageMembers { channel_id }), ContextMenuItem::action("Invite members", InviteMembers { channel_id }), + ContextMenuItem::action("Rename Channel", RenameChannel { channel_id }), ], cx, ); @@ -1702,6 +1730,10 @@ impl CollabPanel { } fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) { + if self.confirm_channel_edit(cx) { + return; + } + if let Some(selection) = self.selection { if let Some(entry) = self.entries.get(selection) { match entry { @@ -1747,30 +1779,38 @@ impl CollabPanel { ListEntry::Channel(channel) => { self.join_channel(channel.id, cx); } - ListEntry::ChannelEditor { .. } => { - self.confirm_channel_edit(cx); - } _ => {} } } - } else { - self.confirm_channel_edit(cx); } } - fn confirm_channel_edit(&mut self, cx: &mut ViewContext<'_, '_, CollabPanel>) { + fn confirm_channel_edit(&mut self, cx: &mut ViewContext<'_, '_, CollabPanel>) -> bool { if let Some((editing_state, channel_name)) = self.take_editing_state(cx) { - let create_channel = self.channel_store.update(cx, |channel_store, _| { - channel_store.create_channel(&channel_name, editing_state.parent_id) - }); - + match editing_state { + ChannelEditingState::Create { parent_id } => { + let request = self.channel_store.update(cx, |channel_store, _| { + channel_store.create_channel(&channel_name, parent_id) + }); + cx.foreground() + .spawn(async move { + request.await?; + anyhow::Ok(()) + }) + .detach(); + } + ChannelEditingState::Rename { channel_id } => { + self.channel_store + .update(cx, |channel_store, cx| { + channel_store.rename(channel_id, &channel_name, cx) + }) + .detach(); + } + } self.update_entries(false, cx); - - cx.foreground() - .spawn(async move { - create_channel.await.log_err(); - }) - .detach(); + true + } else { + false } } @@ -1804,14 +1844,14 @@ impl CollabPanel { } fn new_root_channel(&mut self, cx: &mut ViewContext) { - self.channel_editing_state = Some(ChannelEditingState { parent_id: None }); + self.channel_editing_state = Some(ChannelEditingState::Create { parent_id: None }); self.update_entries(true, cx); cx.focus(self.channel_name_editor.as_any()); cx.notify(); } fn new_subchannel(&mut self, action: &NewChannel, cx: &mut ViewContext) { - self.channel_editing_state = Some(ChannelEditingState { + self.channel_editing_state = Some(ChannelEditingState::Create { parent_id: Some(action.channel_id), }); self.update_entries(true, cx); @@ -1835,7 +1875,33 @@ impl CollabPanel { } } - fn rename(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext) {} + 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_channel(&mut self, action: &RenameChannel, cx: &mut ViewContext) { + if let Some(channel) = self + .channel_store + .read(cx) + .channel_for_id(action.channel_id) + { + self.channel_editing_state = Some(ChannelEditingState::Rename { + channel_id: action.channel_id, + }); + self.channel_name_editor.update(cx, |editor, cx| { + editor.set_text(channel.name.clone(), cx); + editor.select_all(&Default::default(), cx); + }); + self.update_entries(true, cx); + } + } fn show_inline_context_menu(&mut self, _: &menu::ShowContextMenu, cx: &mut ViewContext) { let Some(channel) = self.selected_channel() else { @@ -1887,7 +1953,7 @@ impl CollabPanel { .detach(); } - fn remove_channel_action(&mut self, action: &RemoveChannel, cx: &mut ViewContext) { + fn remove_selected_channel(&mut self, action: &RemoveChannel, cx: &mut ViewContext) { self.remove_channel(action.channel_id, cx) } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 8f187a87c6..13b4c60aad 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -141,6 +141,7 @@ message Envelope { GetChannelMembers get_channel_members = 128; GetChannelMembersResponse get_channel_members_response = 129; SetChannelMemberAdmin set_channel_member_admin = 130; + RenameChannel rename_channel = 131; } } @@ -874,6 +875,12 @@ message UpdateChannels { repeated Channel channel_invitations = 3; repeated uint64 remove_channel_invitations = 4; repeated ChannelParticipants channel_participants = 5; + repeated ChannelPermission channel_permissions = 6; +} + +message ChannelPermission { + uint64 channel_id = 1; + bool is_admin = 2; } message ChannelParticipants { @@ -935,6 +942,11 @@ message SetChannelMemberAdmin { bool admin = 3; } +message RenameChannel { + uint64 channel_id = 1; + string name = 2; +} + message RespondToChannelInvite { uint64 channel_id = 1; bool accept = 2; @@ -1303,8 +1315,7 @@ message Nonce { message Channel { uint64 id = 1; string name = 2; - bool user_is_admin = 3; - optional uint64 parent_id = 4; + optional uint64 parent_id = 3; } message Contact { diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index fac011f803..d3a3091131 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -217,6 +217,7 @@ messages!( (JoinChannel, Foreground), (RoomUpdated, Foreground), (SaveBuffer, Foreground), + (RenameChannel, Foreground), (SetChannelMemberAdmin, Foreground), (SearchProject, Background), (SearchProjectResponse, Background), @@ -304,6 +305,7 @@ request_messages!( (JoinChannel, JoinRoomResponse), (RemoveChannel, Ack), (RenameProjectEntry, ProjectEntryResponse), + (RenameChannel, Ack), (SaveBuffer, BufferSaved), (SearchProject, SearchProjectResponse), (ShareProject, ShareProjectResponse),