Restrict DAG-related functionality, but retain infrastructure for implementing symlinks

This commit is contained in:
Mikayla 2023-10-18 05:28:05 -07:00
parent 70aed4a605
commit 0ce1ec5d15
No known key found for this signature in database
6 changed files with 338 additions and 394 deletions

View file

@ -19,17 +19,15 @@ fn test_update_channels(cx: &mut AppContext) {
id: 1, id: 1,
name: "b".to_string(), name: "b".to_string(),
visibility: proto::ChannelVisibility::Members as i32, visibility: proto::ChannelVisibility::Members as i32,
role: proto::ChannelRole::Admin.into(),
}, },
proto::Channel { proto::Channel {
id: 2, id: 2,
name: "a".to_string(), name: "a".to_string(),
visibility: proto::ChannelVisibility::Members as i32, visibility: proto::ChannelVisibility::Members as i32,
role: proto::ChannelRole::Member.into(),
}, },
], ],
channel_permissions: vec![proto::ChannelPermission {
channel_id: 1,
role: proto::ChannelRole::Admin.into(),
}],
..Default::default() ..Default::default()
}, },
cx, cx,
@ -52,11 +50,13 @@ fn test_update_channels(cx: &mut AppContext) {
id: 3, id: 3,
name: "x".to_string(), name: "x".to_string(),
visibility: proto::ChannelVisibility::Members as i32, visibility: proto::ChannelVisibility::Members as i32,
role: proto::ChannelRole::Member.into(),
}, },
proto::Channel { proto::Channel {
id: 4, id: 4,
name: "y".to_string(), name: "y".to_string(),
visibility: proto::ChannelVisibility::Members as i32, visibility: proto::ChannelVisibility::Members as i32,
role: proto::ChannelRole::Member.into(),
}, },
], ],
insert_edge: vec![ insert_edge: vec![
@ -97,16 +97,19 @@ fn test_dangling_channel_paths(cx: &mut AppContext) {
id: 0, id: 0,
name: "a".to_string(), name: "a".to_string(),
visibility: proto::ChannelVisibility::Members as i32, visibility: proto::ChannelVisibility::Members as i32,
role: proto::ChannelRole::Admin.into(),
}, },
proto::Channel { proto::Channel {
id: 1, id: 1,
name: "b".to_string(), name: "b".to_string(),
visibility: proto::ChannelVisibility::Members as i32, visibility: proto::ChannelVisibility::Members as i32,
role: proto::ChannelRole::Admin.into(),
}, },
proto::Channel { proto::Channel {
id: 2, id: 2,
name: "c".to_string(), name: "c".to_string(),
visibility: proto::ChannelVisibility::Members as i32, visibility: proto::ChannelVisibility::Members as i32,
role: proto::ChannelRole::Admin.into(),
}, },
], ],
insert_edge: vec![ insert_edge: vec![
@ -119,10 +122,6 @@ fn test_dangling_channel_paths(cx: &mut AppContext) {
channel_id: 2, channel_id: 2,
}, },
], ],
channel_permissions: vec![proto::ChannelPermission {
channel_id: 0,
role: proto::ChannelRole::Admin.into(),
}],
..Default::default() ..Default::default()
}, },
cx, cx,
@ -166,6 +165,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
id: channel_id, id: channel_id,
name: "the-channel".to_string(), name: "the-channel".to_string(),
visibility: proto::ChannelVisibility::Members as i32, visibility: proto::ChannelVisibility::Members as i32,
role: proto::ChannelRole::Admin.into(),
}], }],
..Default::default() ..Default::default()
}); });

View file

@ -419,7 +419,7 @@ impl Database {
} }
let channels = channel::Entity::find() let channels = channel::Entity::find()
.filter(channel::Column::Id.is_in(role_for_channel.keys().cloned())) .filter(channel::Column::Id.is_in(role_for_channel.keys().copied()))
.all(&*tx) .all(&*tx)
.await?; .await?;
@ -633,6 +633,36 @@ impl Database {
.await .await
} }
pub async fn get_channel_members_and_roles(
&self,
id: ChannelId,
) -> Result<Vec<(UserId, ChannelRole)>> {
self.transaction(|tx| async move {
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryUserIdsAndRoles {
UserId,
Role,
}
let ancestor_ids = self.get_channel_ancestors(id, &*tx).await?;
let user_ids_and_roles = channel_member::Entity::find()
.distinct()
.filter(
channel_member::Column::ChannelId
.is_in(ancestor_ids.iter().copied())
.and(channel_member::Column::Accepted.eq(true)),
)
.select_only()
.column(channel_member::Column::UserId)
.column(channel_member::Column::Role)
.into_values::<_, QueryUserIdsAndRoles>()
.all(&*tx)
.await?;
Ok(user_ids_and_roles)
})
.await
}
pub async fn set_channel_member_role( pub async fn set_channel_member_role(
&self, &self,
channel_id: ChannelId, channel_id: ChannelId,
@ -1138,9 +1168,6 @@ impl Database {
to: ChannelId, to: ChannelId,
) -> Result<ChannelGraph> { ) -> Result<ChannelGraph> {
self.transaction(|tx| async move { self.transaction(|tx| async move {
// Note that even with these maxed permissions, this linking operation
// is still insecure because you can't remove someone's permissions to a
// channel if they've linked the channel to one where they're an admin.
self.check_user_is_channel_admin(channel, user, &*tx) self.check_user_is_channel_admin(channel, user, &*tx)
.await?; .await?;
@ -1327,6 +1354,23 @@ impl Database {
}) })
.await .await
} }
pub async fn assert_root_channel(&self, channel: ChannelId) -> Result<()> {
self.transaction(|tx| async move {
let path = channel_path::Entity::find()
.filter(channel_path::Column::ChannelId.eq(channel))
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such channel found"))?;
let mut id_parts = path.id_path.trim_matches('/').split('/');
(id_parts.next().is_some() && id_parts.next().is_none())
.then_some(())
.ok_or_else(|| anyhow!("channel is not a root channel").into())
})
.await
}
} }
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]

View file

@ -3,8 +3,8 @@ mod connection_pool;
use crate::{ use crate::{
auth, auth,
db::{ db::{
self, BufferId, ChannelId, ChannelsForUser, Database, MessageId, ProjectId, RoomId, self, BufferId, ChannelId, ChannelRole, ChannelVisibility, ChannelsForUser, Database,
ServerId, User, UserId, MessageId, ProjectId, RoomId, ServerId, User, UserId,
}, },
executor::Executor, executor::Executor,
AppState, Result, AppState, Result,
@ -38,8 +38,8 @@ use lazy_static::lazy_static;
use prometheus::{register_int_gauge, IntGauge}; use prometheus::{register_int_gauge, IntGauge};
use rpc::{ use rpc::{
proto::{ proto::{
self, Ack, AnyTypedEnvelope, ChannelEdge, EntityMessage, EnvelopedMessage, self, Ack, AnyTypedEnvelope, EntityMessage, EnvelopedMessage, LiveKitConnectionInfo,
LiveKitConnectionInfo, RequestMessage, UpdateChannelBufferCollaborators, RequestMessage, UpdateChannelBufferCollaborators,
}, },
Connection, ConnectionId, Peer, Receipt, TypedEnvelope, Connection, ConnectionId, Peer, Receipt, TypedEnvelope,
}; };
@ -2366,15 +2366,18 @@ async fn set_channel_visibility(
for member in members { for member in members {
for connection_id in connection_pool.user_connection_ids(UserId::from_proto(member.user_id)) for connection_id in connection_pool.user_connection_ids(UserId::from_proto(member.user_id))
{ {
let mut update = proto::UpdateChannels::default(); session.peer.send(
update.channels.push(proto::Channel { connection_id,
id: channel.id.to_proto(), proto::UpdateChannels {
name: channel.name.clone(), channels: vec![proto::Channel {
visibility: channel.visibility.into(), id: channel.id.to_proto(),
role: member.role.into(), name: channel.name.clone(),
}); visibility: channel.visibility.into(),
role: member.role.into(),
session.peer.send(connection_id, update.clone())?; }],
..Default::default()
},
)?;
} }
} }
@ -2468,6 +2471,8 @@ async fn rename_channel(
Ok(()) Ok(())
} }
// TODO: Implement in terms of symlinks
// Current behavior of this is more like 'Move root channel'
async fn link_channel( async fn link_channel(
request: proto::LinkChannel, request: proto::LinkChannel,
response: Response<proto::LinkChannel>, response: Response<proto::LinkChannel>,
@ -2476,30 +2481,46 @@ async fn link_channel(
let db = session.db().await; let db = session.db().await;
let channel_id = ChannelId::from_proto(request.channel_id); let channel_id = ChannelId::from_proto(request.channel_id);
let to = ChannelId::from_proto(request.to); let to = ChannelId::from_proto(request.to);
let channels_to_send = db.link_channel(session.user_id, channel_id, to).await?;
let members = db.get_channel_members(to).await?; // TODO: Remove this restriction once we have symlinks
db.assert_root_channel(channel_id).await?;
let channels_to_send = db.link_channel(session.user_id, channel_id, to).await?;
let members = db.get_channel_members_and_roles(to).await?;
let connection_pool = session.connection_pool().await; let connection_pool = session.connection_pool().await;
let update = proto::UpdateChannels {
channels: channels_to_send for (member_id, role) in members {
.channels let build_channel_proto = |channel: &db::Channel| proto::Channel {
.into_iter() id: channel.id.to_proto(),
.map(|channel| proto::Channel { visibility: channel.visibility.into(),
id: channel.id.to_proto(), name: channel.name.clone(),
visibility: channel.visibility.into(), role: role.into(),
name: channel.name, };
// TODO: not all these members should be able to see all those channels
// the channels in channels_to_send are from the admin point of view,
// but any public guests should only get updates about public channels.
role: todo!(),
})
.collect(),
insert_edge: channels_to_send.edges,
..Default::default()
};
for member_id in members {
for connection_id in connection_pool.user_connection_ids(member_id) { for connection_id in connection_pool.user_connection_ids(member_id) {
session.peer.send(connection_id, update.clone())?; let channels: Vec<_> = if role == ChannelRole::Guest {
channels_to_send
.channels
.iter()
.filter(|channel| channel.visibility != ChannelVisibility::Public)
.map(build_channel_proto)
.collect()
} else {
channels_to_send
.channels
.iter()
.map(build_channel_proto)
.collect()
};
session.peer.send(
connection_id,
proto::UpdateChannels {
channels,
insert_edge: channels_to_send.edges.clone(),
..Default::default()
},
)?;
} }
} }
@ -2508,36 +2529,13 @@ async fn link_channel(
Ok(()) Ok(())
} }
// TODO: Implement in terms of symlinks
async fn unlink_channel( async fn unlink_channel(
request: proto::UnlinkChannel, _request: proto::UnlinkChannel,
response: Response<proto::UnlinkChannel>, _response: Response<proto::UnlinkChannel>,
session: Session, _session: Session,
) -> Result<()> { ) -> Result<()> {
let db = session.db().await; Err(anyhow!("unimplemented").into())
let channel_id = ChannelId::from_proto(request.channel_id);
let from = ChannelId::from_proto(request.from);
db.unlink_channel(session.user_id, channel_id, from).await?;
let members = db.get_channel_members(from).await?;
let update = proto::UpdateChannels {
delete_edge: vec![proto::ChannelEdge {
channel_id: channel_id.to_proto(),
parent_id: from.to_proto(),
}],
..Default::default()
};
let connection_pool = session.connection_pool().await;
for member_id in members {
for connection_id in connection_pool.user_connection_ids(member_id) {
session.peer.send(connection_id, update.clone())?;
}
}
response.send(Ack {})?;
Ok(())
} }
async fn move_channel( async fn move_channel(
@ -2554,7 +2552,7 @@ async fn move_channel(
.public_path_to_channel(from_parent) .public_path_to_channel(from_parent)
.await? .await?
.last() .last()
.cloned(); .copied();
let channels_to_send = db let channels_to_send = db
.move_channel(session.user_id, channel_id, from_parent, to) .move_channel(session.user_id, channel_id, from_parent, to)
@ -2574,6 +2572,7 @@ async fn move_channel(
.filter(|member| { .filter(|member| {
member.role() == proto::ChannelRole::Admin || member.role() == proto::ChannelRole::Guest member.role() == proto::ChannelRole::Admin || member.role() == proto::ChannelRole::Guest
}); });
let members_to = db let members_to = db
.get_channel_participant_details(to, session.user_id) .get_channel_participant_details(to, session.user_id)
.await? .await?

View file

@ -1031,14 +1031,14 @@ async fn test_invite_access(
async fn test_channel_moving( async fn test_channel_moving(
deterministic: Arc<Deterministic>, deterministic: Arc<Deterministic>,
cx_a: &mut TestAppContext, cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext, _cx_b: &mut TestAppContext,
cx_c: &mut TestAppContext, _cx_c: &mut TestAppContext,
) { ) {
deterministic.forbid_parking(); deterministic.forbid_parking();
let mut server = TestServer::start(&deterministic).await; let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await; let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await; // let client_b = server.create_client(cx_b, "user_b").await;
let client_c = server.create_client(cx_c, "user_c").await; // let client_c = server.create_client(cx_c, "user_c").await;
let channels = server let channels = server
.make_channel_tree( .make_channel_tree(
@ -1091,187 +1091,188 @@ async fn test_channel_moving(
], ],
); );
client_a // TODO: Restore this test once we have a way to make channel symlinks
.channel_store() // client_a
.update(cx_a, |channel_store, cx| { // .channel_store()
channel_store.link_channel(channel_d_id, channel_c_id, cx) // .update(cx_a, |channel_store, cx| {
}) // channel_store.link_channel(channel_d_id, channel_c_id, cx)
.await // })
.unwrap(); // .await
// .unwrap();
// Current shape for A: // // Current shape for A:
// /------\ // // /------\
// a - b -- c -- d // // a - b -- c -- d
assert_channels_list_shape( // assert_channels_list_shape(
client_a.channel_store(), // client_a.channel_store(),
cx_a, // cx_a,
&[ // &[
(channel_a_id, 0), // (channel_a_id, 0),
(channel_b_id, 1), // (channel_b_id, 1),
(channel_c_id, 2), // (channel_c_id, 2),
(channel_d_id, 3), // (channel_d_id, 3),
(channel_d_id, 2), // (channel_d_id, 2),
], // ],
); // );
//
// let b_channels = server
// .make_channel_tree(
// &[
// ("channel-mu", None),
// ("channel-gamma", Some("channel-mu")),
// ("channel-epsilon", Some("channel-mu")),
// ],
// (&client_b, cx_b),
// )
// .await;
// let channel_mu_id = b_channels[0];
// let channel_ga_id = b_channels[1];
// let channel_ep_id = b_channels[2];
let b_channels = server // // Current shape for B:
.make_channel_tree( // // /- ep
&[ // // mu -- ga
("channel-mu", None), // assert_channels_list_shape(
("channel-gamma", Some("channel-mu")), // client_b.channel_store(),
("channel-epsilon", Some("channel-mu")), // cx_b,
], // &[(channel_mu_id, 0), (channel_ep_id, 1), (channel_ga_id, 1)],
(&client_b, cx_b), // );
)
.await;
let channel_mu_id = b_channels[0];
let channel_ga_id = b_channels[1];
let channel_ep_id = b_channels[2];
// Current shape for B: // client_a
// /- ep // .add_admin_to_channel((&client_b, cx_b), channel_b_id, cx_a)
// mu -- ga // .await;
assert_channels_list_shape(
client_b.channel_store(),
cx_b,
&[(channel_mu_id, 0), (channel_ep_id, 1), (channel_ga_id, 1)],
);
client_a // // Current shape for B:
.add_admin_to_channel((&client_b, cx_b), channel_b_id, cx_a) // // /- ep
.await; // // mu -- ga
// // /---------\
// // b -- c -- d
// assert_channels_list_shape(
// client_b.channel_store(),
// cx_b,
// &[
// // New channels from a
// (channel_b_id, 0),
// (channel_c_id, 1),
// (channel_d_id, 2),
// (channel_d_id, 1),
// // B's old channels
// (channel_mu_id, 0),
// (channel_ep_id, 1),
// (channel_ga_id, 1),
// ],
// );
// Current shape for B: // client_b
// /- ep // .add_admin_to_channel((&client_c, cx_c), channel_ep_id, cx_b)
// mu -- ga // .await;
// /---------\
// b -- c -- d
assert_channels_list_shape(
client_b.channel_store(),
cx_b,
&[
// New channels from a
(channel_b_id, 0),
(channel_c_id, 1),
(channel_d_id, 2),
(channel_d_id, 1),
// B's old channels
(channel_mu_id, 0),
(channel_ep_id, 1),
(channel_ga_id, 1),
],
);
client_b // // Current shape for C:
.add_admin_to_channel((&client_c, cx_c), channel_ep_id, cx_b) // // - ep
.await; // assert_channels_list_shape(client_c.channel_store(), cx_c, &[(channel_ep_id, 0)]);
// Current shape for C: // client_b
// - ep // .channel_store()
assert_channels_list_shape(client_c.channel_store(), cx_c, &[(channel_ep_id, 0)]); // .update(cx_b, |channel_store, cx| {
// channel_store.link_channel(channel_b_id, channel_ep_id, cx)
// })
// .await
// .unwrap();
client_b // // Current shape for B:
.channel_store() // // /---------\
.update(cx_b, |channel_store, cx| { // // /- ep -- b -- c -- d
channel_store.link_channel(channel_b_id, channel_ep_id, cx) // // mu -- ga
}) // assert_channels_list_shape(
.await // client_b.channel_store(),
.unwrap(); // cx_b,
// &[
// (channel_mu_id, 0),
// (channel_ep_id, 1),
// (channel_b_id, 2),
// (channel_c_id, 3),
// (channel_d_id, 4),
// (channel_d_id, 3),
// (channel_ga_id, 1),
// ],
// );
// Current shape for B: // // Current shape for C:
// /---------\ // // /---------\
// /- ep -- b -- c -- d // // ep -- b -- c -- d
// mu -- ga // assert_channels_list_shape(
assert_channels_list_shape( // client_c.channel_store(),
client_b.channel_store(), // cx_c,
cx_b, // &[
&[ // (channel_ep_id, 0),
(channel_mu_id, 0), // (channel_b_id, 1),
(channel_ep_id, 1), // (channel_c_id, 2),
(channel_b_id, 2), // (channel_d_id, 3),
(channel_c_id, 3), // (channel_d_id, 2),
(channel_d_id, 4), // ],
(channel_d_id, 3), // );
(channel_ga_id, 1),
],
);
// Current shape for C: // client_b
// /---------\ // .channel_store()
// ep -- b -- c -- d // .update(cx_b, |channel_store, cx| {
assert_channels_list_shape( // channel_store.link_channel(channel_ga_id, channel_b_id, cx)
client_c.channel_store(), // })
cx_c, // .await
&[ // .unwrap();
(channel_ep_id, 0),
(channel_b_id, 1),
(channel_c_id, 2),
(channel_d_id, 3),
(channel_d_id, 2),
],
);
client_b // // Current shape for B:
.channel_store() // // /---------\
.update(cx_b, |channel_store, cx| { // // /- ep -- b -- c -- d
channel_store.link_channel(channel_ga_id, channel_b_id, cx) // // / \
}) // // mu ---------- ga
.await // assert_channels_list_shape(
.unwrap(); // client_b.channel_store(),
// cx_b,
// &[
// (channel_mu_id, 0),
// (channel_ep_id, 1),
// (channel_b_id, 2),
// (channel_c_id, 3),
// (channel_d_id, 4),
// (channel_d_id, 3),
// (channel_ga_id, 3),
// (channel_ga_id, 1),
// ],
// );
// Current shape for B: // // Current shape for A:
// /---------\ // // /------\
// /- ep -- b -- c -- d // // a - b -- c -- d
// / \ // // \-- ga
// mu ---------- ga // assert_channels_list_shape(
assert_channels_list_shape( // client_a.channel_store(),
client_b.channel_store(), // cx_a,
cx_b, // &[
&[ // (channel_a_id, 0),
(channel_mu_id, 0), // (channel_b_id, 1),
(channel_ep_id, 1), // (channel_c_id, 2),
(channel_b_id, 2), // (channel_d_id, 3),
(channel_c_id, 3), // (channel_d_id, 2),
(channel_d_id, 4), // (channel_ga_id, 2),
(channel_d_id, 3), // ],
(channel_ga_id, 3), // );
(channel_ga_id, 1),
],
);
// Current shape for A: // // Current shape for C:
// /------\ // // /-------\
// a - b -- c -- d // // ep -- b -- c -- d
// \-- ga // // \-- ga
assert_channels_list_shape( // assert_channels_list_shape(
client_a.channel_store(), // client_c.channel_store(),
cx_a, // cx_c,
&[ // &[
(channel_a_id, 0), // (channel_ep_id, 0),
(channel_b_id, 1), // (channel_b_id, 1),
(channel_c_id, 2), // (channel_c_id, 2),
(channel_d_id, 3), // (channel_d_id, 3),
(channel_d_id, 2), // (channel_d_id, 2),
(channel_ga_id, 2), // (channel_ga_id, 2),
], // ],
); // );
// Current shape for C:
// /-------\
// ep -- b -- c -- d
// \-- ga
assert_channels_list_shape(
client_c.channel_store(),
cx_c,
&[
(channel_ep_id, 0),
(channel_b_id, 1),
(channel_c_id, 2),
(channel_d_id, 3),
(channel_d_id, 2),
(channel_ga_id, 2),
],
);
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]

View file

@ -120,22 +120,11 @@ struct StartLinkChannelFor {
parent_id: Option<ChannelId>, parent_id: Option<ChannelId>,
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct LinkChannel {
to: ChannelId,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct MoveChannel { struct MoveChannel {
to: ChannelId, to: ChannelId,
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct UnlinkChannel {
channel_id: ChannelId,
parent_id: ChannelId,
}
type DraggedChannel = (Channel, Option<ChannelId>); type DraggedChannel = (Channel, Option<ChannelId>);
actions!( actions!(
@ -147,8 +136,7 @@ actions!(
CollapseSelectedChannel, CollapseSelectedChannel,
ExpandSelectedChannel, ExpandSelectedChannel,
StartMoveChannel, StartMoveChannel,
StartLinkChannel, MoveSelected,
MoveOrLinkToSelected,
InsertSpace, InsertSpace,
] ]
); );
@ -166,11 +154,8 @@ impl_actions!(
JoinChannelCall, JoinChannelCall,
JoinChannelChat, JoinChannelChat,
CopyChannelLink, CopyChannelLink,
LinkChannel,
StartMoveChannelFor, StartMoveChannelFor,
StartLinkChannelFor,
MoveChannel, MoveChannel,
UnlinkChannel,
ToggleSelectedIx ToggleSelectedIx
] ]
); );
@ -185,7 +170,7 @@ struct ChannelMoveClipboard {
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum ClipboardIntent { enum ClipboardIntent {
Move, Move,
Link, // Link,
} }
const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel"; const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
@ -238,18 +223,6 @@ pub fn init(cx: &mut AppContext) {
}, },
); );
cx.add_action(
|panel: &mut CollabPanel,
action: &StartLinkChannelFor,
_: &mut ViewContext<CollabPanel>| {
panel.channel_clipboard = Some(ChannelMoveClipboard {
channel_id: action.channel_id,
parent_id: action.parent_id,
intent: ClipboardIntent::Link,
})
},
);
cx.add_action( cx.add_action(
|panel: &mut CollabPanel, _: &StartMoveChannel, _: &mut ViewContext<CollabPanel>| { |panel: &mut CollabPanel, _: &StartMoveChannel, _: &mut ViewContext<CollabPanel>| {
if let Some((_, path)) = panel.selected_channel() { if let Some((_, path)) = panel.selected_channel() {
@ -263,86 +236,51 @@ pub fn init(cx: &mut AppContext) {
); );
cx.add_action( cx.add_action(
|panel: &mut CollabPanel, _: &StartLinkChannel, _: &mut ViewContext<CollabPanel>| { |panel: &mut CollabPanel, _: &MoveSelected, cx: &mut ViewContext<CollabPanel>| {
if let Some((_, path)) = panel.selected_channel() {
panel.channel_clipboard = Some(ChannelMoveClipboard {
channel_id: path.channel_id(),
parent_id: path.parent_id(),
intent: ClipboardIntent::Link,
})
}
},
);
cx.add_action(
|panel: &mut CollabPanel, _: &MoveOrLinkToSelected, cx: &mut ViewContext<CollabPanel>| {
let clipboard = panel.channel_clipboard.take(); let clipboard = panel.channel_clipboard.take();
if let Some(((selected_channel, _), clipboard)) = if let Some(((selected_channel, _), clipboard)) =
panel.selected_channel().zip(clipboard) panel.selected_channel().zip(clipboard)
{ {
match clipboard.intent { match clipboard.intent {
ClipboardIntent::Move if clipboard.parent_id.is_some() => { ClipboardIntent::Move => panel.channel_store.update(cx, |channel_store, cx| {
let parent_id = clipboard.parent_id.unwrap(); match clipboard.parent_id {
panel.channel_store.update(cx, |channel_store, cx| { Some(parent_id) => channel_store.move_channel(
channel_store clipboard.channel_id,
.move_channel( parent_id,
clipboard.channel_id, selected_channel.id,
parent_id, cx,
selected_channel.id, ),
cx, None => channel_store.link_channel(
) clipboard.channel_id,
.detach_and_log_err(cx) selected_channel.id,
}) cx,
} ),
_ => panel.channel_store.update(cx, |channel_store, cx| { }
channel_store .detach_and_log_err(cx)
.link_channel(clipboard.channel_id, selected_channel.id, cx)
.detach_and_log_err(cx)
}), }),
} }
} }
}, },
); );
cx.add_action(
|panel: &mut CollabPanel, action: &LinkChannel, cx: &mut ViewContext<CollabPanel>| {
if let Some(clipboard) = panel.channel_clipboard.take() {
panel.channel_store.update(cx, |channel_store, cx| {
channel_store
.link_channel(clipboard.channel_id, action.to, cx)
.detach_and_log_err(cx)
})
}
},
);
cx.add_action( cx.add_action(
|panel: &mut CollabPanel, action: &MoveChannel, cx: &mut ViewContext<CollabPanel>| { |panel: &mut CollabPanel, action: &MoveChannel, cx: &mut ViewContext<CollabPanel>| {
if let Some(clipboard) = panel.channel_clipboard.take() { if let Some(clipboard) = panel.channel_clipboard.take() {
panel.channel_store.update(cx, |channel_store, cx| { panel.channel_store.update(cx, |channel_store, cx| {
if let Some(parent) = clipboard.parent_id { match clipboard.parent_id {
channel_store Some(parent_id) => channel_store.move_channel(
.move_channel(clipboard.channel_id, parent, action.to, cx) clipboard.channel_id,
.detach_and_log_err(cx) parent_id,
} else { action.to,
channel_store cx,
.link_channel(clipboard.channel_id, action.to, cx) ),
.detach_and_log_err(cx) None => channel_store.link_channel(clipboard.channel_id, action.to, cx),
} }
.detach_and_log_err(cx)
}) })
} }
}, },
); );
cx.add_action(
|panel: &mut CollabPanel, action: &UnlinkChannel, cx: &mut ViewContext<CollabPanel>| {
panel.channel_store.update(cx, |channel_store, cx| {
channel_store
.unlink_channel(action.channel_id, action.parent_id, cx)
.detach_and_log_err(cx)
})
},
);
} }
#[derive(Debug)] #[derive(Debug)]
@ -2235,33 +2173,23 @@ impl CollabPanel {
this.deploy_channel_context_menu(Some(e.position), &path, ix, cx); this.deploy_channel_context_menu(Some(e.position), &path, ix, cx);
} }
}) })
.on_up(MouseButton::Left, move |e, this, cx| { .on_up(MouseButton::Left, move |_, this, cx| {
if let Some((_, dragged_channel)) = cx if let Some((_, dragged_channel)) = cx
.global::<DragAndDrop<Workspace>>() .global::<DragAndDrop<Workspace>>()
.currently_dragged::<DraggedChannel>(cx.window()) .currently_dragged::<DraggedChannel>(cx.window())
{ {
if e.modifiers.alt { this.channel_store.update(cx, |channel_store, cx| {
this.channel_store.update(cx, |channel_store, cx| { match dragged_channel.1 {
channel_store Some(parent_id) => channel_store.move_channel(
.link_channel(dragged_channel.0.id, channel_id, cx) dragged_channel.0.id,
.detach_and_log_err(cx) parent_id,
}) channel_id,
} else { cx,
this.channel_store.update(cx, |channel_store, cx| { ),
match dragged_channel.1 { None => channel_store.link_channel(dragged_channel.0.id, channel_id, cx),
Some(parent_id) => channel_store.move_channel( }
dragged_channel.0.id, .detach_and_log_err(cx)
parent_id, })
channel_id,
cx,
),
None => {
channel_store.link_channel(dragged_channel.0.id, channel_id, cx)
}
}
.detach_and_log_err(cx)
})
}
} }
}) })
.on_move({ .on_move({
@ -2288,18 +2216,10 @@ impl CollabPanel {
}) })
.as_draggable( .as_draggable(
(channel.clone(), path.parent_id()), (channel.clone(), path.parent_id()),
move |modifiers, (channel, _), cx: &mut ViewContext<Workspace>| { move |_, (channel, _), cx: &mut ViewContext<Workspace>| {
let theme = &theme::current(cx).collab_panel; let theme = &theme::current(cx).collab_panel;
Flex::<Workspace>::row() Flex::<Workspace>::row()
.with_children(modifiers.alt.then(|| {
Svg::new("icons/plus.svg")
.with_color(theme.channel_hash.color)
.constrained()
.with_width(theme.channel_hash.width)
.aligned()
.left()
}))
.with_child( .with_child(
Svg::new("icons/hash.svg") Svg::new("icons/hash.svg")
.with_color(theme.channel_hash.color) .with_color(theme.channel_hash.color)
@ -2743,19 +2663,6 @@ impl CollabPanel {
}, },
), ),
ContextMenuItem::Separator, ContextMenuItem::Separator,
]);
if let Some(parent_id) = parent_id {
items.push(ContextMenuItem::action(
"Unlink from parent",
UnlinkChannel {
channel_id: path.channel_id(),
parent_id,
},
));
}
items.extend([
ContextMenuItem::action( ContextMenuItem::action(
"Move this channel", "Move this channel",
StartMoveChannelFor { StartMoveChannelFor {
@ -2763,13 +2670,6 @@ impl CollabPanel {
parent_id, parent_id,
}, },
), ),
ContextMenuItem::action(
"Link this channel",
StartLinkChannelFor {
channel_id: path.channel_id(),
parent_id,
},
),
]); ]);
if let Some(channel_name) = channel_name { if let Some(channel_name) = channel_name {
@ -2780,12 +2680,6 @@ impl CollabPanel {
to: path.channel_id(), to: path.channel_id(),
}, },
)); ));
items.push(ContextMenuItem::action(
format!("Link '#{}' here", channel_name),
LinkChannel {
to: path.channel_id(),
},
));
} }
items.extend([ items.extend([

View file

@ -970,10 +970,16 @@ message UpdateChannels {
repeated Channel channel_invitations = 5; repeated Channel channel_invitations = 5;
repeated uint64 remove_channel_invitations = 6; repeated uint64 remove_channel_invitations = 6;
repeated ChannelParticipants channel_participants = 7; repeated ChannelParticipants channel_participants = 7;
//repeated ChannelRoles channel_roles = 8;
repeated UnseenChannelMessage unseen_channel_messages = 9; repeated UnseenChannelMessage unseen_channel_messages = 9;
repeated UnseenChannelBufferChange unseen_channel_buffer_changes = 10; repeated UnseenChannelBufferChange unseen_channel_buffer_changes = 10;
} }
//message ChannelRoles {
// ChannelRole role = 1;
// uint64 channel_id = 2;
//}
message UnseenChannelMessage { message UnseenChannelMessage {
uint64 channel_id = 1; uint64 channel_id = 1;
uint64 message_id = 2; uint64 message_id = 2;