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

View file

@ -419,7 +419,7 @@ impl Database {
}
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)
.await?;
@ -633,6 +633,36 @@ impl Database {
.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(
&self,
channel_id: ChannelId,
@ -1138,9 +1168,6 @@ impl Database {
to: ChannelId,
) -> Result<ChannelGraph> {
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)
.await?;
@ -1327,6 +1354,23 @@ impl Database {
})
.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)]

View file

@ -3,8 +3,8 @@ mod connection_pool;
use crate::{
auth,
db::{
self, BufferId, ChannelId, ChannelsForUser, Database, MessageId, ProjectId, RoomId,
ServerId, User, UserId,
self, BufferId, ChannelId, ChannelRole, ChannelVisibility, ChannelsForUser, Database,
MessageId, ProjectId, RoomId, ServerId, User, UserId,
},
executor::Executor,
AppState, Result,
@ -38,8 +38,8 @@ use lazy_static::lazy_static;
use prometheus::{register_int_gauge, IntGauge};
use rpc::{
proto::{
self, Ack, AnyTypedEnvelope, ChannelEdge, EntityMessage, EnvelopedMessage,
LiveKitConnectionInfo, RequestMessage, UpdateChannelBufferCollaborators,
self, Ack, AnyTypedEnvelope, EntityMessage, EnvelopedMessage, LiveKitConnectionInfo,
RequestMessage, UpdateChannelBufferCollaborators,
},
Connection, ConnectionId, Peer, Receipt, TypedEnvelope,
};
@ -2366,15 +2366,18 @@ async fn set_channel_visibility(
for member in members {
for connection_id in connection_pool.user_connection_ids(UserId::from_proto(member.user_id))
{
let mut update = proto::UpdateChannels::default();
update.channels.push(proto::Channel {
session.peer.send(
connection_id,
proto::UpdateChannels {
channels: vec![proto::Channel {
id: channel.id.to_proto(),
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(())
}
// TODO: Implement in terms of symlinks
// Current behavior of this is more like 'Move root channel'
async fn link_channel(
request: proto::LinkChannel,
response: Response<proto::LinkChannel>,
@ -2476,30 +2481,46 @@ async fn link_channel(
let db = session.db().await;
let channel_id = ChannelId::from_proto(request.channel_id);
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 update = proto::UpdateChannels {
channels: channels_to_send
.channels
.into_iter()
.map(|channel| proto::Channel {
for (member_id, role) in members {
let build_channel_proto = |channel: &db::Channel| proto::Channel {
id: channel.id.to_proto(),
visibility: channel.visibility.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()
name: channel.name.clone(),
role: role.into(),
};
for member_id in members {
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(())
}
// TODO: Implement in terms of symlinks
async fn unlink_channel(
request: proto::UnlinkChannel,
response: Response<proto::UnlinkChannel>,
session: Session,
_request: proto::UnlinkChannel,
_response: Response<proto::UnlinkChannel>,
_session: Session,
) -> Result<()> {
let db = session.db().await;
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(())
Err(anyhow!("unimplemented").into())
}
async fn move_channel(
@ -2554,7 +2552,7 @@ async fn move_channel(
.public_path_to_channel(from_parent)
.await?
.last()
.cloned();
.copied();
let channels_to_send = db
.move_channel(session.user_id, channel_id, from_parent, to)
@ -2574,6 +2572,7 @@ async fn move_channel(
.filter(|member| {
member.role() == proto::ChannelRole::Admin || member.role() == proto::ChannelRole::Guest
});
let members_to = db
.get_channel_participant_details(to, session.user_id)
.await?

View file

@ -1031,14 +1031,14 @@ async fn test_invite_access(
async fn test_channel_moving(
deterministic: Arc<Deterministic>,
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
cx_c: &mut TestAppContext,
_cx_b: &mut TestAppContext,
_cx_c: &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 client_c = server.create_client(cx_c, "user_c").await;
// let client_b = server.create_client(cx_b, "user_b").await;
// let client_c = server.create_client(cx_c, "user_c").await;
let channels = server
.make_channel_tree(
@ -1091,187 +1091,188 @@ async fn test_channel_moving(
],
);
client_a
.channel_store()
.update(cx_a, |channel_store, cx| {
channel_store.link_channel(channel_d_id, channel_c_id, cx)
})
.await
.unwrap();
// TODO: Restore this test once we have a way to make channel symlinks
// client_a
// .channel_store()
// .update(cx_a, |channel_store, cx| {
// channel_store.link_channel(channel_d_id, channel_c_id, cx)
// })
// .await
// .unwrap();
// Current shape for A:
// /------\
// a - b -- c -- d
assert_channels_list_shape(
client_a.channel_store(),
cx_a,
&[
(channel_a_id, 0),
(channel_b_id, 1),
(channel_c_id, 2),
(channel_d_id, 3),
(channel_d_id, 2),
],
);
// // Current shape for A:
// // /------\
// // a - b -- c -- d
// assert_channels_list_shape(
// client_a.channel_store(),
// cx_a,
// &[
// (channel_a_id, 0),
// (channel_b_id, 1),
// (channel_c_id, 2),
// (channel_d_id, 3),
// (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
.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];
// // Current shape for B:
// // /- ep
// // mu -- ga
// assert_channels_list_shape(
// client_b.channel_store(),
// cx_b,
// &[(channel_mu_id, 0), (channel_ep_id, 1), (channel_ga_id, 1)],
// );
// Current shape for B:
// /- ep
// mu -- ga
assert_channels_list_shape(
client_b.channel_store(),
cx_b,
&[(channel_mu_id, 0), (channel_ep_id, 1), (channel_ga_id, 1)],
);
// client_a
// .add_admin_to_channel((&client_b, cx_b), channel_b_id, cx_a)
// .await;
client_a
.add_admin_to_channel((&client_b, cx_b), channel_b_id, cx_a)
.await;
// // Current shape for B:
// // /- ep
// // 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:
// /- ep
// 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),
],
);
// client_b
// .add_admin_to_channel((&client_c, cx_c), channel_ep_id, cx_b)
// .await;
client_b
.add_admin_to_channel((&client_c, cx_c), channel_ep_id, cx_b)
.await;
// // Current shape for C:
// // - ep
// assert_channels_list_shape(client_c.channel_store(), cx_c, &[(channel_ep_id, 0)]);
// Current shape for C:
// - ep
assert_channels_list_shape(client_c.channel_store(), cx_c, &[(channel_ep_id, 0)]);
// client_b
// .channel_store()
// .update(cx_b, |channel_store, cx| {
// channel_store.link_channel(channel_b_id, channel_ep_id, cx)
// })
// .await
// .unwrap();
client_b
.channel_store()
.update(cx_b, |channel_store, cx| {
channel_store.link_channel(channel_b_id, channel_ep_id, cx)
})
.await
.unwrap();
// // Current shape for B:
// // /---------\
// // /- ep -- b -- c -- d
// // mu -- ga
// assert_channels_list_shape(
// 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, 1),
// ],
// );
// Current shape for B:
// /---------\
// /- ep -- b -- c -- d
// mu -- ga
assert_channels_list_shape(
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, 1),
],
);
// // Current shape for C:
// // /---------\
// // ep -- b -- c -- d
// 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),
// ],
// );
// Current shape for C:
// /---------\
// ep -- b -- c -- d
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),
],
);
// client_b
// .channel_store()
// .update(cx_b, |channel_store, cx| {
// channel_store.link_channel(channel_ga_id, channel_b_id, cx)
// })
// .await
// .unwrap();
client_b
.channel_store()
.update(cx_b, |channel_store, cx| {
channel_store.link_channel(channel_ga_id, channel_b_id, cx)
})
.await
.unwrap();
// // Current shape for B:
// // /---------\
// // /- ep -- b -- c -- d
// // / \
// // mu ---------- ga
// assert_channels_list_shape(
// 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:
// /---------\
// /- ep -- b -- c -- d
// / \
// mu ---------- ga
assert_channels_list_shape(
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 A:
// // /------\
// // a - b -- c -- d
// // \-- ga
// assert_channels_list_shape(
// client_a.channel_store(),
// cx_a,
// &[
// (channel_a_id, 0),
// (channel_b_id, 1),
// (channel_c_id, 2),
// (channel_d_id, 3),
// (channel_d_id, 2),
// (channel_ga_id, 2),
// ],
// );
// Current shape for A:
// /------\
// a - b -- c -- d
// \-- ga
assert_channels_list_shape(
client_a.channel_store(),
cx_a,
&[
(channel_a_id, 0),
(channel_b_id, 1),
(channel_c_id, 2),
(channel_d_id, 3),
(channel_d_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),
],
);
// // 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)]

View file

@ -120,22 +120,11 @@ struct StartLinkChannelFor {
parent_id: Option<ChannelId>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct LinkChannel {
to: ChannelId,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct MoveChannel {
to: ChannelId,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct UnlinkChannel {
channel_id: ChannelId,
parent_id: ChannelId,
}
type DraggedChannel = (Channel, Option<ChannelId>);
actions!(
@ -147,8 +136,7 @@ actions!(
CollapseSelectedChannel,
ExpandSelectedChannel,
StartMoveChannel,
StartLinkChannel,
MoveOrLinkToSelected,
MoveSelected,
InsertSpace,
]
);
@ -166,11 +154,8 @@ impl_actions!(
JoinChannelCall,
JoinChannelChat,
CopyChannelLink,
LinkChannel,
StartMoveChannelFor,
StartLinkChannelFor,
MoveChannel,
UnlinkChannel,
ToggleSelectedIx
]
);
@ -185,7 +170,7 @@ struct ChannelMoveClipboard {
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum ClipboardIntent {
Move,
Link,
// Link,
}
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(
|panel: &mut CollabPanel, _: &StartMoveChannel, _: &mut ViewContext<CollabPanel>| {
if let Some((_, path)) = panel.selected_channel() {
@ -263,40 +236,26 @@ pub fn init(cx: &mut AppContext) {
);
cx.add_action(
|panel: &mut CollabPanel, _: &StartLinkChannel, _: &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>| {
|panel: &mut CollabPanel, _: &MoveSelected, cx: &mut ViewContext<CollabPanel>| {
let clipboard = panel.channel_clipboard.take();
if let Some(((selected_channel, _), clipboard)) =
panel.selected_channel().zip(clipboard)
{
match clipboard.intent {
ClipboardIntent::Move if clipboard.parent_id.is_some() => {
let parent_id = clipboard.parent_id.unwrap();
panel.channel_store.update(cx, |channel_store, cx| {
channel_store
.move_channel(
ClipboardIntent::Move => panel.channel_store.update(cx, |channel_store, cx| {
match clipboard.parent_id {
Some(parent_id) => channel_store.move_channel(
clipboard.channel_id,
parent_id,
selected_channel.id,
cx,
)
.detach_and_log_err(cx)
})
),
None => channel_store.link_channel(
clipboard.channel_id,
selected_channel.id,
cx,
),
}
_ => panel.channel_store.update(cx, |channel_store, cx| {
channel_store
.link_channel(clipboard.channel_id, selected_channel.id, cx)
.detach_and_log_err(cx)
}),
}
@ -304,45 +263,24 @@ pub fn init(cx: &mut AppContext) {
},
);
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(
|panel: &mut CollabPanel, action: &MoveChannel, cx: &mut ViewContext<CollabPanel>| {
if let Some(clipboard) = panel.channel_clipboard.take() {
panel.channel_store.update(cx, |channel_store, cx| {
if let Some(parent) = clipboard.parent_id {
channel_store
.move_channel(clipboard.channel_id, parent, action.to, cx)
.detach_and_log_err(cx)
} else {
channel_store
.link_channel(clipboard.channel_id, action.to, cx)
.detach_and_log_err(cx)
match clipboard.parent_id {
Some(parent_id) => channel_store.move_channel(
clipboard.channel_id,
parent_id,
action.to,
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)]
@ -2235,18 +2173,11 @@ impl CollabPanel {
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
.global::<DragAndDrop<Workspace>>()
.currently_dragged::<DraggedChannel>(cx.window())
{
if e.modifiers.alt {
this.channel_store.update(cx, |channel_store, cx| {
channel_store
.link_channel(dragged_channel.0.id, channel_id, cx)
.detach_and_log_err(cx)
})
} else {
this.channel_store.update(cx, |channel_store, cx| {
match dragged_channel.1 {
Some(parent_id) => channel_store.move_channel(
@ -2255,14 +2186,11 @@ impl CollabPanel {
channel_id,
cx,
),
None => {
channel_store.link_channel(dragged_channel.0.id, channel_id, cx)
}
None => channel_store.link_channel(dragged_channel.0.id, channel_id, cx),
}
.detach_and_log_err(cx)
})
}
}
})
.on_move({
let channel = channel.clone();
@ -2288,18 +2216,10 @@ impl CollabPanel {
})
.as_draggable(
(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;
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(
Svg::new("icons/hash.svg")
.with_color(theme.channel_hash.color)
@ -2743,19 +2663,6 @@ impl CollabPanel {
},
),
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(
"Move this channel",
StartMoveChannelFor {
@ -2763,13 +2670,6 @@ impl CollabPanel {
parent_id,
},
),
ContextMenuItem::action(
"Link this channel",
StartLinkChannelFor {
channel_id: path.channel_id(),
parent_id,
},
),
]);
if let Some(channel_name) = channel_name {
@ -2780,12 +2680,6 @@ impl CollabPanel {
to: path.channel_id(),
},
));
items.push(ContextMenuItem::action(
format!("Link '#{}' here", channel_name),
LinkChannel {
to: path.channel_id(),
},
));
}
items.extend([

View file

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