channel projects (#8456)

Add plumbing for hosted projects. This will currently show them if they
exist
but provides no UX to create/rename/delete them.

Also changed the `ChannelId` type to not auto-cast to u64; this avoids
type
confusion if you have multiple id types.


Release Notes:

- N/A
This commit is contained in:
Conrad Irwin 2024-02-26 22:15:11 -07:00 committed by GitHub
parent 8cf36ae603
commit c31626717f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 446 additions and 144 deletions

View file

@ -5,7 +5,7 @@ pub mod room;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use audio::Audio; use audio::Audio;
use call_settings::CallSettings; use call_settings::CallSettings;
use client::{proto, Client, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE}; use client::{proto, ChannelId, Client, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE};
use collections::HashSet; use collections::HashSet;
use futures::{channel::oneshot, future::Shared, Future, FutureExt}; use futures::{channel::oneshot, future::Shared, Future, FutureExt};
use gpui::{ use gpui::{
@ -107,7 +107,7 @@ impl ActiveCall {
} }
} }
pub fn channel_id(&self, cx: &AppContext) -> Option<u64> { pub fn channel_id(&self, cx: &AppContext) -> Option<ChannelId> {
self.room()?.read(cx).channel_id() self.room()?.read(cx).channel_id()
} }
@ -336,7 +336,7 @@ impl ActiveCall {
pub fn join_channel( pub fn join_channel(
&mut self, &mut self,
channel_id: u64, channel_id: ChannelId,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<Result<Option<Model<Room>>>> { ) -> Task<Result<Option<Model<Room>>>> {
if let Some(room) = self.room().cloned() { if let Some(room) = self.room().cloned() {
@ -487,7 +487,7 @@ impl ActiveCall {
pub fn report_call_event_for_room( pub fn report_call_event_for_room(
operation: &'static str, operation: &'static str,
room_id: u64, room_id: u64,
channel_id: Option<u64>, channel_id: Option<ChannelId>,
client: &Arc<Client>, client: &Arc<Client>,
) { ) {
let telemetry = client.telemetry(); let telemetry = client.telemetry();
@ -497,7 +497,7 @@ pub fn report_call_event_for_room(
pub fn report_call_event_for_channel( pub fn report_call_event_for_channel(
operation: &'static str, operation: &'static str,
channel_id: u64, channel_id: ChannelId,
client: &Arc<Client>, client: &Arc<Client>,
cx: &AppContext, cx: &AppContext,
) { ) {

View file

@ -6,7 +6,7 @@ use anyhow::{anyhow, Result};
use audio::{Audio, Sound}; use audio::{Audio, Sound};
use client::{ use client::{
proto::{self, PeerId}, proto::{self, PeerId},
Client, ParticipantIndex, TypedEnvelope, User, UserStore, ChannelId, Client, ParticipantIndex, TypedEnvelope, User, UserStore,
}; };
use collections::{BTreeMap, HashMap, HashSet}; use collections::{BTreeMap, HashMap, HashSet};
use fs::Fs; use fs::Fs;
@ -27,7 +27,7 @@ pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum Event { pub enum Event {
RoomJoined { RoomJoined {
channel_id: Option<u64>, channel_id: Option<ChannelId>,
}, },
ParticipantLocationChanged { ParticipantLocationChanged {
participant_id: proto::PeerId, participant_id: proto::PeerId,
@ -53,13 +53,13 @@ pub enum Event {
project_id: u64, project_id: u64,
}, },
Left { Left {
channel_id: Option<u64>, channel_id: Option<ChannelId>,
}, },
} }
pub struct Room { pub struct Room {
id: u64, id: u64,
channel_id: Option<u64>, channel_id: Option<ChannelId>,
live_kit: Option<LiveKitRoom>, live_kit: Option<LiveKitRoom>,
status: RoomStatus, status: RoomStatus,
shared_projects: HashSet<WeakModel<Project>>, shared_projects: HashSet<WeakModel<Project>>,
@ -84,7 +84,7 @@ pub struct Room {
impl EventEmitter<Event> for Room {} impl EventEmitter<Event> for Room {}
impl Room { impl Room {
pub fn channel_id(&self) -> Option<u64> { pub fn channel_id(&self) -> Option<ChannelId> {
self.channel_id self.channel_id
} }
@ -106,7 +106,7 @@ impl Room {
fn new( fn new(
id: u64, id: u64,
channel_id: Option<u64>, channel_id: Option<ChannelId>,
live_kit_connection_info: Option<proto::LiveKitConnectionInfo>, live_kit_connection_info: Option<proto::LiveKitConnectionInfo>,
client: Arc<Client>, client: Arc<Client>,
user_store: Model<UserStore>, user_store: Model<UserStore>,
@ -273,13 +273,17 @@ impl Room {
} }
pub(crate) async fn join_channel( pub(crate) async fn join_channel(
channel_id: u64, channel_id: ChannelId,
client: Arc<Client>, client: Arc<Client>,
user_store: Model<UserStore>, user_store: Model<UserStore>,
cx: AsyncAppContext, cx: AsyncAppContext,
) -> Result<Model<Self>> { ) -> Result<Model<Self>> {
Self::from_join_response( Self::from_join_response(
client.request(proto::JoinChannel { channel_id }).await?, client
.request(proto::JoinChannel {
channel_id: channel_id.0,
})
.await?,
client, client,
user_store, user_store,
cx, cx,
@ -337,7 +341,7 @@ impl Room {
let room = cx.new_model(|cx| { let room = cx.new_model(|cx| {
Self::new( Self::new(
room_proto.id, room_proto.id,
response.channel_id, response.channel_id.map(ChannelId),
response.live_kit_connection_info, response.live_kit_connection_info,
client, client,
user_store, user_store,

View file

@ -11,7 +11,7 @@ pub use channel_chat::{
mentions_to_proto, ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId, mentions_to_proto, ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId,
MessageParams, MessageParams,
}; };
pub use channel_store::{Channel, ChannelEvent, ChannelId, ChannelMembership, ChannelStore}; pub use channel_store::{Channel, ChannelEvent, ChannelMembership, ChannelStore, HostedProjectId};
#[cfg(test)] #[cfg(test)]
mod channel_store_tests; mod channel_store_tests;

View file

@ -1,6 +1,6 @@
use crate::{Channel, ChannelId, ChannelStore}; use crate::{Channel, ChannelStore};
use anyhow::Result; use anyhow::Result;
use client::{Client, Collaborator, UserStore, ZED_ALWAYS_ACTIVE}; use client::{ChannelId, Client, Collaborator, UserStore, ZED_ALWAYS_ACTIVE};
use collections::HashMap; use collections::HashMap;
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task}; use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
use language::proto::serialize_version; use language::proto::serialize_version;
@ -51,7 +51,7 @@ impl ChannelBuffer {
) -> Result<Model<Self>> { ) -> Result<Model<Self>> {
let response = client let response = client
.request(proto::JoinChannelBuffer { .request(proto::JoinChannelBuffer {
channel_id: channel.id, channel_id: channel.id.0,
}) })
.await?; .await?;
let buffer_id = BufferId::new(response.buffer_id)?; let buffer_id = BufferId::new(response.buffer_id)?;
@ -68,7 +68,7 @@ impl ChannelBuffer {
})?; })?;
buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))??; buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))??;
let subscription = client.subscribe_to_entity(channel.id)?; let subscription = client.subscribe_to_entity(channel.id.0)?;
anyhow::Ok(cx.new_model(|cx| { anyhow::Ok(cx.new_model(|cx| {
cx.subscribe(&buffer, Self::on_buffer_update).detach(); cx.subscribe(&buffer, Self::on_buffer_update).detach();
@ -97,7 +97,7 @@ impl ChannelBuffer {
} }
self.client self.client
.send(proto::LeaveChannelBuffer { .send(proto::LeaveChannelBuffer {
channel_id: self.channel_id, channel_id: self.channel_id.0,
}) })
.log_err(); .log_err();
} }
@ -191,7 +191,7 @@ impl ChannelBuffer {
let operation = language::proto::serialize_operation(operation); let operation = language::proto::serialize_operation(operation);
self.client self.client
.send(proto::UpdateChannelBuffer { .send(proto::UpdateChannelBuffer {
channel_id: self.channel_id, channel_id: self.channel_id.0,
operations: vec![operation], operations: vec![operation],
}) })
.log_err(); .log_err();

View file

@ -1,9 +1,9 @@
use crate::{Channel, ChannelId, ChannelStore}; use crate::{Channel, ChannelStore};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use client::{ use client::{
proto, proto,
user::{User, UserStore}, user::{User, UserStore},
Client, Subscription, TypedEnvelope, UserId, ChannelId, Client, Subscription, TypedEnvelope, UserId,
}; };
use collections::HashSet; use collections::HashSet;
use futures::lock::Mutex; use futures::lock::Mutex;
@ -104,10 +104,12 @@ impl ChannelChat {
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<Model<Self>> { ) -> Result<Model<Self>> {
let channel_id = channel.id; let channel_id = channel.id;
let subscription = client.subscribe_to_entity(channel_id).unwrap(); let subscription = client.subscribe_to_entity(channel_id.0).unwrap();
let response = client let response = client
.request(proto::JoinChannelChat { channel_id }) .request(proto::JoinChannelChat {
channel_id: channel_id.0,
})
.await?; .await?;
let handle = cx.new_model(|cx| { let handle = cx.new_model(|cx| {
@ -143,7 +145,7 @@ impl ChannelChat {
fn release(&mut self, _: &mut AppContext) { fn release(&mut self, _: &mut AppContext) {
self.rpc self.rpc
.send(proto::LeaveChannelChat { .send(proto::LeaveChannelChat {
channel_id: self.channel_id, channel_id: self.channel_id.0,
}) })
.log_err(); .log_err();
} }
@ -200,7 +202,7 @@ impl ChannelChat {
Ok(cx.spawn(move |this, mut cx| async move { Ok(cx.spawn(move |this, mut cx| async move {
let outgoing_message_guard = outgoing_messages_lock.lock().await; let outgoing_message_guard = outgoing_messages_lock.lock().await;
let request = rpc.request(proto::SendChannelMessage { let request = rpc.request(proto::SendChannelMessage {
channel_id, channel_id: channel_id.0,
body: message.text, body: message.text,
nonce: Some(nonce.into()), nonce: Some(nonce.into()),
mentions: mentions_to_proto(&message.mentions), mentions: mentions_to_proto(&message.mentions),
@ -220,7 +222,7 @@ impl ChannelChat {
pub fn remove_message(&mut self, id: u64, cx: &mut ModelContext<Self>) -> Task<Result<()>> { pub fn remove_message(&mut self, id: u64, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
let response = self.rpc.request(proto::RemoveChannelMessage { let response = self.rpc.request(proto::RemoveChannelMessage {
channel_id: self.channel_id, channel_id: self.channel_id.0,
message_id: id, message_id: id,
}); });
cx.spawn(move |this, mut cx| async move { cx.spawn(move |this, mut cx| async move {
@ -245,7 +247,7 @@ impl ChannelChat {
async move { async move {
let response = rpc let response = rpc
.request(proto::GetChannelMessages { .request(proto::GetChannelMessages {
channel_id, channel_id: channel_id.0,
before_message_id, before_message_id,
}) })
.await?; .await?;
@ -323,7 +325,7 @@ impl ChannelChat {
{ {
self.rpc self.rpc
.send(proto::AckChannelMessage { .send(proto::AckChannelMessage {
channel_id: self.channel_id, channel_id: self.channel_id.0,
message_id: latest_message_id, message_id: latest_message_id,
}) })
.ok(); .ok();
@ -401,7 +403,11 @@ impl ChannelChat {
let channel_id = self.channel_id; let channel_id = self.channel_id;
cx.spawn(move |this, mut cx| { cx.spawn(move |this, mut cx| {
async move { async move {
let response = rpc.request(proto::JoinChannelChat { channel_id }).await?; let response = rpc
.request(proto::JoinChannelChat {
channel_id: channel_id.0,
})
.await?;
Self::handle_loaded_messages( Self::handle_loaded_messages(
this.clone(), this.clone(),
user_store.clone(), user_store.clone(),
@ -418,7 +424,7 @@ impl ChannelChat {
for pending_message in pending_messages { for pending_message in pending_messages {
let request = rpc.request(proto::SendChannelMessage { let request = rpc.request(proto::SendChannelMessage {
channel_id, channel_id: channel_id.0,
body: pending_message.body, body: pending_message.body,
mentions: mentions_to_proto(&pending_message.mentions), mentions: mentions_to_proto(&pending_message.mentions),
nonce: Some(pending_message.nonce.into()), nonce: Some(pending_message.nonce.into()),
@ -461,7 +467,7 @@ impl ChannelChat {
if self.acknowledged_message_ids.insert(id) { if self.acknowledged_message_ids.insert(id) {
self.rpc self.rpc
.send(proto::AckChannelMessage { .send(proto::AckChannelMessage {
channel_id: self.channel_id, channel_id: self.channel_id.0,
message_id: id, message_id: id,
}) })
.ok(); .ok();

View file

@ -3,7 +3,7 @@ mod channel_index;
use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat, ChannelMessage}; use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat, ChannelMessage};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use channel_index::ChannelIndex; use channel_index::ChannelIndex;
use client::{Client, Subscription, User, UserId, UserStore}; use client::{ChannelId, Client, Subscription, User, UserId, UserStore};
use collections::{hash_map, HashMap, HashSet}; use collections::{hash_map, HashMap, HashSet};
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt}; use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
use gpui::{ use gpui::{
@ -19,15 +19,16 @@ use rpc::{
use std::{mem, sync::Arc, time::Duration}; use std::{mem, sync::Arc, time::Duration};
use util::{async_maybe, maybe, ResultExt}; use util::{async_maybe, maybe, ResultExt};
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) { pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
let channel_store = let channel_store =
cx.new_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx)); cx.new_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
cx.set_global(GlobalChannelStore(channel_store)); cx.set_global(GlobalChannelStore(channel_store));
} }
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub struct HostedProjectId(pub u64);
pub type ChannelId = u64;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
struct NotesVersion { struct NotesVersion {
@ -35,11 +36,31 @@ struct NotesVersion {
version: clock::Global, version: clock::Global,
} }
#[derive(Debug, Clone)]
pub struct HostedProject {
id: HostedProjectId,
channel_id: ChannelId,
name: SharedString,
_visibility: proto::ChannelVisibility,
}
impl From<proto::HostedProject> for HostedProject {
fn from(project: proto::HostedProject) -> Self {
Self {
id: HostedProjectId(project.id),
channel_id: ChannelId(project.channel_id),
_visibility: project.visibility(),
name: project.name.into(),
}
}
}
pub struct ChannelStore { pub struct ChannelStore {
pub channel_index: ChannelIndex, pub channel_index: ChannelIndex,
channel_invitations: Vec<Arc<Channel>>, channel_invitations: Vec<Arc<Channel>>,
channel_participants: HashMap<ChannelId, Vec<Arc<User>>>, channel_participants: HashMap<ChannelId, Vec<Arc<User>>>,
channel_states: HashMap<ChannelId, ChannelState>, channel_states: HashMap<ChannelId, ChannelState>,
hosted_projects: HashMap<HostedProjectId, HostedProject>,
outgoing_invites: HashSet<(ChannelId, UserId)>, outgoing_invites: HashSet<(ChannelId, UserId)>,
update_channels_tx: mpsc::UnboundedSender<proto::UpdateChannels>, update_channels_tx: mpsc::UnboundedSender<proto::UpdateChannels>,
@ -58,7 +79,7 @@ pub struct Channel {
pub id: ChannelId, pub id: ChannelId,
pub name: SharedString, pub name: SharedString,
pub visibility: proto::ChannelVisibility, pub visibility: proto::ChannelVisibility,
pub parent_path: Vec<u64>, pub parent_path: Vec<ChannelId>,
} }
#[derive(Default)] #[derive(Default)]
@ -68,6 +89,7 @@ pub struct ChannelState {
observed_chat_message: Option<u64>, observed_chat_message: Option<u64>,
observed_notes_versions: Option<NotesVersion>, observed_notes_versions: Option<NotesVersion>,
role: Option<ChannelRole>, role: Option<ChannelRole>,
projects: HashSet<HostedProjectId>,
} }
impl Channel { impl Channel {
@ -92,10 +114,7 @@ impl Channel {
} }
pub fn root_id(&self) -> ChannelId { pub fn root_id(&self) -> ChannelId {
self.parent_path self.parent_path.first().copied().unwrap_or(self.id)
.first()
.map(|id| *id as ChannelId)
.unwrap_or(self.id)
} }
pub fn slug(str: &str) -> String { pub fn slug(str: &str) -> String {
@ -199,6 +218,7 @@ impl ChannelStore {
channel_invitations: Vec::default(), channel_invitations: Vec::default(),
channel_index: ChannelIndex::default(), channel_index: ChannelIndex::default(),
channel_participants: Default::default(), channel_participants: Default::default(),
hosted_projects: Default::default(),
outgoing_invites: Default::default(), outgoing_invites: Default::default(),
opened_buffers: Default::default(), opened_buffers: Default::default(),
opened_chats: Default::default(), opened_chats: Default::default(),
@ -285,6 +305,19 @@ impl ChannelStore {
self.channel_index.by_id().get(&channel_id) self.channel_index.by_id().get(&channel_id)
} }
pub fn projects_for_id(&self, channel_id: ChannelId) -> Vec<(SharedString, HostedProjectId)> {
let mut projects: Vec<(SharedString, HostedProjectId)> = self
.channel_states
.get(&channel_id)
.map(|state| state.projects.clone())
.unwrap_or_default()
.into_iter()
.flat_map(|id| Some((self.hosted_projects.get(&id)?.name.clone(), id)))
.collect();
projects.sort();
projects
}
pub fn has_open_channel_buffer(&self, channel_id: ChannelId, _cx: &AppContext) -> bool { pub fn has_open_channel_buffer(&self, channel_id: ChannelId, _cx: &AppContext) -> bool {
if let Some(buffer) = self.opened_buffers.get(&channel_id) { if let Some(buffer) = self.opened_buffers.get(&channel_id) {
if let OpenedModelHandle::Open(buffer) = buffer { if let OpenedModelHandle::Open(buffer) = buffer {
@ -562,13 +595,16 @@ impl ChannelStore {
let name = name.trim_start_matches("#").to_owned(); let name = name.trim_start_matches("#").to_owned();
cx.spawn(move |this, mut cx| async move { cx.spawn(move |this, mut cx| async move {
let response = client let response = client
.request(proto::CreateChannel { name, parent_id }) .request(proto::CreateChannel {
name,
parent_id: parent_id.map(|cid| cid.0),
})
.await?; .await?;
let channel = response let channel = response
.channel .channel
.ok_or_else(|| anyhow!("missing channel in response"))?; .ok_or_else(|| anyhow!("missing channel in response"))?;
let channel_id = channel.id; let channel_id = ChannelId(channel.id);
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
let task = this.update_channels( let task = this.update_channels(
@ -600,7 +636,10 @@ impl ChannelStore {
let client = self.client.clone(); let client = self.client.clone();
cx.spawn(move |_, _| async move { cx.spawn(move |_, _| async move {
let _ = client let _ = client
.request(proto::MoveChannel { channel_id, to }) .request(proto::MoveChannel {
channel_id: channel_id.0,
to: to.0,
})
.await?; .await?;
Ok(()) Ok(())
@ -617,7 +656,7 @@ impl ChannelStore {
cx.spawn(move |_, _| async move { cx.spawn(move |_, _| async move {
let _ = client let _ = client
.request(proto::SetChannelVisibility { .request(proto::SetChannelVisibility {
channel_id, channel_id: channel_id.0,
visibility: visibility.into(), visibility: visibility.into(),
}) })
.await?; .await?;
@ -642,7 +681,7 @@ impl ChannelStore {
cx.spawn(move |this, mut cx| async move { cx.spawn(move |this, mut cx| async move {
let result = client let result = client
.request(proto::InviteChannelMember { .request(proto::InviteChannelMember {
channel_id, channel_id: channel_id.0,
user_id, user_id,
role: role.into(), role: role.into(),
}) })
@ -674,7 +713,7 @@ impl ChannelStore {
cx.spawn(move |this, mut cx| async move { cx.spawn(move |this, mut cx| async move {
let result = client let result = client
.request(proto::RemoveChannelMember { .request(proto::RemoveChannelMember {
channel_id, channel_id: channel_id.0,
user_id, user_id,
}) })
.await; .await;
@ -704,7 +743,7 @@ impl ChannelStore {
cx.spawn(move |this, mut cx| async move { cx.spawn(move |this, mut cx| async move {
let result = client let result = client
.request(proto::SetChannelMemberRole { .request(proto::SetChannelMemberRole {
channel_id, channel_id: channel_id.0,
user_id, user_id,
role: role.into(), role: role.into(),
}) })
@ -730,7 +769,10 @@ impl ChannelStore {
let name = new_name.to_string(); let name = new_name.to_string();
cx.spawn(move |this, mut cx| async move { cx.spawn(move |this, mut cx| async move {
let channel = client let channel = client
.request(proto::RenameChannel { channel_id, name }) .request(proto::RenameChannel {
channel_id: channel_id.0,
name,
})
.await? .await?
.channel .channel
.ok_or_else(|| anyhow!("missing channel in response"))?; .ok_or_else(|| anyhow!("missing channel in response"))?;
@ -763,7 +805,10 @@ impl ChannelStore {
let client = self.client.clone(); let client = self.client.clone();
cx.background_executor().spawn(async move { cx.background_executor().spawn(async move {
client client
.request(proto::RespondToChannelInvite { channel_id, accept }) .request(proto::RespondToChannelInvite {
channel_id: channel_id.0,
accept,
})
.await?; .await?;
Ok(()) Ok(())
}) })
@ -778,7 +823,9 @@ impl ChannelStore {
let user_store = self.user_store.downgrade(); let user_store = self.user_store.downgrade();
cx.spawn(move |_, mut cx| async move { cx.spawn(move |_, mut cx| async move {
let response = client let response = client
.request(proto::GetChannelMembers { channel_id }) .request(proto::GetChannelMembers {
channel_id: channel_id.0,
})
.await?; .await?;
let user_ids = response.members.iter().map(|m| m.user_id).collect(); let user_ids = response.members.iter().map(|m| m.user_id).collect();
@ -806,7 +853,11 @@ impl ChannelStore {
pub fn remove_channel(&self, channel_id: ChannelId) -> impl Future<Output = Result<()>> { pub fn remove_channel(&self, channel_id: ChannelId) -> impl Future<Output = Result<()>> {
let client = self.client.clone(); let client = self.client.clone();
async move { async move {
client.request(proto::DeleteChannel { channel_id }).await?; client
.request(proto::DeleteChannel {
channel_id: channel_id.0,
})
.await?;
Ok(()) Ok(())
} }
} }
@ -843,19 +894,23 @@ impl ChannelStore {
for buffer_version in message.payload.observed_channel_buffer_version { for buffer_version in message.payload.observed_channel_buffer_version {
let version = language::proto::deserialize_version(&buffer_version.version); let version = language::proto::deserialize_version(&buffer_version.version);
this.acknowledge_notes_version( this.acknowledge_notes_version(
buffer_version.channel_id, ChannelId(buffer_version.channel_id),
buffer_version.epoch, buffer_version.epoch,
&version, &version,
cx, cx,
); );
} }
for message_id in message.payload.observed_channel_message_id { for message_id in message.payload.observed_channel_message_id {
this.acknowledge_message_id(message_id.channel_id, message_id.message_id, cx); this.acknowledge_message_id(
ChannelId(message_id.channel_id),
message_id.message_id,
cx,
);
} }
for membership in message.payload.channel_memberships { for membership in message.payload.channel_memberships {
if let Some(role) = ChannelRole::from_i32(membership.role) { if let Some(role) = ChannelRole::from_i32(membership.role) {
this.channel_states this.channel_states
.entry(membership.channel_id) .entry(ChannelId(membership.channel_id))
.or_insert_with(|| ChannelState::default()) .or_insert_with(|| ChannelState::default())
.set_role(role) .set_role(role)
} }
@ -888,7 +943,7 @@ impl ChannelStore {
let channel_buffer = buffer.read(cx); let channel_buffer = buffer.read(cx);
let buffer = channel_buffer.buffer().read(cx); let buffer = channel_buffer.buffer().read(cx);
buffer_versions.push(proto::ChannelBufferVersion { buffer_versions.push(proto::ChannelBufferVersion {
channel_id: channel_buffer.channel_id, channel_id: channel_buffer.channel_id.0,
epoch: channel_buffer.epoch(), epoch: channel_buffer.epoch(),
version: language::proto::serialize_version(&buffer.version()), version: language::proto::serialize_version(&buffer.version()),
}); });
@ -919,7 +974,7 @@ impl ChannelStore {
if let Some(remote_buffer) = response if let Some(remote_buffer) = response
.buffers .buffers
.iter_mut() .iter_mut()
.find(|buffer| buffer.channel_id == channel_id) .find(|buffer| buffer.channel_id == channel_id.0)
{ {
let channel_id = channel_buffer.channel_id; let channel_id = channel_buffer.channel_id;
let remote_version = let remote_version =
@ -955,7 +1010,7 @@ impl ChannelStore {
{ {
client client
.send(proto::UpdateChannelBuffer { .send(proto::UpdateChannelBuffer {
channel_id, channel_id: channel_id.0,
operations: chunk, operations: chunk,
}) })
.ok(); .ok();
@ -1010,12 +1065,12 @@ impl ChannelStore {
) -> Option<Task<Result<()>>> { ) -> Option<Task<Result<()>>> {
if !payload.remove_channel_invitations.is_empty() { if !payload.remove_channel_invitations.is_empty() {
self.channel_invitations self.channel_invitations
.retain(|channel| !payload.remove_channel_invitations.contains(&channel.id)); .retain(|channel| !payload.remove_channel_invitations.contains(&channel.id.0));
} }
for channel in payload.channel_invitations { for channel in payload.channel_invitations {
match self match self
.channel_invitations .channel_invitations
.binary_search_by_key(&channel.id, |c| c.id) .binary_search_by_key(&channel.id, |c| c.id.0)
{ {
Ok(ix) => { Ok(ix) => {
Arc::make_mut(&mut self.channel_invitations[ix]).name = channel.name.into() Arc::make_mut(&mut self.channel_invitations[ix]).name = channel.name.into()
@ -1023,10 +1078,14 @@ impl ChannelStore {
Err(ix) => self.channel_invitations.insert( Err(ix) => self.channel_invitations.insert(
ix, ix,
Arc::new(Channel { Arc::new(Channel {
id: channel.id, id: ChannelId(channel.id),
visibility: channel.visibility(), visibility: channel.visibility(),
name: channel.name.into(), name: channel.name.into(),
parent_path: channel.parent_path, parent_path: channel
.parent_path
.into_iter()
.map(|cid| ChannelId(cid))
.collect(),
}), }),
), ),
} }
@ -1035,20 +1094,27 @@ impl ChannelStore {
let channels_changed = !payload.channels.is_empty() let channels_changed = !payload.channels.is_empty()
|| !payload.delete_channels.is_empty() || !payload.delete_channels.is_empty()
|| !payload.latest_channel_message_ids.is_empty() || !payload.latest_channel_message_ids.is_empty()
|| !payload.latest_channel_buffer_versions.is_empty(); || !payload.latest_channel_buffer_versions.is_empty()
|| !payload.hosted_projects.is_empty()
|| !payload.deleted_hosted_projects.is_empty();
if channels_changed { if channels_changed {
if !payload.delete_channels.is_empty() { if !payload.delete_channels.is_empty() {
self.channel_index.delete_channels(&payload.delete_channels); let delete_channels: Vec<ChannelId> = payload
.delete_channels
.into_iter()
.map(|cid| ChannelId(cid))
.collect();
self.channel_index.delete_channels(&delete_channels);
self.channel_participants self.channel_participants
.retain(|channel_id, _| !&payload.delete_channels.contains(channel_id)); .retain(|channel_id, _| !delete_channels.contains(&channel_id));
for channel_id in &payload.delete_channels { for channel_id in &delete_channels {
let channel_id = *channel_id; let channel_id = *channel_id;
if payload if payload
.channels .channels
.iter() .iter()
.any(|channel| channel.id == channel_id) .any(|channel| channel.id == channel_id.0)
{ {
continue; continue;
} }
@ -1064,7 +1130,7 @@ impl ChannelStore {
let mut index = self.channel_index.bulk_insert(); let mut index = self.channel_index.bulk_insert();
for channel in payload.channels { for channel in payload.channels {
let id = channel.id; let id = ChannelId(channel.id);
let channel_changed = index.insert(channel); let channel_changed = index.insert(channel);
if channel_changed { if channel_changed {
@ -1079,17 +1145,45 @@ impl ChannelStore {
for latest_buffer_version in payload.latest_channel_buffer_versions { for latest_buffer_version in payload.latest_channel_buffer_versions {
let version = language::proto::deserialize_version(&latest_buffer_version.version); let version = language::proto::deserialize_version(&latest_buffer_version.version);
self.channel_states self.channel_states
.entry(latest_buffer_version.channel_id) .entry(ChannelId(latest_buffer_version.channel_id))
.or_default() .or_default()
.update_latest_notes_version(latest_buffer_version.epoch, &version) .update_latest_notes_version(latest_buffer_version.epoch, &version)
} }
for latest_channel_message in payload.latest_channel_message_ids { for latest_channel_message in payload.latest_channel_message_ids {
self.channel_states self.channel_states
.entry(latest_channel_message.channel_id) .entry(ChannelId(latest_channel_message.channel_id))
.or_default() .or_default()
.update_latest_message_id(latest_channel_message.message_id); .update_latest_message_id(latest_channel_message.message_id);
} }
for hosted_project in payload.hosted_projects {
let hosted_project: HostedProject = hosted_project.into();
if let Some(old_project) = self
.hosted_projects
.insert(hosted_project.id, hosted_project.clone())
{
self.channel_states
.entry(old_project.channel_id)
.or_default()
.remove_hosted_project(old_project.id);
}
self.channel_states
.entry(hosted_project.channel_id)
.or_default()
.add_hosted_project(hosted_project.id);
}
for hosted_project_id in payload.deleted_hosted_projects {
let hosted_project_id = HostedProjectId(hosted_project_id);
if let Some(old_project) = self.hosted_projects.remove(&hosted_project_id) {
self.channel_states
.entry(old_project.channel_id)
.or_default()
.remove_hosted_project(old_project.id);
}
}
} }
cx.notify(); cx.notify();
@ -1129,7 +1223,7 @@ impl ChannelStore {
participants.sort_by_key(|u| u.id); participants.sort_by_key(|u| u.id);
this.channel_participants this.channel_participants
.insert(entry.channel_id, participants); .insert(ChannelId(entry.channel_id), participants);
} }
cx.notify(); cx.notify();
@ -1207,4 +1301,12 @@ impl ChannelState {
version: version.clone(), version: version.clone(),
}); });
} }
fn add_hosted_project(&mut self, project_id: HostedProjectId) {
self.projects.insert(project_id);
}
fn remove_hosted_project(&mut self, project_id: HostedProjectId) {
self.projects.remove(&project_id);
}
} }

View file

@ -1,4 +1,5 @@
use crate::{Channel, ChannelId}; use crate::Channel;
use client::ChannelId;
use collections::BTreeMap; use collections::BTreeMap;
use rpc::proto; use rpc::proto;
use std::sync::Arc; use std::sync::Arc;
@ -50,27 +51,32 @@ pub struct ChannelPathsInsertGuard<'a> {
impl<'a> ChannelPathsInsertGuard<'a> { impl<'a> ChannelPathsInsertGuard<'a> {
pub fn insert(&mut self, channel_proto: proto::Channel) -> bool { pub fn insert(&mut self, channel_proto: proto::Channel) -> bool {
let mut ret = false; let mut ret = false;
if let Some(existing_channel) = self.channels_by_id.get_mut(&channel_proto.id) { let parent_path = channel_proto
.parent_path
.iter()
.map(|cid| ChannelId(*cid))
.collect();
if let Some(existing_channel) = self.channels_by_id.get_mut(&ChannelId(channel_proto.id)) {
let existing_channel = Arc::make_mut(existing_channel); let existing_channel = Arc::make_mut(existing_channel);
ret = existing_channel.visibility != channel_proto.visibility() ret = existing_channel.visibility != channel_proto.visibility()
|| existing_channel.name != channel_proto.name || existing_channel.name != channel_proto.name
|| existing_channel.parent_path != channel_proto.parent_path; || existing_channel.parent_path != parent_path;
existing_channel.visibility = channel_proto.visibility(); existing_channel.visibility = channel_proto.visibility();
existing_channel.name = channel_proto.name.into(); existing_channel.name = channel_proto.name.into();
existing_channel.parent_path = channel_proto.parent_path.into(); existing_channel.parent_path = parent_path;
} else { } else {
self.channels_by_id.insert( self.channels_by_id.insert(
channel_proto.id, ChannelId(channel_proto.id),
Arc::new(Channel { Arc::new(Channel {
id: channel_proto.id, id: ChannelId(channel_proto.id),
visibility: channel_proto.visibility(), visibility: channel_proto.visibility(),
name: channel_proto.name.into(), name: channel_proto.name.into(),
parent_path: channel_proto.parent_path, parent_path,
}), }),
); );
self.insert_root(channel_proto.id); self.insert_root(ChannelId(channel_proto.id));
} }
ret ret
} }
@ -94,7 +100,7 @@ impl<'a> Drop for ChannelPathsInsertGuard<'a> {
fn channel_path_sorting_key<'a>( fn channel_path_sorting_key<'a>(
id: ChannelId, id: ChannelId,
channels_by_id: &'a BTreeMap<ChannelId, Arc<Channel>>, channels_by_id: &'a BTreeMap<ChannelId, Arc<Channel>>,
) -> impl Iterator<Item = (&str, u64)> { ) -> impl Iterator<Item = (&str, ChannelId)> {
let (parent_path, name) = channels_by_id let (parent_path, name) = channels_by_id
.get(&id) .get(&id)
.map_or((&[] as &[_], None), |channel| { .map_or((&[] as &[_], None), |channel| {

View file

@ -1,6 +1,6 @@
mod event_coalescer; mod event_coalescer;
use crate::TelemetrySettings; use crate::{ChannelId, TelemetrySettings};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use clock::SystemClock; use clock::SystemClock;
use futures::Future; use futures::Future;
@ -278,12 +278,12 @@ impl Telemetry {
self: &Arc<Self>, self: &Arc<Self>,
operation: &'static str, operation: &'static str,
room_id: Option<u64>, room_id: Option<u64>,
channel_id: Option<u64>, channel_id: Option<ChannelId>,
) { ) {
let event = Event::Call(CallEvent { let event = Event::Call(CallEvent {
operation: operation.to_string(), operation: operation.to_string(),
room_id, room_id,
channel_id, channel_id: channel_id.map(|cid| cid.0),
}); });
self.report_event(event) self.report_event(event)

View file

@ -15,6 +15,15 @@ use util::TryFutureExt as _;
pub type UserId = u64; pub type UserId = u64;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub struct ChannelId(pub u64);
impl std::fmt::Display for ChannelId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ParticipantIndex(pub u32); pub struct ParticipantIndex(pub u32);

View file

@ -375,3 +375,13 @@ CREATE TABLE extension_versions (
CREATE UNIQUE INDEX "index_extensions_external_id" ON "extensions" ("external_id"); CREATE UNIQUE INDEX "index_extensions_external_id" ON "extensions" ("external_id");
CREATE INDEX "index_extensions_total_download_count" ON "extensions" ("total_download_count"); CREATE INDEX "index_extensions_total_download_count" ON "extensions" ("total_download_count");
CREATE TABLE hosted_projects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
channel_id INTEGER NOT NULL REFERENCES channels(id),
name TEXT NOT NULL,
visibility TEXT NOT NULL,
deleted_at TIMESTAMP NULL
);
CREATE INDEX idx_hosted_projects_on_channel_id ON hosted_projects (channel_id);
CREATE UNIQUE INDEX uix_hosted_projects_on_channel_id_and_name ON hosted_projects (channel_id, name) WHERE (deleted_at IS NULL);

View file

@ -0,0 +1,11 @@
-- Add migration script here
CREATE TABLE hosted_projects (
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
channel_id INT NOT NULL REFERENCES channels(id),
name TEXT NOT NULL,
visibility TEXT NOT NULL,
deleted_at TIMESTAMP NULL
);
CREATE INDEX idx_hosted_projects_on_channel_id ON hosted_projects (channel_id);
CREATE UNIQUE INDEX uix_hosted_projects_on_channel_id_and_name ON hosted_projects (channel_id, name) WHERE (deleted_at IS NULL);

View file

@ -587,6 +587,7 @@ pub struct ChannelsForUser {
pub channels: Vec<Channel>, pub channels: Vec<Channel>,
pub channel_memberships: Vec<channel_member::Model>, pub channel_memberships: Vec<channel_member::Model>,
pub channel_participants: HashMap<ChannelId, Vec<UserId>>, pub channel_participants: HashMap<ChannelId, Vec<UserId>>,
pub hosted_projects: Vec<proto::HostedProject>,
pub observed_buffer_versions: Vec<proto::ChannelBufferVersion>, pub observed_buffer_versions: Vec<proto::ChannelBufferVersion>,
pub observed_channel_messages: Vec<proto::ChannelMessageId>, pub observed_channel_messages: Vec<proto::ChannelMessageId>,

View file

@ -88,6 +88,7 @@ id_type!(FlagId);
id_type!(ExtensionId); id_type!(ExtensionId);
id_type!(NotificationId); id_type!(NotificationId);
id_type!(NotificationKindId); id_type!(NotificationKindId);
id_type!(HostedProjectId);
/// ChannelRole gives you permissions for both channels and calls. /// ChannelRole gives you permissions for both channels and calls.
#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default, Hash)] #[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default, Hash)]

View file

@ -6,6 +6,7 @@ pub mod channels;
pub mod contacts; pub mod contacts;
pub mod contributors; pub mod contributors;
pub mod extensions; pub mod extensions;
pub mod hosted_projects;
pub mod messages; pub mod messages;
pub mod notifications; pub mod notifications;
pub mod projects; pub mod projects;

View file

@ -652,9 +652,14 @@ impl Database {
.observed_channel_messages(&channel_ids, user_id, &*tx) .observed_channel_messages(&channel_ids, user_id, &*tx)
.await?; .await?;
let hosted_projects = self
.get_hosted_projects(&channel_ids, &roles_by_channel_id, &*tx)
.await?;
Ok(ChannelsForUser { Ok(ChannelsForUser {
channel_memberships, channel_memberships,
channels, channels,
hosted_projects,
channel_participants, channel_participants,
latest_buffer_versions, latest_buffer_versions,
latest_channel_messages, latest_channel_messages,

View file

@ -0,0 +1,42 @@
use rpc::proto;
use super::*;
impl Database {
pub async fn get_hosted_projects(
&self,
channel_ids: &Vec<ChannelId>,
roles: &HashMap<ChannelId, ChannelRole>,
tx: &DatabaseTransaction,
) -> Result<Vec<proto::HostedProject>> {
Ok(hosted_project::Entity::find()
.filter(hosted_project::Column::ChannelId.is_in(channel_ids.iter().map(|id| id.0)))
.all(&*tx)
.await?
.into_iter()
.flat_map(|project| {
if project.deleted_at.is_some() {
return None;
}
match project.visibility {
ChannelVisibility::Public => {}
ChannelVisibility::Members => {
let is_visible = roles
.get(&project.channel_id)
.map(|role| role.can_see_all_descendants())
.unwrap_or(false);
if !is_visible {
return None;
}
}
};
Some(proto::HostedProject {
id: project.id.to_proto(),
channel_id: project.channel_id.to_proto(),
name: project.name.clone(),
visibility: project.visibility.into(),
})
})
.collect())
}
}

View file

@ -14,6 +14,7 @@ pub mod extension;
pub mod extension_version; pub mod extension_version;
pub mod feature_flag; pub mod feature_flag;
pub mod follower; pub mod follower;
pub mod hosted_project;
pub mod language_server; pub mod language_server;
pub mod notification; pub mod notification;
pub mod notification_kind; pub mod notification_kind;

View file

@ -0,0 +1,18 @@
use crate::db::{ChannelId, ChannelVisibility, HostedProjectId};
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "hosted_projects")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: HostedProjectId,
pub channel_id: ChannelId,
pub name: String,
pub visibility: ChannelVisibility,
pub deleted_at: Option<DateTime>,
}
impl ActiveModelBehavior for ActiveModel {}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}

View file

@ -3396,6 +3396,9 @@ fn build_channels_update(
for channel in channel_invites { for channel in channel_invites {
update.channel_invitations.push(channel.to_proto()); update.channel_invitations.push(channel.to_proto());
} }
for project in channels.hosted_projects {
update.hosted_projects.push(project);
}
update update
} }

View file

@ -1,4 +1,5 @@
use call::Room; use call::Room;
use client::ChannelId;
use gpui::{Model, TestAppContext}; use gpui::{Model, TestAppContext};
mod channel_buffer_tests; mod channel_buffer_tests;
@ -43,6 +44,6 @@ fn room_participants(room: &Model<Room>, cx: &mut TestAppContext) -> RoomPartici
}) })
} }
fn channel_id(room: &Model<Room>, cx: &mut TestAppContext) -> Option<u64> { fn channel_id(room: &Model<Room>, cx: &mut TestAppContext) -> Option<ChannelId> {
cx.read(|cx| room.read(cx).channel_id()) cx.read(|cx| room.read(cx).channel_id())
} }

View file

@ -183,7 +183,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes
server server
.app_state .app_state
.db .db
.set_channel_requires_zed_cla(ChannelId::from_proto(parent_channel_id), true) .set_channel_requires_zed_cla(ChannelId::from_proto(parent_channel_id.0), true)
.await .await
.unwrap(); .unwrap();

View file

@ -100,13 +100,13 @@ async fn test_basic_channel_messages(
Notification::ChannelMessageMention { Notification::ChannelMessageMention {
message_id, message_id,
sender_id: client_a.id(), sender_id: client_a.id(),
channel_id, channel_id: channel_id.0,
} }
); );
assert_eq!( assert_eq!(
store.notification_at(1).unwrap().notification, store.notification_at(1).unwrap().notification,
Notification::ChannelInvitation { Notification::ChannelInvitation {
channel_id, channel_id: channel_id.0,
channel_name: "the-channel".to_string(), channel_name: "the-channel".to_string(),
inviter_id: client_a.id() inviter_id: client_a.id()
} }

View file

@ -4,8 +4,8 @@ use crate::{
tests::{room_participants, RoomParticipants, TestServer}, tests::{room_participants, RoomParticipants, TestServer},
}; };
use call::ActiveCall; use call::ActiveCall;
use channel::{ChannelId, ChannelMembership, ChannelStore}; use channel::{ChannelMembership, ChannelStore};
use client::User; use client::{ChannelId, User};
use futures::future::try_join_all; use futures::future::try_join_all;
use gpui::{BackgroundExecutor, Model, SharedString, TestAppContext}; use gpui::{BackgroundExecutor, Model, SharedString, TestAppContext};
use rpc::{ use rpc::{
@ -281,7 +281,7 @@ async fn test_core_channels(
.app_state .app_state
.db .db
.rename_channel( .rename_channel(
db::ChannelId::from_proto(channel_a_id), db::ChannelId::from_proto(channel_a_id.0),
UserId::from_proto(client_a.id()), UserId::from_proto(client_a.id()),
"channel-a-renamed", "channel-a-renamed",
) )
@ -1444,7 +1444,7 @@ fn assert_channels(
fn assert_channels_list_shape( fn assert_channels_list_shape(
channel_store: &Model<ChannelStore>, channel_store: &Model<ChannelStore>,
cx: &TestAppContext, cx: &TestAppContext,
expected_channels: &[(u64, usize)], expected_channels: &[(ChannelId, usize)],
) { ) {
let actual = cx.read(|cx| { let actual = cx.read(|cx| {
channel_store.read_with(cx, |store, _| { channel_store.read_with(cx, |store, _| {

View file

@ -1,5 +1,6 @@
use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer}; use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
use call::{ActiveCall, ParticipantLocation}; use call::{ActiveCall, ParticipantLocation};
use client::ChannelId;
use collab_ui::{ use collab_ui::{
channel_view::ChannelView, channel_view::ChannelView,
notifications::project_shared_notification::ProjectSharedNotification, notifications::project_shared_notification::ProjectSharedNotification,
@ -2000,7 +2001,7 @@ async fn test_following_to_channel_notes_without_a_shared_project(
} }
async fn join_channel( async fn join_channel(
channel_id: u64, channel_id: ChannelId,
client: &TestClient, client: &TestClient,
cx: &mut TestAppContext, cx: &mut TestAppContext,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {

View file

@ -137,7 +137,7 @@ async fn test_notifications(
assert_eq!( assert_eq!(
entry.notification, entry.notification,
Notification::ChannelInvitation { Notification::ChannelInvitation {
channel_id, channel_id: channel_id.0,
channel_name: "the-channel".to_string(), channel_name: "the-channel".to_string(),
inviter_id: client_a.id() inviter_id: client_a.id()
} }

View file

@ -253,7 +253,7 @@ impl RandomizedTest for RandomChannelBufferTest {
.channel_buffers() .channel_buffers()
.deref() .deref()
.iter() .iter()
.find(|b| b.read(cx).channel_id == channel_id.to_proto()) .find(|b| b.read(cx).channel_id.0 == channel_id.to_proto())
{ {
let channel_buffer = channel_buffer.read(cx); let channel_buffer = channel_buffer.read(cx);

View file

@ -8,7 +8,8 @@ use anyhow::anyhow;
use call::ActiveCall; use call::ActiveCall;
use channel::{ChannelBuffer, ChannelStore}; use channel::{ChannelBuffer, ChannelStore};
use client::{ use client::{
self, proto::PeerId, Client, Connection, Credentials, EstablishConnectionError, UserStore, self, proto::PeerId, ChannelId, Client, Connection, Credentials, EstablishConnectionError,
UserStore,
}; };
use clock::FakeSystemClock; use clock::FakeSystemClock;
use collab_ui::channel_view::ChannelView; use collab_ui::channel_view::ChannelView;
@ -120,7 +121,7 @@ impl TestServer {
pub async fn start2( pub async fn start2(
cx_a: &mut TestAppContext, cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext, cx_b: &mut TestAppContext,
) -> (TestServer, TestClient, TestClient, u64) { ) -> (TestServer, TestClient, TestClient, ChannelId) {
let mut server = Self::start(cx_a.executor()).await; let mut server = Self::start(cx_a.executor()).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;
@ -353,10 +354,10 @@ impl TestServer {
pub async fn make_channel( pub async fn make_channel(
&self, &self,
channel: &str, channel: &str,
parent: Option<u64>, parent: Option<ChannelId>,
admin: (&TestClient, &mut TestAppContext), admin: (&TestClient, &mut TestAppContext),
members: &mut [(&TestClient, &mut TestAppContext)], members: &mut [(&TestClient, &mut TestAppContext)],
) -> u64 { ) -> ChannelId {
let (_, admin_cx) = admin; let (_, admin_cx) = admin;
let channel_id = admin_cx let channel_id = admin_cx
.read(ChannelStore::global) .read(ChannelStore::global)
@ -399,7 +400,7 @@ impl TestServer {
channel: &str, channel: &str,
client: &TestClient, client: &TestClient,
cx: &mut TestAppContext, cx: &mut TestAppContext,
) -> u64 { ) -> ChannelId {
let channel_id = self let channel_id = self
.make_channel(channel, None, (client, cx), &mut []) .make_channel(channel, None, (client, cx), &mut [])
.await; .await;
@ -423,7 +424,7 @@ impl TestServer {
&self, &self,
channels: &[(&str, Option<&str>)], channels: &[(&str, Option<&str>)],
creator: (&TestClient, &mut TestAppContext), creator: (&TestClient, &mut TestAppContext),
) -> Vec<u64> { ) -> Vec<ChannelId> {
let mut observed_channels = HashMap::default(); let mut observed_channels = HashMap::default();
let mut result = Vec::new(); let mut result = Vec::new();
for (channel, parent) in channels { for (channel, parent) in channels {
@ -677,7 +678,7 @@ impl TestClient {
pub async fn host_workspace( pub async fn host_workspace(
&self, &self,
workspace: &View<Workspace>, workspace: &View<Workspace>,
channel_id: u64, channel_id: ChannelId,
cx: &mut VisualTestContext, cx: &mut VisualTestContext,
) { ) {
cx.update(|cx| { cx.update(|cx| {
@ -698,7 +699,7 @@ impl TestClient {
pub async fn join_workspace<'a>( pub async fn join_workspace<'a>(
&'a self, &'a self,
channel_id: u64, channel_id: ChannelId,
cx: &'a mut TestAppContext, cx: &'a mut TestAppContext,
) -> (View<Workspace>, &'a mut VisualTestContext) { ) -> (View<Workspace>, &'a mut VisualTestContext) {
cx.update(|cx| workspace::join_channel(channel_id, self.app_state.clone(), None, cx)) cx.update(|cx| workspace::join_channel(channel_id, self.app_state.clone(), None, cx))
@ -777,7 +778,7 @@ impl TestClient {
} }
pub fn open_channel_notes( pub fn open_channel_notes(
channel_id: u64, channel_id: ChannelId,
cx: &mut VisualTestContext, cx: &mut VisualTestContext,
) -> Task<anyhow::Result<View<ChannelView>>> { ) -> Task<anyhow::Result<View<ChannelView>>> {
let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap()); let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());

View file

@ -1,9 +1,9 @@
use anyhow::Result; use anyhow::Result;
use call::report_call_event_for_channel; use call::report_call_event_for_channel;
use channel::{Channel, ChannelBuffer, ChannelBufferEvent, ChannelId, ChannelStore}; use channel::{Channel, ChannelBuffer, ChannelBufferEvent, ChannelStore};
use client::{ use client::{
proto::{self, PeerId}, proto::{self, PeerId},
Collaborator, ParticipantIndex, ChannelId, Collaborator, ParticipantIndex,
}; };
use collections::HashMap; use collections::HashMap;
use editor::{ use editor::{
@ -454,7 +454,7 @@ impl FollowableItem for ChannelView {
Some(proto::view::Variant::ChannelView( Some(proto::view::Variant::ChannelView(
proto::view::ChannelView { proto::view::ChannelView {
channel_id: channel_buffer.channel_id, channel_id: channel_buffer.channel_id.0,
editor: if let Some(proto::view::Variant::Editor(proto)) = editor: if let Some(proto::view::Variant::Editor(proto)) =
self.editor.read(cx).to_state_proto(cx) self.editor.read(cx).to_state_proto(cx)
{ {
@ -480,7 +480,8 @@ impl FollowableItem for ChannelView {
unreachable!() unreachable!()
}; };
let open = ChannelView::open_in_pane(state.channel_id, None, pane, workspace, cx); let open =
ChannelView::open_in_pane(ChannelId(state.channel_id), None, pane, workspace, cx);
Some(cx.spawn(|mut cx| async move { Some(cx.spawn(|mut cx| async move {
let this = open.await?; let this = open.await?;

View file

@ -2,7 +2,7 @@ use crate::{collab_panel, ChatPanelSettings};
use anyhow::Result; use anyhow::Result;
use call::{room, ActiveCall}; use call::{room, ActiveCall};
use channel::{ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId, ChannelStore}; use channel::{ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId, ChannelStore};
use client::Client; use client::{ChannelId, Client};
use collections::HashMap; use collections::HashMap;
use db::kvp::KEY_VALUE_STORE; use db::kvp::KEY_VALUE_STORE;
use editor::Editor; use editor::Editor;
@ -169,7 +169,7 @@ impl ChatPanel {
}) })
} }
pub fn channel_id(&self, cx: &AppContext) -> Option<u64> { pub fn channel_id(&self, cx: &AppContext) -> Option<ChannelId> {
self.active_chat self.active_chat
.as_ref() .as_ref()
.map(|(chat, _)| chat.read(cx).channel_id) .map(|(chat, _)| chat.read(cx).channel_id)
@ -710,7 +710,7 @@ impl ChatPanel {
pub fn select_channel( pub fn select_channel(
&mut self, &mut self,
selected_channel_id: u64, selected_channel_id: ChannelId,
scroll_to_message_id: Option<u64>, scroll_to_message_id: Option<u64>,
cx: &mut ViewContext<ChatPanel>, cx: &mut ViewContext<ChatPanel>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {

View file

@ -1,6 +1,6 @@
use anyhow::Result; use anyhow::Result;
use channel::{ChannelId, ChannelMembership, ChannelStore, MessageParams}; use channel::{ChannelMembership, ChannelStore, MessageParams};
use client::UserId; use client::{ChannelId, UserId};
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use editor::{AnchorRangeExt, CompletionProvider, Editor, EditorElement, EditorStyle}; use editor::{AnchorRangeExt, CompletionProvider, Editor, EditorElement, EditorStyle};
use fuzzy::StringMatchCandidate; use fuzzy::StringMatchCandidate;
@ -131,7 +131,7 @@ impl MessageEditor {
pub fn set_channel( pub fn set_channel(
&mut self, &mut self,
channel_id: u64, channel_id: ChannelId,
channel_name: Option<SharedString>, channel_name: Option<SharedString>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {

View file

@ -7,8 +7,8 @@ use crate::{
CollaborationPanelSettings, CollaborationPanelSettings,
}; };
use call::ActiveCall; use call::ActiveCall;
use channel::{Channel, ChannelEvent, ChannelId, ChannelStore}; use channel::{Channel, ChannelEvent, ChannelStore, HostedProjectId};
use client::{Client, Contact, User, UserStore}; use client::{ChannelId, Client, Contact, User, UserStore};
use contact_finder::ContactFinder; use contact_finder::ContactFinder;
use db::kvp::KEY_VALUE_STORE; use db::kvp::KEY_VALUE_STORE;
use editor::{Editor, EditorElement, EditorStyle}; use editor::{Editor, EditorElement, EditorStyle};
@ -184,6 +184,10 @@ enum ListEntry {
ChannelEditor { ChannelEditor {
depth: usize, depth: usize,
}, },
HostedProject {
id: HostedProjectId,
name: SharedString,
},
Contact { Contact {
contact: Arc<Contact>, contact: Arc<Contact>,
calling: bool, calling: bool,
@ -326,7 +330,10 @@ impl CollabPanel {
panel.width = serialized_panel.width; panel.width = serialized_panel.width;
panel.collapsed_channels = serialized_panel panel.collapsed_channels = serialized_panel
.collapsed_channels .collapsed_channels
.unwrap_or_else(|| Vec::new()); .unwrap_or_else(|| Vec::new())
.iter()
.map(|cid| ChannelId(*cid))
.collect();
cx.notify(); cx.notify();
}); });
} }
@ -344,7 +351,9 @@ impl CollabPanel {
COLLABORATION_PANEL_KEY.into(), COLLABORATION_PANEL_KEY.into(),
serde_json::to_string(&SerializedCollabPanel { serde_json::to_string(&SerializedCollabPanel {
width, width,
collapsed_channels: Some(collapsed_channels), collapsed_channels: Some(
collapsed_channels.iter().map(|cid| cid.0).collect(),
),
})?, })?,
) )
.await?; .await?;
@ -563,6 +572,7 @@ impl CollabPanel {
} }
} }
let hosted_projects = channel_store.projects_for_id(channel.id);
let has_children = channel_store let has_children = channel_store
.channel_at_index(mat.candidate_id + 1) .channel_at_index(mat.candidate_id + 1)
.map_or(false, |next_channel| { .map_or(false, |next_channel| {
@ -596,6 +606,10 @@ impl CollabPanel {
}); });
} }
} }
for (name, id) in hosted_projects {
self.entries.push(ListEntry::HostedProject { id, name })
}
} }
} }
@ -1023,6 +1037,33 @@ impl CollabPanel {
.tooltip(move |cx| Tooltip::text("Open Chat", cx)) .tooltip(move |cx| Tooltip::text("Open Chat", cx))
} }
fn render_channel_project(
&self,
id: HostedProjectId,
name: &SharedString,
is_selected: bool,
cx: &mut ViewContext<Self>,
) -> impl IntoElement {
ListItem::new(ElementId::NamedInteger(
"channel-project".into(),
id.0 as usize,
))
.indent_level(2)
.indent_step_size(px(20.))
.selected(is_selected)
.on_click(cx.listener(move |_this, _, _cx| {
// todo!()
}))
.start_slot(
h_flex()
.relative()
.gap_1()
.child(IconButton::new(0, IconName::FileTree)),
)
.child(Label::new(name.clone()))
.tooltip(move |cx| Tooltip::text("Open Project", cx))
}
fn has_subchannels(&self, ix: usize) -> bool { fn has_subchannels(&self, ix: usize) -> bool {
self.entries.get(ix).map_or(false, |entry| { self.entries.get(ix).map_or(false, |entry| {
if let ListEntry::Channel { has_children, .. } = entry { if let ListEntry::Channel { has_children, .. } = entry {
@ -1486,6 +1527,12 @@ impl CollabPanel {
ListEntry::ChannelChat { channel_id } => { ListEntry::ChannelChat { channel_id } => {
self.join_channel_chat(*channel_id, cx) self.join_channel_chat(*channel_id, cx)
} }
ListEntry::HostedProject {
id: _id,
name: _name,
} => {
// todo!()
}
ListEntry::OutgoingRequest(_) => {} ListEntry::OutgoingRequest(_) => {}
ListEntry::ChannelEditor { .. } => {} ListEntry::ChannelEditor { .. } => {}
@ -1923,7 +1970,7 @@ impl CollabPanel {
fn respond_to_channel_invite( fn respond_to_channel_invite(
&mut self, &mut self,
channel_id: u64, channel_id: ChannelId,
accept: bool, accept: bool,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
@ -1942,7 +1989,7 @@ impl CollabPanel {
.detach_and_prompt_err("Call failed", cx, |_, _| None); .detach_and_prompt_err("Call failed", cx, |_, _| None);
} }
fn join_channel(&self, channel_id: u64, cx: &mut ViewContext<Self>) { fn join_channel(&self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
let Some(workspace) = self.workspace.upgrade() else { let Some(workspace) = self.workspace.upgrade() else {
return; return;
}; };
@ -2089,6 +2136,10 @@ impl CollabPanel {
ListEntry::ChannelChat { channel_id } => self ListEntry::ChannelChat { channel_id } => self
.render_channel_chat(*channel_id, is_selected, cx) .render_channel_chat(*channel_id, is_selected, cx)
.into_any_element(), .into_any_element(),
ListEntry::HostedProject { id, name } => self
.render_channel_project(*id, name, is_selected, cx)
.into_any_element(),
} }
} }
@ -2405,7 +2456,7 @@ impl CollabPanel {
.tooltip(|cx| Tooltip::text("Accept invite", cx)), .tooltip(|cx| Tooltip::text("Accept invite", cx)),
]; ];
ListItem::new(("channel-invite", channel.id as usize)) ListItem::new(("channel-invite", channel.id.0 as usize))
.selected(is_selected) .selected(is_selected)
.child( .child(
h_flex() h_flex()
@ -2497,7 +2548,7 @@ impl CollabPanel {
div() div()
.h_6() .h_6()
.id(channel_id as usize) .id(channel_id.0 as usize)
.group("") .group("")
.flex() .flex()
.w_full() .w_full()
@ -2525,7 +2576,7 @@ impl CollabPanel {
this.move_channel(dragged_channel.id, channel_id, cx); this.move_channel(dragged_channel.id, channel_id, cx);
})) }))
.child( .child(
ListItem::new(channel_id as usize) ListItem::new(channel_id.0 as usize)
// Add one level of depth for the disclosure arrow. // Add one level of depth for the disclosure arrow.
.indent_level(depth + 1) .indent_level(depth + 1)
.indent_step_size(px(20.)) .indent_step_size(px(20.))
@ -2572,7 +2623,7 @@ impl CollabPanel {
) )
.child( .child(
h_flex() h_flex()
.id(channel_id as usize) .id(channel_id.0 as usize)
.child(Label::new(channel.name.clone())) .child(Label::new(channel.name.clone()))
.children(face_pile.map(|face_pile| face_pile.p_1())), .children(face_pile.map(|face_pile| face_pile.p_1())),
), ),
@ -2826,6 +2877,11 @@ impl PartialEq for ListEntry {
return channel_1.id == channel_2.id; return channel_1.id == channel_2.id;
} }
} }
ListEntry::HostedProject { id, .. } => {
if let ListEntry::HostedProject { id: other_id, .. } = other {
return id == other_id;
}
}
ListEntry::ChannelNotes { channel_id } => { ListEntry::ChannelNotes { channel_id } => {
if let ListEntry::ChannelNotes { if let ListEntry::ChannelNotes {
channel_id: other_id, channel_id: other_id,

View file

@ -1,7 +1,7 @@
use channel::{ChannelId, ChannelMembership, ChannelStore}; use channel::{ChannelMembership, ChannelStore};
use client::{ use client::{
proto::{self, ChannelRole, ChannelVisibility}, proto::{self, ChannelRole, ChannelVisibility},
User, UserId, UserStore, ChannelId, User, UserId, UserStore,
}; };
use fuzzy::{match_strings, StringMatchCandidate}; use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{ use gpui::{

View file

@ -1,7 +1,7 @@
use crate::{chat_panel::ChatPanel, NotificationPanelSettings}; use crate::{chat_panel::ChatPanel, NotificationPanelSettings};
use anyhow::Result; use anyhow::Result;
use channel::ChannelStore; use channel::ChannelStore;
use client::{Client, Notification, User, UserStore}; use client::{ChannelId, Client, Notification, User, UserStore};
use collections::HashMap; use collections::HashMap;
use db::kvp::KEY_VALUE_STORE; use db::kvp::KEY_VALUE_STORE;
use futures::StreamExt; use futures::StreamExt;
@ -357,7 +357,7 @@ impl NotificationPanel {
"{} invited you to join the #{channel_name} channel", "{} invited you to join the #{channel_name} channel",
inviter.github_login inviter.github_login
), ),
needs_response: channel_store.has_channel_invitation(channel_id), needs_response: channel_store.has_channel_invitation(ChannelId(channel_id)),
actor: Some(inviter), actor: Some(inviter),
can_navigate: false, can_navigate: false,
}) })
@ -368,7 +368,7 @@ impl NotificationPanel {
message_id, message_id,
} => { } => {
let sender = user_store.get_cached_user(sender_id)?; let sender = user_store.get_cached_user(sender_id)?;
let channel = channel_store.channel_for_id(channel_id)?; let channel = channel_store.channel_for_id(ChannelId(channel_id))?;
let message = self let message = self
.notification_store .notification_store
.read(cx) .read(cx)
@ -432,7 +432,7 @@ impl NotificationPanel {
if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) { if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
panel.update(cx, |panel, cx| { panel.update(cx, |panel, cx| {
panel panel
.select_channel(channel_id, Some(message_id), cx) .select_channel(ChannelId(channel_id), Some(message_id), cx)
.detach_and_log_err(cx); .detach_and_log_err(cx);
}); });
} }
@ -454,7 +454,7 @@ impl NotificationPanel {
panel.is_scrolled_to_bottom() panel.is_scrolled_to_bottom()
&& panel && panel
.active_chat() .active_chat()
.map_or(false, |chat| chat.read(cx).channel_id == *channel_id) .map_or(false, |chat| chat.read(cx).channel_id.0 == *channel_id)
} else { } else {
false false
}; };

View file

@ -1,6 +1,6 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use channel::{ChannelMessage, ChannelMessageId, ChannelStore}; use channel::{ChannelMessage, ChannelMessageId, ChannelStore};
use client::{Client, UserStore}; use client::{ChannelId, Client, UserStore};
use collections::HashMap; use collections::HashMap;
use db::smol::stream::StreamExt; use db::smol::stream::StreamExt;
use gpui::{ use gpui::{
@ -413,7 +413,7 @@ impl NotificationStore {
Notification::ChannelInvitation { channel_id, .. } => { Notification::ChannelInvitation { channel_id, .. } => {
self.channel_store self.channel_store
.update(cx, |store, cx| { .update(cx, |store, cx| {
store.respond_to_channel_invite(channel_id, response, cx) store.respond_to_channel_invite(ChannelId(channel_id), response, cx)
}) })
.detach(); .detach();
} }

View file

@ -12,7 +12,7 @@ message Envelope {
uint32 id = 1; uint32 id = 1;
optional uint32 responding_to = 2; optional uint32 responding_to = 2;
optional PeerId original_sender_id = 3; optional PeerId original_sender_id = 3;
/* /*
When you are adding a new message type, instead of adding it in semantic order When you are adding a new message type, instead of adding it in semantic order
and bumping the message ID's of everything that follows, add it at the end of the and bumping the message ID's of everything that follows, add it at the end of the
@ -56,7 +56,7 @@ message Envelope {
GetDefinitionResponse get_definition_response = 33; GetDefinitionResponse get_definition_response = 33;
GetTypeDefinition get_type_definition = 34; GetTypeDefinition get_type_definition = 34;
GetTypeDefinitionResponse get_type_definition_response = 35; GetTypeDefinitionResponse get_type_definition_response = 35;
GetReferences get_references = 36; GetReferences get_references = 36;
GetReferencesResponse get_references_response = 37; GetReferencesResponse get_references_response = 37;
GetDocumentHighlights get_document_highlights = 38; GetDocumentHighlights get_document_highlights = 38;
@ -192,7 +192,7 @@ message Envelope {
LspExtExpandMacroResponse lsp_ext_expand_macro_response = 155; LspExtExpandMacroResponse lsp_ext_expand_macro_response = 155;
SetRoomParticipantRole set_room_participant_role = 156; SetRoomParticipantRole set_room_participant_role = 156;
UpdateUserChannels update_user_channels = 157; UpdateUserChannels update_user_channels = 157;
GetImplementation get_implementation = 162; GetImplementation get_implementation = 162;
GetImplementationResponse get_implementation_response = 163; GetImplementationResponse get_implementation_response = 163;
@ -1026,6 +1026,9 @@ message UpdateChannels {
repeated ChannelParticipants channel_participants = 7; repeated ChannelParticipants channel_participants = 7;
repeated ChannelMessageId latest_channel_message_ids = 8; repeated ChannelMessageId latest_channel_message_ids = 8;
repeated ChannelBufferVersion latest_channel_buffer_versions = 9; repeated ChannelBufferVersion latest_channel_buffer_versions = 9;
repeated HostedProject hosted_projects = 10;
repeated uint64 deleted_hosted_projects = 11;
} }
message UpdateUserChannels { message UpdateUserChannels {
@ -1054,6 +1057,13 @@ message ChannelParticipants {
repeated uint64 participant_user_ids = 2; repeated uint64 participant_user_ids = 2;
} }
message HostedProject {
uint64 id = 1;
uint64 channel_id = 2;
string name = 3;
ChannelVisibility visibility = 4;
}
message JoinChannel { message JoinChannel {
uint64 channel_id = 1; uint64 channel_id = 1;
} }

View file

@ -15,7 +15,7 @@ use anyhow::{anyhow, Context as _, Result};
use call::{call_settings::CallSettings, ActiveCall}; use call::{call_settings::CallSettings, ActiveCall};
use client::{ use client::{
proto::{self, ErrorCode, PeerId}, proto::{self, ErrorCode, PeerId},
Client, ErrorExt, Status, TypedEnvelope, UserStore, ChannelId, Client, ErrorExt, Status, TypedEnvelope, UserStore,
}; };
use collections::{hash_map, HashMap, HashSet}; use collections::{hash_map, HashMap, HashSet};
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
@ -4117,7 +4117,7 @@ pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
actions!(collab, [OpenChannelNotes]); actions!(collab, [OpenChannelNotes]);
async fn join_channel_internal( async fn join_channel_internal(
channel_id: u64, channel_id: ChannelId,
app_state: &Arc<AppState>, app_state: &Arc<AppState>,
requesting_window: Option<WindowHandle<Workspace>>, requesting_window: Option<WindowHandle<Workspace>>,
active_call: &Model<ActiveCall>, active_call: &Model<ActiveCall>,
@ -4257,7 +4257,7 @@ async fn join_channel_internal(
} }
pub fn join_channel( pub fn join_channel(
channel_id: u64, channel_id: ChannelId,
app_state: Arc<AppState>, app_state: Arc<AppState>,
requesting_window: Option<WindowHandle<Workspace>>, requesting_window: Option<WindowHandle<Workspace>>,
cx: &mut AppContext, cx: &mut AppContext,

View file

@ -323,8 +323,10 @@ fn main() {
cx.spawn(|cx| async move { cx.spawn(|cx| async move {
// ignore errors here, we'll show a generic "not signed in" // ignore errors here, we'll show a generic "not signed in"
let _ = authenticate(client, &cx).await; let _ = authenticate(client, &cx).await;
cx.update(|cx| workspace::join_channel(channel_id, app_state, None, cx))? cx.update(|cx| {
.await?; workspace::join_channel(client::ChannelId(channel_id), app_state, None, cx)
})?
.await?;
anyhow::Ok(()) anyhow::Ok(())
}) })
.detach_and_log_err(cx); .detach_and_log_err(cx);
@ -343,7 +345,7 @@ fn main() {
workspace::get_any_active_workspace(app_state, cx.clone()).await?; workspace::get_any_active_workspace(app_state, cx.clone()).await?;
let workspace = workspace_window.root_view(&cx)?; let workspace = workspace_window.root_view(&cx)?;
cx.update_window(workspace_window.into(), |_, cx| { cx.update_window(workspace_window.into(), |_, cx| {
ChannelView::open(channel_id, heading, workspace, cx) ChannelView::open(client::ChannelId(channel_id), heading, workspace, cx)
})? })?
.await?; .await?;
anyhow::Ok(()) anyhow::Ok(())
@ -378,7 +380,12 @@ fn main() {
cx.update(|mut cx| { cx.update(|mut cx| {
cx.spawn(|cx| async move { cx.spawn(|cx| async move {
cx.update(|cx| { cx.update(|cx| {
workspace::join_channel(channel_id, app_state, None, cx) workspace::join_channel(
client::ChannelId(channel_id),
app_state,
None,
cx,
)
})? })?
.await?; .await?;
anyhow::Ok(()) anyhow::Ok(())
@ -397,7 +404,12 @@ fn main() {
workspace::get_any_active_workspace(app_state, cx.clone()).await?; workspace::get_any_active_workspace(app_state, cx.clone()).await?;
let workspace = workspace_window.root_view(&cx)?; let workspace = workspace_window.root_view(&cx)?;
cx.update_window(workspace_window.into(), |_, cx| { cx.update_window(workspace_window.into(), |_, cx| {
ChannelView::open(channel_id, heading, workspace, cx) ChannelView::open(
client::ChannelId(channel_id),
heading,
workspace,
cx,
)
})? })?
.await?; .await?;
anyhow::Ok(()) anyhow::Ok(())