Restrict DAG-related functionality, but retain infrastructure for implementing symlinks
This commit is contained in:
parent
70aed4a605
commit
0ce1ec5d15
6 changed files with 338 additions and 394 deletions
|
@ -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()
|
||||||
});
|
});
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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?
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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([
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue