revert single channel click (#7738)
- Revert "collab tweaks (#7706)" - Revert "2112 (#7640)" - Revert "single click channel (#7596)" - Reserve protobufs - Don't revert migrations Release Notes: - N/A **or** - N/A
This commit is contained in:
parent
ecd9b93cb1
commit
2294d99046
26 changed files with 525 additions and 709 deletions
|
@ -104,8 +104,10 @@
|
||||||
"show_whitespaces": "selection",
|
"show_whitespaces": "selection",
|
||||||
// Settings related to calls in Zed
|
// Settings related to calls in Zed
|
||||||
"calls": {
|
"calls": {
|
||||||
// Join calls with the microphone muted by default
|
// Join calls with the microphone live by default
|
||||||
"mute_on_join": false
|
"mute_on_join": false,
|
||||||
|
// Share your project when you are the first to join a channel
|
||||||
|
"share_on_join": true
|
||||||
},
|
},
|
||||||
// Toolbar related settings
|
// Toolbar related settings
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
|
|
|
@ -84,7 +84,6 @@ pub struct ActiveCall {
|
||||||
),
|
),
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
user_store: Model<UserStore>,
|
user_store: Model<UserStore>,
|
||||||
pending_channel_id: Option<u64>,
|
|
||||||
_subscriptions: Vec<client::Subscription>,
|
_subscriptions: Vec<client::Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +97,6 @@ impl ActiveCall {
|
||||||
location: None,
|
location: None,
|
||||||
pending_invites: Default::default(),
|
pending_invites: Default::default(),
|
||||||
incoming_call: watch::channel(),
|
incoming_call: watch::channel(),
|
||||||
pending_channel_id: None,
|
|
||||||
_join_debouncer: OneAtATime { cancel: None },
|
_join_debouncer: OneAtATime { cancel: None },
|
||||||
_subscriptions: vec![
|
_subscriptions: vec![
|
||||||
client.add_request_handler(cx.weak_model(), Self::handle_incoming_call),
|
client.add_request_handler(cx.weak_model(), Self::handle_incoming_call),
|
||||||
|
@ -113,10 +111,6 @@ impl ActiveCall {
|
||||||
self.room()?.read(cx).channel_id()
|
self.room()?.read(cx).channel_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pending_channel_id(&self) -> Option<u64> {
|
|
||||||
self.pending_channel_id
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_incoming_call(
|
async fn handle_incoming_call(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
envelope: TypedEnvelope<proto::IncomingCall>,
|
envelope: TypedEnvelope<proto::IncomingCall>,
|
||||||
|
@ -345,13 +339,11 @@ impl ActiveCall {
|
||||||
channel_id: u64,
|
channel_id: u64,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Option<Model<Room>>>> {
|
) -> Task<Result<Option<Model<Room>>>> {
|
||||||
let mut leave = None;
|
|
||||||
if let Some(room) = self.room().cloned() {
|
if let Some(room) = self.room().cloned() {
|
||||||
if room.read(cx).channel_id() == Some(channel_id) {
|
if room.read(cx).channel_id() == Some(channel_id) {
|
||||||
return Task::ready(Ok(Some(room)));
|
return Task::ready(Ok(Some(room)));
|
||||||
} else {
|
} else {
|
||||||
let (room, _) = self.room.take().unwrap();
|
room.update(cx, |room, cx| room.clear_state(cx));
|
||||||
leave = room.update(cx, |room, cx| Some(room.leave(cx)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,21 +353,14 @@ impl ActiveCall {
|
||||||
|
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let user_store = self.user_store.clone();
|
let user_store = self.user_store.clone();
|
||||||
self.pending_channel_id = Some(channel_id);
|
|
||||||
let join = self._join_debouncer.spawn(cx, move |cx| async move {
|
let join = self._join_debouncer.spawn(cx, move |cx| async move {
|
||||||
if let Some(task) = leave {
|
|
||||||
task.await?
|
|
||||||
}
|
|
||||||
Room::join_channel(channel_id, client, user_store, cx).await
|
Room::join_channel(channel_id, client, user_store, cx).await
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let room = join.await?;
|
let room = join.await?;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
|
||||||
this.pending_channel_id.take();
|
.await?;
|
||||||
this.set_room(room.clone(), cx)
|
|
||||||
})?
|
|
||||||
.await?;
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.report_call_event("join channel", cx)
|
this.report_call_event("join channel", cx)
|
||||||
})?;
|
})?;
|
||||||
|
|
|
@ -7,6 +7,7 @@ use settings::Settings;
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct CallSettings {
|
pub struct CallSettings {
|
||||||
pub mute_on_join: bool,
|
pub mute_on_join: bool,
|
||||||
|
pub share_on_join: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration of voice calls in Zed.
|
/// Configuration of voice calls in Zed.
|
||||||
|
@ -16,6 +17,11 @@ pub struct CallSettingsContent {
|
||||||
///
|
///
|
||||||
/// Default: false
|
/// Default: false
|
||||||
pub mute_on_join: Option<bool>,
|
pub mute_on_join: Option<bool>,
|
||||||
|
|
||||||
|
/// Whether your current project should be shared when joining an empty channel.
|
||||||
|
///
|
||||||
|
/// Default: true
|
||||||
|
pub share_on_join: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Settings for CallSettings {
|
impl Settings for CallSettings {
|
||||||
|
|
|
@ -49,7 +49,6 @@ pub struct RemoteParticipant {
|
||||||
pub participant_index: ParticipantIndex,
|
pub participant_index: ParticipantIndex,
|
||||||
pub muted: bool,
|
pub muted: bool,
|
||||||
pub speaking: bool,
|
pub speaking: bool,
|
||||||
pub in_call: bool,
|
|
||||||
pub video_tracks: HashMap<live_kit_client::Sid, Arc<RemoteVideoTrack>>,
|
pub video_tracks: HashMap<live_kit_client::Sid, Arc<RemoteVideoTrack>>,
|
||||||
pub audio_tracks: HashMap<live_kit_client::Sid, Arc<RemoteAudioTrack>>,
|
pub audio_tracks: HashMap<live_kit_client::Sid, Arc<RemoteAudioTrack>>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,6 @@ pub struct Room {
|
||||||
id: u64,
|
id: u64,
|
||||||
channel_id: Option<u64>,
|
channel_id: Option<u64>,
|
||||||
live_kit: Option<LiveKitRoom>,
|
live_kit: Option<LiveKitRoom>,
|
||||||
live_kit_connection_info: Option<proto::LiveKitConnectionInfo>,
|
|
||||||
status: RoomStatus,
|
status: RoomStatus,
|
||||||
shared_projects: HashSet<WeakModel<Project>>,
|
shared_projects: HashSet<WeakModel<Project>>,
|
||||||
joined_projects: HashSet<WeakModel<Project>>,
|
joined_projects: HashSet<WeakModel<Project>>,
|
||||||
|
@ -113,18 +112,91 @@ impl Room {
|
||||||
user_store: Model<UserStore>,
|
user_store: Model<UserStore>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let live_kit_room = if let Some(connection_info) = live_kit_connection_info {
|
||||||
|
let room = live_kit_client::Room::new();
|
||||||
|
let mut status = room.status();
|
||||||
|
// Consume the initial status of the room.
|
||||||
|
let _ = status.try_recv();
|
||||||
|
let _maintain_room = cx.spawn(|this, mut cx| async move {
|
||||||
|
while let Some(status) = status.next().await {
|
||||||
|
let this = if let Some(this) = this.upgrade() {
|
||||||
|
this
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
if status == live_kit_client::ConnectionState::Disconnected {
|
||||||
|
this.update(&mut cx, |this, cx| this.leave(cx).log_err())
|
||||||
|
.ok();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let _handle_updates = cx.spawn({
|
||||||
|
let room = room.clone();
|
||||||
|
move |this, mut cx| async move {
|
||||||
|
let mut updates = room.updates();
|
||||||
|
while let Some(update) = updates.next().await {
|
||||||
|
let this = if let Some(this) = this.upgrade() {
|
||||||
|
this
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.live_kit_room_updated(update, cx).log_err()
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let connect = room.connect(&connection_info.server_url, &connection_info.token);
|
||||||
|
cx.spawn(|this, mut cx| async move {
|
||||||
|
connect.await?;
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
if !this.read_only() {
|
||||||
|
if let Some(live_kit) = &this.live_kit {
|
||||||
|
if !live_kit.muted_by_user && !live_kit.deafened {
|
||||||
|
return this.share_microphone(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Task::ready(Ok(()))
|
||||||
|
})?
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
|
||||||
|
Some(LiveKitRoom {
|
||||||
|
room,
|
||||||
|
screen_track: LocalTrack::None,
|
||||||
|
microphone_track: LocalTrack::None,
|
||||||
|
next_publish_id: 0,
|
||||||
|
muted_by_user: Self::mute_on_join(cx),
|
||||||
|
deafened: false,
|
||||||
|
speaking: false,
|
||||||
|
_maintain_room,
|
||||||
|
_handle_updates,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let maintain_connection = cx.spawn({
|
let maintain_connection = cx.spawn({
|
||||||
let client = client.clone();
|
let client = client.clone();
|
||||||
move |this, cx| Self::maintain_connection(this, client.clone(), cx).log_err()
|
move |this, cx| Self::maintain_connection(this, client.clone(), cx).log_err()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Audio::play_sound(Sound::Joined, cx);
|
||||||
|
|
||||||
let (room_update_completed_tx, room_update_completed_rx) = watch::channel();
|
let (room_update_completed_tx, room_update_completed_rx) = watch::channel();
|
||||||
|
|
||||||
let mut this = Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
channel_id,
|
channel_id,
|
||||||
live_kit: None,
|
live_kit: live_kit_room,
|
||||||
live_kit_connection_info,
|
|
||||||
status: RoomStatus::Online,
|
status: RoomStatus::Online,
|
||||||
shared_projects: Default::default(),
|
shared_projects: Default::default(),
|
||||||
joined_projects: Default::default(),
|
joined_projects: Default::default(),
|
||||||
|
@ -148,11 +220,7 @@ impl Room {
|
||||||
maintain_connection: Some(maintain_connection),
|
maintain_connection: Some(maintain_connection),
|
||||||
room_update_completed_tx,
|
room_update_completed_tx,
|
||||||
room_update_completed_rx,
|
room_update_completed_rx,
|
||||||
};
|
|
||||||
if this.live_kit_connection_info.is_some() {
|
|
||||||
this.join_call(cx).detach_and_log_err(cx);
|
|
||||||
}
|
}
|
||||||
this
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn create(
|
pub(crate) fn create(
|
||||||
|
@ -211,7 +279,7 @@ impl Room {
|
||||||
cx: AsyncAppContext,
|
cx: AsyncAppContext,
|
||||||
) -> Result<Model<Self>> {
|
) -> Result<Model<Self>> {
|
||||||
Self::from_join_response(
|
Self::from_join_response(
|
||||||
client.request(proto::JoinChannel2 { channel_id }).await?,
|
client.request(proto::JoinChannel { channel_id }).await?,
|
||||||
client,
|
client,
|
||||||
user_store,
|
user_store,
|
||||||
cx,
|
cx,
|
||||||
|
@ -256,7 +324,7 @@ impl Room {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mute_on_join(cx: &AppContext) -> bool {
|
pub fn mute_on_join(cx: &AppContext) -> bool {
|
||||||
CallSettings::get_global(cx).mute_on_join
|
CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_join_response(
|
fn from_join_response(
|
||||||
|
@ -306,9 +374,7 @@ impl Room {
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!("leaving room");
|
log::info!("leaving room");
|
||||||
if self.live_kit.is_some() {
|
Audio::play_sound(Sound::Leave, cx);
|
||||||
Audio::play_sound(Sound::Leave, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.clear_state(cx);
|
self.clear_state(cx);
|
||||||
|
|
||||||
|
@ -527,24 +593,6 @@ impl Room {
|
||||||
&self.remote_participants
|
&self.remote_participants
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn call_participants(&self, cx: &AppContext) -> Vec<Arc<User>> {
|
|
||||||
self.remote_participants()
|
|
||||||
.values()
|
|
||||||
.filter_map(|participant| {
|
|
||||||
if participant.in_call {
|
|
||||||
Some(participant.user.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.chain(if self.in_call() {
|
|
||||||
self.user_store.read(cx).current_user()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remote_participant_for_peer_id(&self, peer_id: PeerId) -> Option<&RemoteParticipant> {
|
pub fn remote_participant_for_peer_id(&self, peer_id: PeerId) -> Option<&RemoteParticipant> {
|
||||||
self.remote_participants
|
self.remote_participants
|
||||||
.values()
|
.values()
|
||||||
|
@ -569,6 +617,10 @@ impl Room {
|
||||||
self.local_participant.role == proto::ChannelRole::Admin
|
self.local_participant.role == proto::ChannelRole::Admin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn local_participant_is_guest(&self) -> bool {
|
||||||
|
self.local_participant.role == proto::ChannelRole::Guest
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_participant_role(
|
pub fn set_participant_role(
|
||||||
&mut self,
|
&mut self,
|
||||||
user_id: u64,
|
user_id: u64,
|
||||||
|
@ -776,7 +828,6 @@ impl Room {
|
||||||
}
|
}
|
||||||
|
|
||||||
let role = participant.role();
|
let role = participant.role();
|
||||||
let in_call = participant.in_call;
|
|
||||||
let location = ParticipantLocation::from_proto(participant.location)
|
let location = ParticipantLocation::from_proto(participant.location)
|
||||||
.unwrap_or(ParticipantLocation::External);
|
.unwrap_or(ParticipantLocation::External);
|
||||||
if let Some(remote_participant) =
|
if let Some(remote_participant) =
|
||||||
|
@ -787,15 +838,9 @@ impl Room {
|
||||||
remote_participant.participant_index = participant_index;
|
remote_participant.participant_index = participant_index;
|
||||||
if location != remote_participant.location
|
if location != remote_participant.location
|
||||||
|| role != remote_participant.role
|
|| role != remote_participant.role
|
||||||
|| in_call != remote_participant.in_call
|
|
||||||
{
|
{
|
||||||
if in_call && !remote_participant.in_call {
|
|
||||||
Audio::play_sound(Sound::Joined, cx);
|
|
||||||
}
|
|
||||||
remote_participant.location = location;
|
remote_participant.location = location;
|
||||||
remote_participant.role = role;
|
remote_participant.role = role;
|
||||||
remote_participant.in_call = participant.in_call;
|
|
||||||
|
|
||||||
cx.emit(Event::ParticipantLocationChanged {
|
cx.emit(Event::ParticipantLocationChanged {
|
||||||
participant_id: peer_id,
|
participant_id: peer_id,
|
||||||
});
|
});
|
||||||
|
@ -812,15 +857,12 @@ impl Room {
|
||||||
role,
|
role,
|
||||||
muted: true,
|
muted: true,
|
||||||
speaking: false,
|
speaking: false,
|
||||||
in_call: participant.in_call,
|
|
||||||
video_tracks: Default::default(),
|
video_tracks: Default::default(),
|
||||||
audio_tracks: Default::default(),
|
audio_tracks: Default::default(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if participant.in_call {
|
Audio::play_sound(Sound::Joined, cx);
|
||||||
Audio::play_sound(Sound::Joined, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(live_kit) = this.live_kit.as_ref() {
|
if let Some(live_kit) = this.live_kit.as_ref() {
|
||||||
let video_tracks =
|
let video_tracks =
|
||||||
|
@ -1009,6 +1051,15 @@ impl Room {
|
||||||
}
|
}
|
||||||
|
|
||||||
RoomUpdate::SubscribedToRemoteAudioTrack(track, publication) => {
|
RoomUpdate::SubscribedToRemoteAudioTrack(track, publication) => {
|
||||||
|
if let Some(live_kit) = &self.live_kit {
|
||||||
|
if live_kit.deafened {
|
||||||
|
track.stop();
|
||||||
|
cx.foreground_executor()
|
||||||
|
.spawn(publication.set_enabled(false))
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let user_id = track.publisher_id().parse()?;
|
let user_id = track.publisher_id().parse()?;
|
||||||
let track_id = track.sid().to_string();
|
let track_id = track.sid().to_string();
|
||||||
let participant = self
|
let participant = self
|
||||||
|
@ -1155,7 +1206,7 @@ impl Room {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn share_project(
|
pub fn share_project(
|
||||||
&mut self,
|
&mut self,
|
||||||
project: Model<Project>,
|
project: Model<Project>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
|
@ -1257,14 +1308,18 @@ impl Room {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_sharing_mic(&self) -> bool {
|
||||||
|
self.live_kit.as_ref().map_or(false, |live_kit| {
|
||||||
|
!matches!(live_kit.microphone_track, LocalTrack::None)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_muted(&self) -> bool {
|
pub fn is_muted(&self) -> bool {
|
||||||
self.live_kit
|
self.live_kit.as_ref().map_or(false, |live_kit| {
|
||||||
.as_ref()
|
matches!(live_kit.microphone_track, LocalTrack::None)
|
||||||
.map_or(true, |live_kit| match &live_kit.microphone_track {
|
|| live_kit.muted_by_user
|
||||||
LocalTrack::None => true,
|
|| live_kit.deafened
|
||||||
LocalTrack::Pending { .. } => true,
|
})
|
||||||
LocalTrack::Published { track_publication } => track_publication.is_muted(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_only(&self) -> bool {
|
pub fn read_only(&self) -> bool {
|
||||||
|
@ -1278,8 +1333,8 @@ impl Room {
|
||||||
.map_or(false, |live_kit| live_kit.speaking)
|
.map_or(false, |live_kit| live_kit.speaking)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn in_call(&self) -> bool {
|
pub fn is_deafened(&self) -> Option<bool> {
|
||||||
self.live_kit.is_some()
|
self.live_kit.as_ref().map(|live_kit| live_kit.deafened)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
@ -1332,8 +1387,12 @@ impl Room {
|
||||||
Ok(publication) => {
|
Ok(publication) => {
|
||||||
if canceled {
|
if canceled {
|
||||||
live_kit.room.unpublish_track(publication);
|
live_kit.room.unpublish_track(publication);
|
||||||
live_kit.microphone_track = LocalTrack::None;
|
|
||||||
} else {
|
} else {
|
||||||
|
if live_kit.muted_by_user || live_kit.deafened {
|
||||||
|
cx.background_executor()
|
||||||
|
.spawn(publication.set_mute(true))
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
live_kit.microphone_track = LocalTrack::Published {
|
live_kit.microphone_track = LocalTrack::Published {
|
||||||
track_publication: publication,
|
track_publication: publication,
|
||||||
};
|
};
|
||||||
|
@ -1437,140 +1496,50 @@ impl Room {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_mute(&mut self, cx: &mut ModelContext<Self>) {
|
pub fn toggle_mute(&mut self, cx: &mut ModelContext<Self>) {
|
||||||
let muted = !self.is_muted();
|
if let Some(live_kit) = self.live_kit.as_mut() {
|
||||||
if let Some(task) = self.set_mute(muted, cx) {
|
// When unmuting, undeafen if the user was deafened before.
|
||||||
task.detach_and_log_err(cx);
|
let was_deafened = live_kit.deafened;
|
||||||
|
if live_kit.muted_by_user
|
||||||
|
|| live_kit.deafened
|
||||||
|
|| matches!(live_kit.microphone_track, LocalTrack::None)
|
||||||
|
{
|
||||||
|
live_kit.muted_by_user = false;
|
||||||
|
live_kit.deafened = false;
|
||||||
|
} else {
|
||||||
|
live_kit.muted_by_user = true;
|
||||||
|
}
|
||||||
|
let muted = live_kit.muted_by_user;
|
||||||
|
let should_undeafen = was_deafened && !live_kit.deafened;
|
||||||
|
|
||||||
|
if let Some(task) = self.set_mute(muted, cx) {
|
||||||
|
task.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if should_undeafen {
|
||||||
|
if let Some(task) = self.set_deafened(false, cx) {
|
||||||
|
task.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn join_call(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
pub fn toggle_deafen(&mut self, cx: &mut ModelContext<Self>) {
|
||||||
if self.live_kit.is_some() {
|
if let Some(live_kit) = self.live_kit.as_mut() {
|
||||||
return Task::ready(Ok(()));
|
// When deafening, mute the microphone if it was not already muted.
|
||||||
}
|
// When un-deafening, unmute the microphone, unless it was explicitly muted.
|
||||||
|
let deafened = !live_kit.deafened;
|
||||||
|
live_kit.deafened = deafened;
|
||||||
|
let should_change_mute = !live_kit.muted_by_user;
|
||||||
|
|
||||||
let room = live_kit_client::Room::new();
|
if let Some(task) = self.set_deafened(deafened, cx) {
|
||||||
let mut status = room.status();
|
task.detach_and_log_err(cx);
|
||||||
// Consume the initial status of the room.
|
}
|
||||||
let _ = status.try_recv();
|
|
||||||
let _maintain_room = cx.spawn(|this, mut cx| async move {
|
|
||||||
while let Some(status) = status.next().await {
|
|
||||||
let this = if let Some(this) = this.upgrade() {
|
|
||||||
this
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
|
|
||||||
if status == live_kit_client::ConnectionState::Disconnected {
|
if should_change_mute {
|
||||||
this.update(&mut cx, |this, cx| this.leave(cx).log_err())
|
if let Some(task) = self.set_mute(deafened, cx) {
|
||||||
.ok();
|
task.detach_and_log_err(cx);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
let _handle_updates = cx.spawn({
|
|
||||||
let room = room.clone();
|
|
||||||
move |this, mut cx| async move {
|
|
||||||
let mut updates = room.updates();
|
|
||||||
while let Some(update) = updates.next().await {
|
|
||||||
let this = if let Some(this) = this.upgrade() {
|
|
||||||
this
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.live_kit_room_updated(update, cx).log_err()
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.live_kit = Some(LiveKitRoom {
|
|
||||||
room: room.clone(),
|
|
||||||
screen_track: LocalTrack::None,
|
|
||||||
microphone_track: LocalTrack::None,
|
|
||||||
next_publish_id: 0,
|
|
||||||
speaking: false,
|
|
||||||
_maintain_room,
|
|
||||||
_handle_updates,
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.spawn({
|
|
||||||
let client = self.client.clone();
|
|
||||||
let share_microphone = !self.read_only() && !Self::mute_on_join(cx);
|
|
||||||
let connection_info = self.live_kit_connection_info.clone();
|
|
||||||
let channel_id = self.channel_id;
|
|
||||||
|
|
||||||
move |this, mut cx| async move {
|
|
||||||
let connection_info = if let Some(connection_info) = connection_info {
|
|
||||||
connection_info.clone()
|
|
||||||
} else if let Some(channel_id) = channel_id {
|
|
||||||
if let Some(connection_info) = client
|
|
||||||
.request(proto::JoinChannelCall { channel_id })
|
|
||||||
.await?
|
|
||||||
.live_kit_connection_info
|
|
||||||
{
|
|
||||||
connection_info
|
|
||||||
} else {
|
|
||||||
return Err(anyhow!("failed to get connection info from server"));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"tried to connect to livekit without connection info"
|
|
||||||
));
|
|
||||||
};
|
|
||||||
room.connect(&connection_info.server_url, &connection_info.token)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let track_updates = this.update(&mut cx, |this, cx| {
|
|
||||||
Audio::play_sound(Sound::Joined, cx);
|
|
||||||
let Some(live_kit) = this.live_kit.as_mut() else {
|
|
||||||
return vec![];
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut track_updates = Vec::new();
|
|
||||||
for participant in this.remote_participants.values() {
|
|
||||||
for publication in live_kit
|
|
||||||
.room
|
|
||||||
.remote_audio_track_publications(&participant.user.id.to_string())
|
|
||||||
{
|
|
||||||
track_updates.push(publication.set_enabled(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
for track in participant.audio_tracks.values() {
|
|
||||||
track.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
track_updates
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if share_microphone {
|
|
||||||
this.update(&mut cx, |this, cx| this.share_microphone(cx))?
|
|
||||||
.await?
|
|
||||||
};
|
|
||||||
|
|
||||||
for result in futures::future::join_all(track_updates).await {
|
|
||||||
result?;
|
|
||||||
}
|
|
||||||
anyhow::Ok(())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn leave_call(&mut self, cx: &mut ModelContext<Self>) {
|
|
||||||
Audio::play_sound(Sound::Leave, cx);
|
|
||||||
if let Some(channel_id) = self.channel_id() {
|
|
||||||
let client = self.client.clone();
|
|
||||||
cx.background_executor()
|
|
||||||
.spawn(client.request(proto::LeaveChannelCall { channel_id }))
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
self.live_kit.take();
|
|
||||||
self.live_kit_connection_info.take();
|
|
||||||
cx.notify();
|
|
||||||
} else {
|
|
||||||
self.leave(cx).detach_and_log_err(cx)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1601,6 +1570,40 @@ impl Room {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_deafened(
|
||||||
|
&mut self,
|
||||||
|
deafened: bool,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Option<Task<Result<()>>> {
|
||||||
|
let live_kit = self.live_kit.as_mut()?;
|
||||||
|
cx.notify();
|
||||||
|
|
||||||
|
let mut track_updates = Vec::new();
|
||||||
|
for participant in self.remote_participants.values() {
|
||||||
|
for publication in live_kit
|
||||||
|
.room
|
||||||
|
.remote_audio_track_publications(&participant.user.id.to_string())
|
||||||
|
{
|
||||||
|
track_updates.push(publication.set_enabled(!deafened));
|
||||||
|
}
|
||||||
|
|
||||||
|
for track in participant.audio_tracks.values() {
|
||||||
|
if deafened {
|
||||||
|
track.stop();
|
||||||
|
} else {
|
||||||
|
track.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(cx.foreground_executor().spawn(async move {
|
||||||
|
for result in futures::future::join_all(track_updates).await {
|
||||||
|
result?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
fn set_mute(
|
fn set_mute(
|
||||||
&mut self,
|
&mut self,
|
||||||
should_mute: bool,
|
should_mute: bool,
|
||||||
|
@ -1645,6 +1648,9 @@ struct LiveKitRoom {
|
||||||
room: Arc<live_kit_client::Room>,
|
room: Arc<live_kit_client::Room>,
|
||||||
screen_track: LocalTrack,
|
screen_track: LocalTrack,
|
||||||
microphone_track: LocalTrack,
|
microphone_track: LocalTrack,
|
||||||
|
/// Tracks whether we're currently in a muted state due to auto-mute from deafening or manual mute performed by user.
|
||||||
|
muted_by_user: bool,
|
||||||
|
deafened: bool,
|
||||||
speaking: bool,
|
speaking: bool,
|
||||||
next_publish_id: usize,
|
next_publish_id: usize,
|
||||||
_maintain_room: Task<()>,
|
_maintain_room: Task<()>,
|
||||||
|
|
|
@ -97,57 +97,11 @@ impl Database {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_in_channel_call(
|
|
||||||
&self,
|
|
||||||
channel_id: ChannelId,
|
|
||||||
user_id: UserId,
|
|
||||||
in_call: bool,
|
|
||||||
) -> Result<(proto::Room, ChannelRole)> {
|
|
||||||
self.transaction(move |tx| async move {
|
|
||||||
let channel = self.get_channel_internal(channel_id, &*tx).await?;
|
|
||||||
let role = self.channel_role_for_user(&channel, user_id, &*tx).await?;
|
|
||||||
if role.is_none() || role == Some(ChannelRole::Banned) {
|
|
||||||
Err(ErrorCode::Forbidden.anyhow())?
|
|
||||||
}
|
|
||||||
let role = role.unwrap();
|
|
||||||
|
|
||||||
let Some(room) = room::Entity::find()
|
|
||||||
.filter(room::Column::ChannelId.eq(channel_id))
|
|
||||||
.one(&*tx)
|
|
||||||
.await?
|
|
||||||
else {
|
|
||||||
Err(anyhow!("no room exists"))?
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = room_participant::Entity::update_many()
|
|
||||||
.filter(
|
|
||||||
Condition::all()
|
|
||||||
.add(room_participant::Column::RoomId.eq(room.id))
|
|
||||||
.add(room_participant::Column::UserId.eq(user_id)),
|
|
||||||
)
|
|
||||||
.set(room_participant::ActiveModel {
|
|
||||||
in_call: ActiveValue::Set(in_call),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.exec(&*tx)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if result.rows_affected != 1 {
|
|
||||||
Err(anyhow!("not in channel"))?
|
|
||||||
}
|
|
||||||
|
|
||||||
let room = self.get_room(room.id, &*tx).await?;
|
|
||||||
Ok((room, role))
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a user to the specified channel.
|
/// Adds a user to the specified channel.
|
||||||
pub async fn join_channel(
|
pub async fn join_channel(
|
||||||
&self,
|
&self,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
user_id: UserId,
|
user_id: UserId,
|
||||||
autojoin: bool,
|
|
||||||
connection: ConnectionId,
|
connection: ConnectionId,
|
||||||
environment: &str,
|
environment: &str,
|
||||||
) -> Result<(JoinRoom, Option<MembershipUpdated>, ChannelRole)> {
|
) -> Result<(JoinRoom, Option<MembershipUpdated>, ChannelRole)> {
|
||||||
|
@ -212,7 +166,7 @@ impl Database {
|
||||||
.get_or_create_channel_room(channel_id, &live_kit_room, environment, &*tx)
|
.get_or_create_channel_room(channel_id, &live_kit_room, environment, &*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.join_channel_room_internal(room_id, user_id, autojoin, connection, role, &*tx)
|
self.join_channel_room_internal(room_id, user_id, connection, role, &*tx)
|
||||||
.await
|
.await
|
||||||
.map(|jr| (jr, accept_invite_result, role))
|
.map(|jr| (jr, accept_invite_result, role))
|
||||||
})
|
})
|
||||||
|
|
|
@ -135,7 +135,6 @@ impl Database {
|
||||||
))),
|
))),
|
||||||
participant_index: ActiveValue::set(Some(0)),
|
participant_index: ActiveValue::set(Some(0)),
|
||||||
role: ActiveValue::set(Some(ChannelRole::Admin)),
|
role: ActiveValue::set(Some(ChannelRole::Admin)),
|
||||||
in_call: ActiveValue::set(true),
|
|
||||||
|
|
||||||
id: ActiveValue::NotSet,
|
id: ActiveValue::NotSet,
|
||||||
location_kind: ActiveValue::NotSet,
|
location_kind: ActiveValue::NotSet,
|
||||||
|
@ -188,7 +187,6 @@ impl Database {
|
||||||
))),
|
))),
|
||||||
initial_project_id: ActiveValue::set(initial_project_id),
|
initial_project_id: ActiveValue::set(initial_project_id),
|
||||||
role: ActiveValue::set(Some(called_user_role)),
|
role: ActiveValue::set(Some(called_user_role)),
|
||||||
in_call: ActiveValue::set(true),
|
|
||||||
|
|
||||||
id: ActiveValue::NotSet,
|
id: ActiveValue::NotSet,
|
||||||
answering_connection_id: ActiveValue::NotSet,
|
answering_connection_id: ActiveValue::NotSet,
|
||||||
|
@ -416,7 +414,6 @@ impl Database {
|
||||||
&self,
|
&self,
|
||||||
room_id: RoomId,
|
room_id: RoomId,
|
||||||
user_id: UserId,
|
user_id: UserId,
|
||||||
autojoin: bool,
|
|
||||||
connection: ConnectionId,
|
connection: ConnectionId,
|
||||||
role: ChannelRole,
|
role: ChannelRole,
|
||||||
tx: &DatabaseTransaction,
|
tx: &DatabaseTransaction,
|
||||||
|
@ -440,8 +437,6 @@ impl Database {
|
||||||
))),
|
))),
|
||||||
participant_index: ActiveValue::Set(Some(participant_index)),
|
participant_index: ActiveValue::Set(Some(participant_index)),
|
||||||
role: ActiveValue::set(Some(role)),
|
role: ActiveValue::set(Some(role)),
|
||||||
in_call: ActiveValue::set(autojoin),
|
|
||||||
|
|
||||||
id: ActiveValue::NotSet,
|
id: ActiveValue::NotSet,
|
||||||
location_kind: ActiveValue::NotSet,
|
location_kind: ActiveValue::NotSet,
|
||||||
location_project_id: ActiveValue::NotSet,
|
location_project_id: ActiveValue::NotSet,
|
||||||
|
@ -1263,7 +1258,6 @@ impl Database {
|
||||||
location: Some(proto::ParticipantLocation { variant: location }),
|
location: Some(proto::ParticipantLocation { variant: location }),
|
||||||
participant_index: participant_index as u32,
|
participant_index: participant_index as u32,
|
||||||
role: db_participant.role.unwrap_or(ChannelRole::Member).into(),
|
role: db_participant.role.unwrap_or(ChannelRole::Member).into(),
|
||||||
in_call: db_participant.in_call,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -20,7 +20,6 @@ pub struct Model {
|
||||||
pub calling_connection_server_id: Option<ServerId>,
|
pub calling_connection_server_id: Option<ServerId>,
|
||||||
pub participant_index: Option<i32>,
|
pub participant_index: Option<i32>,
|
||||||
pub role: Option<ChannelRole>,
|
pub role: Option<ChannelRole>,
|
||||||
pub in_call: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
|
|
|
@ -138,7 +138,6 @@ async fn test_joining_channels(db: &Arc<Database>) {
|
||||||
.join_channel(
|
.join_channel(
|
||||||
channel_1,
|
channel_1,
|
||||||
user_1,
|
user_1,
|
||||||
false,
|
|
||||||
ConnectionId { owner_id, id: 1 },
|
ConnectionId { owner_id, id: 1 },
|
||||||
TEST_RELEASE_CHANNEL,
|
TEST_RELEASE_CHANNEL,
|
||||||
)
|
)
|
||||||
|
@ -733,15 +732,9 @@ async fn test_guest_access(db: &Arc<Database>) {
|
||||||
.await
|
.await
|
||||||
.is_err());
|
.is_err());
|
||||||
|
|
||||||
db.join_channel(
|
db.join_channel(zed_channel, guest, guest_connection, TEST_RELEASE_CHANNEL)
|
||||||
zed_channel,
|
.await
|
||||||
guest,
|
.unwrap();
|
||||||
false,
|
|
||||||
guest_connection,
|
|
||||||
TEST_RELEASE_CHANNEL,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(db
|
assert!(db
|
||||||
.join_channel_chat(zed_channel, guest_connection, guest)
|
.join_channel_chat(zed_channel, guest_connection, guest)
|
||||||
|
|
|
@ -105,7 +105,6 @@ struct Session {
|
||||||
zed_environment: Arc<str>,
|
zed_environment: Arc<str>,
|
||||||
user_id: UserId,
|
user_id: UserId,
|
||||||
connection_id: ConnectionId,
|
connection_id: ConnectionId,
|
||||||
zed_version: SemanticVersion,
|
|
||||||
db: Arc<tokio::sync::Mutex<DbHandle>>,
|
db: Arc<tokio::sync::Mutex<DbHandle>>,
|
||||||
peer: Arc<Peer>,
|
peer: Arc<Peer>,
|
||||||
connection_pool: Arc<parking_lot::Mutex<ConnectionPool>>,
|
connection_pool: Arc<parking_lot::Mutex<ConnectionPool>>,
|
||||||
|
@ -132,19 +131,6 @@ impl Session {
|
||||||
_not_send: PhantomData,
|
_not_send: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn endpoint_removed_in(&self, endpoint: &str, version: SemanticVersion) -> anyhow::Result<()> {
|
|
||||||
if self.zed_version > version {
|
|
||||||
Err(anyhow!(
|
|
||||||
"{} was removed in {} (you're on {})",
|
|
||||||
endpoint,
|
|
||||||
version,
|
|
||||||
self.zed_version
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Session {
|
impl fmt::Debug for Session {
|
||||||
|
@ -288,11 +274,8 @@ impl Server {
|
||||||
.add_request_handler(get_channel_members)
|
.add_request_handler(get_channel_members)
|
||||||
.add_request_handler(respond_to_channel_invite)
|
.add_request_handler(respond_to_channel_invite)
|
||||||
.add_request_handler(join_channel)
|
.add_request_handler(join_channel)
|
||||||
.add_request_handler(join_channel2)
|
|
||||||
.add_request_handler(join_channel_chat)
|
.add_request_handler(join_channel_chat)
|
||||||
.add_message_handler(leave_channel_chat)
|
.add_message_handler(leave_channel_chat)
|
||||||
.add_request_handler(join_channel_call)
|
|
||||||
.add_request_handler(leave_channel_call)
|
|
||||||
.add_request_handler(send_channel_message)
|
.add_request_handler(send_channel_message)
|
||||||
.add_request_handler(remove_channel_message)
|
.add_request_handler(remove_channel_message)
|
||||||
.add_request_handler(get_channel_messages)
|
.add_request_handler(get_channel_messages)
|
||||||
|
@ -576,7 +559,6 @@ impl Server {
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
address: String,
|
address: String,
|
||||||
user: User,
|
user: User,
|
||||||
zed_version: SemanticVersion,
|
|
||||||
impersonator: Option<User>,
|
impersonator: Option<User>,
|
||||||
mut send_connection_id: Option<oneshot::Sender<ConnectionId>>,
|
mut send_connection_id: Option<oneshot::Sender<ConnectionId>>,
|
||||||
executor: Executor,
|
executor: Executor,
|
||||||
|
@ -634,7 +616,6 @@ impl Server {
|
||||||
let session = Session {
|
let session = Session {
|
||||||
user_id,
|
user_id,
|
||||||
connection_id,
|
connection_id,
|
||||||
zed_version,
|
|
||||||
db: Arc::new(tokio::sync::Mutex::new(DbHandle(this.app_state.db.clone()))),
|
db: Arc::new(tokio::sync::Mutex::new(DbHandle(this.app_state.db.clone()))),
|
||||||
zed_environment: this.app_state.config.zed_environment.clone(),
|
zed_environment: this.app_state.config.zed_environment.clone(),
|
||||||
peer: this.peer.clone(),
|
peer: this.peer.clone(),
|
||||||
|
@ -885,7 +866,7 @@ pub fn routes(server: Arc<Server>) -> Router<Body> {
|
||||||
|
|
||||||
pub async fn handle_websocket_request(
|
pub async fn handle_websocket_request(
|
||||||
TypedHeader(ProtocolVersion(protocol_version)): TypedHeader<ProtocolVersion>,
|
TypedHeader(ProtocolVersion(protocol_version)): TypedHeader<ProtocolVersion>,
|
||||||
app_version_header: Option<TypedHeader<AppVersionHeader>>,
|
_app_version_header: Option<TypedHeader<AppVersionHeader>>,
|
||||||
ConnectInfo(socket_address): ConnectInfo<SocketAddr>,
|
ConnectInfo(socket_address): ConnectInfo<SocketAddr>,
|
||||||
Extension(server): Extension<Arc<Server>>,
|
Extension(server): Extension<Arc<Server>>,
|
||||||
Extension(user): Extension<User>,
|
Extension(user): Extension<User>,
|
||||||
|
@ -900,12 +881,6 @@ pub async fn handle_websocket_request(
|
||||||
.into_response();
|
.into_response();
|
||||||
}
|
}
|
||||||
|
|
||||||
// zed 0.122.x was the first version that sent an app header, so once that hits stable
|
|
||||||
// we can return UPGRADE_REQUIRED instead of unwrap_or_default();
|
|
||||||
let app_version = app_version_header
|
|
||||||
.map(|header| header.0 .0)
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let socket_address = socket_address.to_string();
|
let socket_address = socket_address.to_string();
|
||||||
ws.on_upgrade(move |socket| {
|
ws.on_upgrade(move |socket| {
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
@ -920,7 +895,6 @@ pub async fn handle_websocket_request(
|
||||||
connection,
|
connection,
|
||||||
socket_address,
|
socket_address,
|
||||||
user,
|
user,
|
||||||
app_version,
|
|
||||||
impersonator.0,
|
impersonator.0,
|
||||||
None,
|
None,
|
||||||
Executor::Production,
|
Executor::Production,
|
||||||
|
@ -1063,7 +1037,7 @@ async fn join_room(
|
||||||
let channel_id = session.db().await.channel_id_for_room(room_id).await?;
|
let channel_id = session.db().await.channel_id_for_room(room_id).await?;
|
||||||
|
|
||||||
if let Some(channel_id) = channel_id {
|
if let Some(channel_id) = channel_id {
|
||||||
return join_channel_internal(channel_id, true, Box::new(response), session).await;
|
return join_channel_internal(channel_id, Box::new(response), session).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let joined_room = {
|
let joined_room = {
|
||||||
|
@ -2726,67 +2700,14 @@ async fn respond_to_channel_invite(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Join the channels' call
|
/// Join the channels' room
|
||||||
async fn join_channel(
|
async fn join_channel(
|
||||||
request: proto::JoinChannel,
|
request: proto::JoinChannel,
|
||||||
response: Response<proto::JoinChannel>,
|
response: Response<proto::JoinChannel>,
|
||||||
session: Session,
|
session: Session,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
session.endpoint_removed_in("join_channel", "0.123.0".parse().unwrap())?;
|
|
||||||
|
|
||||||
let channel_id = ChannelId::from_proto(request.channel_id);
|
let channel_id = ChannelId::from_proto(request.channel_id);
|
||||||
join_channel_internal(channel_id, true, Box::new(response), session).await
|
join_channel_internal(channel_id, Box::new(response), session).await
|
||||||
}
|
|
||||||
|
|
||||||
async fn join_channel2(
|
|
||||||
request: proto::JoinChannel2,
|
|
||||||
response: Response<proto::JoinChannel2>,
|
|
||||||
session: Session,
|
|
||||||
) -> Result<()> {
|
|
||||||
let channel_id = ChannelId::from_proto(request.channel_id);
|
|
||||||
join_channel_internal(channel_id, false, Box::new(response), session).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn join_channel_call(
|
|
||||||
request: proto::JoinChannelCall,
|
|
||||||
response: Response<proto::JoinChannelCall>,
|
|
||||||
session: Session,
|
|
||||||
) -> Result<()> {
|
|
||||||
let channel_id = ChannelId::from_proto(request.channel_id);
|
|
||||||
let db = session.db().await;
|
|
||||||
let (joined_room, role) = db
|
|
||||||
.set_in_channel_call(channel_id, session.user_id, true)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let Some(connection_info) = session.live_kit_client.as_ref().and_then(|live_kit| {
|
|
||||||
live_kit_info_for_user(live_kit, &session.user_id, role, &joined_room.live_kit_room)
|
|
||||||
}) else {
|
|
||||||
Err(anyhow!("no live kit token info"))?
|
|
||||||
};
|
|
||||||
|
|
||||||
room_updated(&joined_room, &session.peer);
|
|
||||||
response.send(proto::JoinChannelCallResponse {
|
|
||||||
live_kit_connection_info: Some(connection_info),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn leave_channel_call(
|
|
||||||
request: proto::LeaveChannelCall,
|
|
||||||
response: Response<proto::LeaveChannelCall>,
|
|
||||||
session: Session,
|
|
||||||
) -> Result<()> {
|
|
||||||
let channel_id = ChannelId::from_proto(request.channel_id);
|
|
||||||
let db = session.db().await;
|
|
||||||
let (joined_room, _) = db
|
|
||||||
.set_in_channel_call(channel_id, session.user_id, false)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
room_updated(&joined_room, &session.peer);
|
|
||||||
response.send(proto::Ack {})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trait JoinChannelInternalResponse {
|
trait JoinChannelInternalResponse {
|
||||||
|
@ -2802,15 +2723,9 @@ impl JoinChannelInternalResponse for Response<proto::JoinRoom> {
|
||||||
Response::<proto::JoinRoom>::send(self, result)
|
Response::<proto::JoinRoom>::send(self, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl JoinChannelInternalResponse for Response<proto::JoinChannel2> {
|
|
||||||
fn send(self, result: proto::JoinRoomResponse) -> Result<()> {
|
|
||||||
Response::<proto::JoinChannel2>::send(self, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn join_channel_internal(
|
async fn join_channel_internal(
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
autojoin: bool,
|
|
||||||
response: Box<impl JoinChannelInternalResponse>,
|
response: Box<impl JoinChannelInternalResponse>,
|
||||||
session: Session,
|
session: Session,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
@ -2822,22 +2737,39 @@ async fn join_channel_internal(
|
||||||
.join_channel(
|
.join_channel(
|
||||||
channel_id,
|
channel_id,
|
||||||
session.user_id,
|
session.user_id,
|
||||||
autojoin,
|
|
||||||
session.connection_id,
|
session.connection_id,
|
||||||
session.zed_environment.as_ref(),
|
session.zed_environment.as_ref(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let live_kit_connection_info = session.live_kit_client.as_ref().and_then(|live_kit| {
|
let live_kit_connection_info = session.live_kit_client.as_ref().and_then(|live_kit| {
|
||||||
if !autojoin {
|
let (can_publish, token) = if role == ChannelRole::Guest {
|
||||||
return None;
|
(
|
||||||
}
|
false,
|
||||||
live_kit_info_for_user(
|
live_kit
|
||||||
live_kit,
|
.guest_token(
|
||||||
&session.user_id,
|
&joined_room.room.live_kit_room,
|
||||||
role,
|
&session.user_id.to_string(),
|
||||||
&joined_room.room.live_kit_room,
|
)
|
||||||
)
|
.trace_err()?,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
true,
|
||||||
|
live_kit
|
||||||
|
.room_token(
|
||||||
|
&joined_room.room.live_kit_room,
|
||||||
|
&session.user_id.to_string(),
|
||||||
|
)
|
||||||
|
.trace_err()?,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(LiveKitConnectionInfo {
|
||||||
|
server_url: live_kit.url().into(),
|
||||||
|
token,
|
||||||
|
can_publish,
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
response.send(proto::JoinRoomResponse {
|
response.send(proto::JoinRoomResponse {
|
||||||
|
@ -2873,35 +2805,6 @@ async fn join_channel_internal(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn live_kit_info_for_user(
|
|
||||||
live_kit: &Arc<dyn live_kit_server::api::Client>,
|
|
||||||
user_id: &UserId,
|
|
||||||
role: ChannelRole,
|
|
||||||
live_kit_room: &String,
|
|
||||||
) -> Option<LiveKitConnectionInfo> {
|
|
||||||
let (can_publish, token) = if role == ChannelRole::Guest {
|
|
||||||
(
|
|
||||||
false,
|
|
||||||
live_kit
|
|
||||||
.guest_token(live_kit_room, &user_id.to_string())
|
|
||||||
.trace_err()?,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
true,
|
|
||||||
live_kit
|
|
||||||
.room_token(live_kit_room, &user_id.to_string())
|
|
||||||
.trace_err()?,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(LiveKitConnectionInfo {
|
|
||||||
server_url: live_kit.url().into(),
|
|
||||||
token,
|
|
||||||
can_publish,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start editing the channel notes
|
/// Start editing the channel notes
|
||||||
async fn join_channel_buffer(
|
async fn join_channel_buffer(
|
||||||
request: proto::JoinChannelBuffer,
|
request: proto::JoinChannelBuffer,
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
use crate::{
|
use crate::{db::ChannelId, tests::TestServer};
|
||||||
db::ChannelId,
|
|
||||||
tests::{test_server::join_channel_call, TestServer},
|
|
||||||
};
|
|
||||||
use call::ActiveCall;
|
use call::ActiveCall;
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{BackgroundExecutor, TestAppContext};
|
use gpui::{BackgroundExecutor, TestAppContext};
|
||||||
|
@ -35,7 +32,7 @@ async fn test_channel_guests(
|
||||||
cx_a.executor().run_until_parked();
|
cx_a.executor().run_until_parked();
|
||||||
|
|
||||||
// Client B joins channel A as a guest
|
// Client B joins channel A as a guest
|
||||||
cx_b.update(|cx| workspace::open_channel(channel_id, client_b.app_state.clone(), None, cx))
|
cx_b.update(|cx| workspace::join_channel(channel_id, client_b.app_state.clone(), None, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -75,7 +72,7 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project_a = client_a.build_test_project(cx_a).await;
|
let project_a = client_a.build_test_project(cx_a).await;
|
||||||
cx_a.update(|cx| workspace::open_channel(channel_id, client_a.app_state.clone(), None, cx))
|
cx_a.update(|cx| workspace::join_channel(channel_id, client_a.app_state.clone(), None, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -87,13 +84,11 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test
|
||||||
cx_a.run_until_parked();
|
cx_a.run_until_parked();
|
||||||
|
|
||||||
// Client B joins channel A as a guest
|
// Client B joins channel A as a guest
|
||||||
cx_b.update(|cx| workspace::open_channel(channel_id, client_b.app_state.clone(), None, cx))
|
cx_b.update(|cx| workspace::join_channel(channel_id, client_b.app_state.clone(), None, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
cx_a.run_until_parked();
|
cx_a.run_until_parked();
|
||||||
|
|
||||||
join_channel_call(cx_b).await.unwrap();
|
|
||||||
|
|
||||||
// client B opens 1.txt as a guest
|
// client B opens 1.txt as a guest
|
||||||
let (workspace_b, cx_b) = client_b.active_workspace(cx_b);
|
let (workspace_b, cx_b) = client_b.active_workspace(cx_b);
|
||||||
let room_b = cx_b
|
let room_b = cx_b
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{self, UserId},
|
db::{self, UserId},
|
||||||
rpc::RECONNECT_TIMEOUT,
|
rpc::RECONNECT_TIMEOUT,
|
||||||
tests::{room_participants, test_server::join_channel_call, RoomParticipants, TestServer},
|
tests::{room_participants, RoomParticipants, TestServer},
|
||||||
};
|
};
|
||||||
use call::ActiveCall;
|
use call::ActiveCall;
|
||||||
use channel::{ChannelId, ChannelMembership, ChannelStore};
|
use channel::{ChannelId, ChannelMembership, ChannelStore};
|
||||||
|
@ -382,7 +382,6 @@ async fn test_channel_room(
|
||||||
.update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
|
.update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
join_channel_call(cx_a).await.unwrap();
|
|
||||||
|
|
||||||
// Give everyone a chance to observe user A joining
|
// Give everyone a chance to observe user A joining
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
|
@ -430,7 +429,7 @@ async fn test_channel_room(
|
||||||
.update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx))
|
.update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
join_channel_call(cx_b).await.unwrap();
|
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
|
|
||||||
cx_a.read(|cx| {
|
cx_a.read(|cx| {
|
||||||
|
@ -553,9 +552,6 @@ async fn test_channel_room(
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
join_channel_call(cx_a).await.unwrap();
|
|
||||||
join_channel_call(cx_b).await.unwrap();
|
|
||||||
|
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
|
|
||||||
let room_a =
|
let room_a =
|
||||||
|
|
|
@ -24,7 +24,7 @@ use workspace::{
|
||||||
|
|
||||||
use super::TestClient;
|
use super::TestClient;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test(iterations = 10)]
|
||||||
async fn test_basic_following(
|
async fn test_basic_following(
|
||||||
cx_a: &mut TestAppContext,
|
cx_a: &mut TestAppContext,
|
||||||
cx_b: &mut TestAppContext,
|
cx_b: &mut TestAppContext,
|
||||||
|
@ -437,7 +437,6 @@ async fn test_basic_following(
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
let shared_screen = workspace_a.update(cx_a, |workspace, cx| {
|
let shared_screen = workspace_a.update(cx_a, |workspace, cx| {
|
||||||
workspace
|
workspace
|
||||||
|
@ -523,7 +522,6 @@ async fn test_basic_following(
|
||||||
workspace_a.update(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
|
workspace_a.update(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
|
||||||
None
|
None
|
||||||
);
|
);
|
||||||
executor.run_until_parked();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
@ -2006,7 +2004,7 @@ async fn join_channel(
|
||||||
client: &TestClient,
|
client: &TestClient,
|
||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
cx.update(|cx| workspace::open_channel(channel_id, client.app_state.clone(), None, cx))
|
cx.update(|cx| workspace::join_channel(channel_id, client.app_state.clone(), None, cx))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1881,7 +1881,7 @@ fn active_call_events(cx: &mut TestAppContext) -> Rc<RefCell<Vec<room::Event>>>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_mute(
|
async fn test_mute_deafen(
|
||||||
executor: BackgroundExecutor,
|
executor: BackgroundExecutor,
|
||||||
cx_a: &mut TestAppContext,
|
cx_a: &mut TestAppContext,
|
||||||
cx_b: &mut TestAppContext,
|
cx_b: &mut TestAppContext,
|
||||||
|
@ -1920,7 +1920,7 @@ async fn test_mute(
|
||||||
room_a.read_with(cx_a, |room, _| assert!(!room.is_muted()));
|
room_a.read_with(cx_a, |room, _| assert!(!room.is_muted()));
|
||||||
room_b.read_with(cx_b, |room, _| assert!(!room.is_muted()));
|
room_b.read_with(cx_b, |room, _| assert!(!room.is_muted()));
|
||||||
|
|
||||||
// Users A and B are both unmuted.
|
// Users A and B are both muted.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
participant_audio_state(&room_a, cx_a),
|
participant_audio_state(&room_a, cx_a),
|
||||||
&[ParticipantAudioState {
|
&[ParticipantAudioState {
|
||||||
|
@ -1962,6 +1962,30 @@ async fn test_mute(
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// User A deafens
|
||||||
|
room_a.update(cx_a, |room, cx| room.toggle_deafen(cx));
|
||||||
|
executor.run_until_parked();
|
||||||
|
|
||||||
|
// User A does not hear user B.
|
||||||
|
room_a.read_with(cx_a, |room, _| assert!(room.is_muted()));
|
||||||
|
room_b.read_with(cx_b, |room, _| assert!(!room.is_muted()));
|
||||||
|
assert_eq!(
|
||||||
|
participant_audio_state(&room_a, cx_a),
|
||||||
|
&[ParticipantAudioState {
|
||||||
|
user_id: client_b.user_id().unwrap(),
|
||||||
|
is_muted: false,
|
||||||
|
audio_tracks_playing: vec![false],
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
participant_audio_state(&room_b, cx_b),
|
||||||
|
&[ParticipantAudioState {
|
||||||
|
user_id: client_a.user_id().unwrap(),
|
||||||
|
is_muted: true,
|
||||||
|
audio_tracks_playing: vec![true],
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
|
||||||
// User B calls user C, C joins.
|
// User B calls user C, C joins.
|
||||||
active_call_b
|
active_call_b
|
||||||
.update(cx_b, |call, cx| {
|
.update(cx_b, |call, cx| {
|
||||||
|
@ -1976,6 +2000,22 @@ async fn test_mute(
|
||||||
.unwrap();
|
.unwrap();
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
|
|
||||||
|
// User A does not hear users B or C.
|
||||||
|
assert_eq!(
|
||||||
|
participant_audio_state(&room_a, cx_a),
|
||||||
|
&[
|
||||||
|
ParticipantAudioState {
|
||||||
|
user_id: client_b.user_id().unwrap(),
|
||||||
|
is_muted: false,
|
||||||
|
audio_tracks_playing: vec![false],
|
||||||
|
},
|
||||||
|
ParticipantAudioState {
|
||||||
|
user_id: client_c.user_id().unwrap(),
|
||||||
|
is_muted: false,
|
||||||
|
audio_tracks_playing: vec![false],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
participant_audio_state(&room_b, cx_b),
|
participant_audio_state(&room_b, cx_b),
|
||||||
&[
|
&[
|
||||||
|
|
|
@ -37,7 +37,7 @@ use std::{
|
||||||
Arc,
|
Arc,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use util::{http::FakeHttpClient, SemanticVersion};
|
use util::http::FakeHttpClient;
|
||||||
use workspace::{Workspace, WorkspaceStore};
|
use workspace::{Workspace, WorkspaceStore};
|
||||||
|
|
||||||
pub struct TestServer {
|
pub struct TestServer {
|
||||||
|
@ -231,7 +231,6 @@ impl TestServer {
|
||||||
server_conn,
|
server_conn,
|
||||||
client_name,
|
client_name,
|
||||||
user,
|
user,
|
||||||
SemanticVersion::default(),
|
|
||||||
None,
|
None,
|
||||||
Some(connection_id_tx),
|
Some(connection_id_tx),
|
||||||
Executor::Deterministic(cx.background_executor().clone()),
|
Executor::Deterministic(cx.background_executor().clone()),
|
||||||
|
@ -687,7 +686,7 @@ impl TestClient {
|
||||||
channel_id: u64,
|
channel_id: u64,
|
||||||
cx: &'a mut TestAppContext,
|
cx: &'a mut TestAppContext,
|
||||||
) -> (View<Workspace>, &'a mut VisualTestContext) {
|
) -> (View<Workspace>, &'a mut VisualTestContext) {
|
||||||
cx.update(|cx| workspace::open_channel(channel_id, self.app_state.clone(), None, cx))
|
cx.update(|cx| workspace::join_channel(channel_id, self.app_state.clone(), None, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
@ -762,11 +761,6 @@ impl TestClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn join_channel_call(cx: &mut TestAppContext) -> Task<anyhow::Result<()>> {
|
|
||||||
let room = cx.read(|cx| ActiveCall::global(cx).read(cx).room().cloned());
|
|
||||||
room.unwrap().update(cx, |room, cx| room.join_call(cx))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn open_channel_notes(
|
pub fn open_channel_notes(
|
||||||
channel_id: u64,
|
channel_id: u64,
|
||||||
cx: &mut VisualTestContext,
|
cx: &mut VisualTestContext,
|
||||||
|
|
|
@ -40,7 +40,7 @@ use util::{maybe, ResultExt, TryFutureExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
dock::{DockPosition, Panel, PanelEvent},
|
dock::{DockPosition, Panel, PanelEvent},
|
||||||
notifications::{DetachAndPromptErr, NotifyResultExt, NotifyTaskExt},
|
notifications::{DetachAndPromptErr, NotifyResultExt, NotifyTaskExt},
|
||||||
Workspace,
|
OpenChannelNotes, Workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
|
@ -69,6 +69,19 @@ pub fn init(cx: &mut AppContext) {
|
||||||
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
|
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
|
||||||
workspace.toggle_panel_focus::<CollabPanel>(cx);
|
workspace.toggle_panel_focus::<CollabPanel>(cx);
|
||||||
});
|
});
|
||||||
|
workspace.register_action(|_, _: &OpenChannelNotes, cx| {
|
||||||
|
let channel_id = ActiveCall::global(cx)
|
||||||
|
.read(cx)
|
||||||
|
.room()
|
||||||
|
.and_then(|room| room.read(cx).channel_id());
|
||||||
|
|
||||||
|
if let Some(channel_id) = channel_id {
|
||||||
|
let workspace = cx.view().clone();
|
||||||
|
cx.window_context().defer(move |cx| {
|
||||||
|
ChannelView::open(channel_id, None, workspace, cx).detach_and_log_err(cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
@ -162,9 +175,6 @@ enum ListEntry {
|
||||||
depth: usize,
|
depth: usize,
|
||||||
has_children: bool,
|
has_children: bool,
|
||||||
},
|
},
|
||||||
ChannelCall {
|
|
||||||
channel_id: ChannelId,
|
|
||||||
},
|
|
||||||
ChannelNotes {
|
ChannelNotes {
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
},
|
},
|
||||||
|
@ -372,7 +382,6 @@ impl CollabPanel {
|
||||||
|
|
||||||
if query.is_empty() {
|
if query.is_empty() {
|
||||||
if let Some(channel_id) = room.channel_id() {
|
if let Some(channel_id) = room.channel_id() {
|
||||||
self.entries.push(ListEntry::ChannelCall { channel_id });
|
|
||||||
self.entries.push(ListEntry::ChannelNotes { channel_id });
|
self.entries.push(ListEntry::ChannelNotes { channel_id });
|
||||||
self.entries.push(ListEntry::ChannelChat { channel_id });
|
self.entries.push(ListEntry::ChannelChat { channel_id });
|
||||||
}
|
}
|
||||||
|
@ -470,7 +479,7 @@ impl CollabPanel {
|
||||||
&& participant.video_tracks.is_empty(),
|
&& participant.video_tracks.is_empty(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if room.in_call() && !participant.video_tracks.is_empty() {
|
if !participant.video_tracks.is_empty() {
|
||||||
self.entries.push(ListEntry::ParticipantScreen {
|
self.entries.push(ListEntry::ParticipantScreen {
|
||||||
peer_id: Some(participant.peer_id),
|
peer_id: Some(participant.peer_id),
|
||||||
is_last: true,
|
is_last: true,
|
||||||
|
@ -504,20 +513,6 @@ impl CollabPanel {
|
||||||
role: proto::ChannelRole::Member,
|
role: proto::ChannelRole::Member,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
} else if let Some(channel_id) = ActiveCall::global(cx).read(cx).pending_channel_id() {
|
|
||||||
self.entries.push(ListEntry::Header(Section::ActiveCall));
|
|
||||||
if !old_entries
|
|
||||||
.iter()
|
|
||||||
.any(|entry| matches!(entry, ListEntry::Header(Section::ActiveCall)))
|
|
||||||
{
|
|
||||||
scroll_to_top = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if query.is_empty() {
|
|
||||||
self.entries.push(ListEntry::ChannelCall { channel_id });
|
|
||||||
self.entries.push(ListEntry::ChannelNotes { channel_id });
|
|
||||||
self.entries.push(ListEntry::ChannelChat { channel_id });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut request_entries = Vec::new();
|
let mut request_entries = Vec::new();
|
||||||
|
@ -837,6 +832,8 @@ impl CollabPanel {
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> ListItem {
|
) -> ListItem {
|
||||||
let user_id = user.id;
|
let user_id = user.id;
|
||||||
|
let is_current_user =
|
||||||
|
self.user_store.read(cx).current_user().map(|user| user.id) == Some(user_id);
|
||||||
let tooltip = format!("Follow {}", user.github_login);
|
let tooltip = format!("Follow {}", user.github_login);
|
||||||
|
|
||||||
let is_call_admin = ActiveCall::global(cx).read(cx).room().is_some_and(|room| {
|
let is_call_admin = ActiveCall::global(cx).read(cx).room().is_some_and(|room| {
|
||||||
|
@ -849,6 +846,12 @@ impl CollabPanel {
|
||||||
.selected(is_selected)
|
.selected(is_selected)
|
||||||
.end_slot(if is_pending {
|
.end_slot(if is_pending {
|
||||||
Label::new("Calling").color(Color::Muted).into_any_element()
|
Label::new("Calling").color(Color::Muted).into_any_element()
|
||||||
|
} else if is_current_user {
|
||||||
|
IconButton::new("leave-call", IconName::Exit)
|
||||||
|
.style(ButtonStyle::Subtle)
|
||||||
|
.on_click(move |_, cx| Self::leave_call(cx))
|
||||||
|
.tooltip(|cx| Tooltip::text("Leave Call", cx))
|
||||||
|
.into_any_element()
|
||||||
} else if role == proto::ChannelRole::Guest {
|
} else if role == proto::ChannelRole::Guest {
|
||||||
Label::new("Guest").color(Color::Muted).into_any_element()
|
Label::new("Guest").color(Color::Muted).into_any_element()
|
||||||
} else {
|
} else {
|
||||||
|
@ -950,88 +953,12 @@ impl CollabPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_channel_call(
|
|
||||||
&self,
|
|
||||||
channel_id: ChannelId,
|
|
||||||
is_selected: bool,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> impl IntoElement {
|
|
||||||
let (is_in_call, call_participants) = ActiveCall::global(cx)
|
|
||||||
.read(cx)
|
|
||||||
.room()
|
|
||||||
.map(|room| (room.read(cx).in_call(), room.read(cx).call_participants(cx)))
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
const FACEPILE_LIMIT: usize = 3;
|
|
||||||
|
|
||||||
let face_pile = if !call_participants.is_empty() {
|
|
||||||
let extra_count = call_participants.len().saturating_sub(FACEPILE_LIMIT);
|
|
||||||
let result = FacePile::new(
|
|
||||||
call_participants
|
|
||||||
.iter()
|
|
||||||
.map(|user| Avatar::new(user.avatar_uri.clone()).into_any_element())
|
|
||||||
.take(FACEPILE_LIMIT)
|
|
||||||
.chain(if extra_count > 0 {
|
|
||||||
Some(
|
|
||||||
div()
|
|
||||||
.ml_2()
|
|
||||||
.child(Label::new(format!("+{extra_count}")))
|
|
||||||
.into_any_element(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
})
|
|
||||||
.collect::<SmallVec<_>>(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Some(result)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
ListItem::new("channel-call")
|
|
||||||
.selected(is_selected)
|
|
||||||
.start_slot(
|
|
||||||
h_flex()
|
|
||||||
.gap_1()
|
|
||||||
.child(render_tree_branch(false, true, cx))
|
|
||||||
.child(IconButton::new(0, IconName::AudioOn)),
|
|
||||||
)
|
|
||||||
.when(is_in_call, |el| {
|
|
||||||
el.end_slot(
|
|
||||||
IconButton::new(1, IconName::Exit)
|
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.shape(ui::IconButtonShape::Square)
|
|
||||||
.tooltip(|cx| Tooltip::text("Leave call", cx))
|
|
||||||
.on_click(cx.listener(|this, _, cx| this.leave_channel_call(cx))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.when(!is_in_call, |el| {
|
|
||||||
el.tooltip(move |cx| Tooltip::text("Join audio call", cx))
|
|
||||||
.on_click(cx.listener(move |this, _, cx| {
|
|
||||||
this.join_channel_call(channel_id, cx);
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.text_ui()
|
|
||||||
.when(!call_participants.is_empty(), |el| {
|
|
||||||
el.font_weight(FontWeight::SEMIBOLD)
|
|
||||||
})
|
|
||||||
.child("call"),
|
|
||||||
)
|
|
||||||
.children(face_pile)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_channel_notes(
|
fn render_channel_notes(
|
||||||
&self,
|
&self,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
is_selected: bool,
|
is_selected: bool,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let channel_store = self.channel_store.read(cx);
|
|
||||||
let has_notes_notification = channel_store.has_channel_buffer_changed(channel_id);
|
|
||||||
|
|
||||||
ListItem::new("channel-notes")
|
ListItem::new("channel-notes")
|
||||||
.selected(is_selected)
|
.selected(is_selected)
|
||||||
.on_click(cx.listener(move |this, _, cx| {
|
.on_click(cx.listener(move |this, _, cx| {
|
||||||
|
@ -1043,14 +970,7 @@ impl CollabPanel {
|
||||||
.child(render_tree_branch(false, true, cx))
|
.child(render_tree_branch(false, true, cx))
|
||||||
.child(IconButton::new(0, IconName::File)),
|
.child(IconButton::new(0, IconName::File)),
|
||||||
)
|
)
|
||||||
.child(
|
.child(Label::new("notes"))
|
||||||
div()
|
|
||||||
.text_ui()
|
|
||||||
.when(has_notes_notification, |el| {
|
|
||||||
el.font_weight(FontWeight::SEMIBOLD)
|
|
||||||
})
|
|
||||||
.child("notes"),
|
|
||||||
)
|
|
||||||
.tooltip(move |cx| Tooltip::text("Open Channel Notes", cx))
|
.tooltip(move |cx| Tooltip::text("Open Channel Notes", cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1060,8 +980,6 @@ impl CollabPanel {
|
||||||
is_selected: bool,
|
is_selected: bool,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let channel_store = self.channel_store.read(cx);
|
|
||||||
let has_messages_notification = channel_store.has_new_messages(channel_id);
|
|
||||||
ListItem::new("channel-chat")
|
ListItem::new("channel-chat")
|
||||||
.selected(is_selected)
|
.selected(is_selected)
|
||||||
.on_click(cx.listener(move |this, _, cx| {
|
.on_click(cx.listener(move |this, _, cx| {
|
||||||
|
@ -1073,14 +991,7 @@ impl CollabPanel {
|
||||||
.child(render_tree_branch(false, false, cx))
|
.child(render_tree_branch(false, false, cx))
|
||||||
.child(IconButton::new(0, IconName::MessageBubbles)),
|
.child(IconButton::new(0, IconName::MessageBubbles)),
|
||||||
)
|
)
|
||||||
.child(
|
.child(Label::new("chat"))
|
||||||
div()
|
|
||||||
.text_ui()
|
|
||||||
.when(has_messages_notification, |el| {
|
|
||||||
el.font_weight(FontWeight::SEMIBOLD)
|
|
||||||
})
|
|
||||||
.child("chat"),
|
|
||||||
)
|
|
||||||
.tooltip(move |cx| Tooltip::text("Open Chat", cx))
|
.tooltip(move |cx| Tooltip::text("Open Chat", cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1338,14 +1249,12 @@ impl CollabPanel {
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
let this = cx.view().clone();
|
let this = cx.view().clone();
|
||||||
let room = ActiveCall::global(cx).read(cx).room();
|
let in_room = ActiveCall::global(cx).read(cx).room().is_some();
|
||||||
let in_room = room.is_some();
|
|
||||||
let in_call = room.is_some_and(|room| room.read(cx).in_call());
|
|
||||||
|
|
||||||
let context_menu = ContextMenu::build(cx, |mut context_menu, _| {
|
let context_menu = ContextMenu::build(cx, |mut context_menu, _| {
|
||||||
let user_id = contact.user.id;
|
let user_id = contact.user.id;
|
||||||
|
|
||||||
if contact.online && !contact.busy && (!in_room || in_call) {
|
if contact.online && !contact.busy {
|
||||||
let label = if in_room {
|
let label = if in_room {
|
||||||
format!("Invite {} to join", contact.user.github_login)
|
format!("Invite {} to join", contact.user.github_login)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1479,7 +1388,23 @@ impl CollabPanel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ListEntry::Channel { channel, .. } => self.open_channel(channel.id, cx),
|
ListEntry::Channel { channel, .. } => {
|
||||||
|
let is_active = maybe!({
|
||||||
|
let call_channel = ActiveCall::global(cx)
|
||||||
|
.read(cx)
|
||||||
|
.room()?
|
||||||
|
.read(cx)
|
||||||
|
.channel_id()?;
|
||||||
|
|
||||||
|
Some(call_channel == channel.id)
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
if is_active {
|
||||||
|
self.open_channel_notes(channel.id, cx)
|
||||||
|
} else {
|
||||||
|
self.join_channel(channel.id, cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
ListEntry::ContactPlaceholder => self.toggle_contact_finder(cx),
|
ListEntry::ContactPlaceholder => self.toggle_contact_finder(cx),
|
||||||
ListEntry::CallParticipant { user, peer_id, .. } => {
|
ListEntry::CallParticipant { user, peer_id, .. } => {
|
||||||
if Some(user) == self.user_store.read(cx).current_user().as_ref() {
|
if Some(user) == self.user_store.read(cx).current_user().as_ref() {
|
||||||
|
@ -1496,9 +1421,6 @@ impl CollabPanel {
|
||||||
ListEntry::ChannelInvite(channel) => {
|
ListEntry::ChannelInvite(channel) => {
|
||||||
self.respond_to_channel_invite(channel.id, true, cx)
|
self.respond_to_channel_invite(channel.id, true, cx)
|
||||||
}
|
}
|
||||||
ListEntry::ChannelCall { channel_id } => {
|
|
||||||
self.join_channel_call(*channel_id, cx)
|
|
||||||
}
|
|
||||||
ListEntry::ChannelNotes { channel_id } => {
|
ListEntry::ChannelNotes { channel_id } => {
|
||||||
self.open_channel_notes(*channel_id, cx)
|
self.open_channel_notes(*channel_id, cx)
|
||||||
}
|
}
|
||||||
|
@ -1961,47 +1883,20 @@ impl CollabPanel {
|
||||||
.detach_and_prompt_err("Call failed", cx, |_, _| None);
|
.detach_and_prompt_err("Call failed", cx, |_, _| None);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_channel(&mut self, channel_id: u64, cx: &mut ViewContext<Self>) {
|
fn join_channel(&self, channel_id: u64, cx: &mut ViewContext<Self>) {
|
||||||
let Some(workspace) = self.workspace.upgrade() else {
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(handle) = cx.window_handle().downcast::<Workspace>() else {
|
let Some(handle) = cx.window_handle().downcast::<Workspace>() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let is_in_call = ActiveCall::global(cx)
|
workspace::join_channel(
|
||||||
.read(cx)
|
channel_id,
|
||||||
.room()
|
workspace.read(cx).app_state().clone(),
|
||||||
.map(|room| room.read(cx).in_call())
|
Some(handle),
|
||||||
.unwrap_or(false);
|
cx,
|
||||||
if !is_in_call {
|
)
|
||||||
workspace::open_channel(
|
.detach_and_prompt_err("Failed to join channel", cx, |_, _| None)
|
||||||
channel_id,
|
|
||||||
workspace.read(cx).app_state().clone(),
|
|
||||||
Some(handle),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.detach_and_prompt_err("Failed to join channel", cx, |_, _| None);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.open_channel_notes(channel_id, cx);
|
|
||||||
self.join_channel_chat(channel_id, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn join_channel_call(&mut self, _channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
|
||||||
let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
room.update(cx, |room, cx| room.join_call(cx))
|
|
||||||
.detach_and_prompt_err("Failed to join call", cx, |_, _| None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn leave_channel_call(&mut self, cx: &mut ViewContext<Self>) {
|
|
||||||
let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
room.update(cx, |room, cx| room.leave_call(cx));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn join_channel_chat(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
fn join_channel_chat(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
||||||
|
@ -2129,9 +2024,6 @@ impl CollabPanel {
|
||||||
ListEntry::ParticipantScreen { peer_id, is_last } => self
|
ListEntry::ParticipantScreen { peer_id, is_last } => self
|
||||||
.render_participant_screen(*peer_id, *is_last, is_selected, cx)
|
.render_participant_screen(*peer_id, *is_last, is_selected, cx)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
ListEntry::ChannelCall { channel_id } => self
|
|
||||||
.render_channel_call(*channel_id, is_selected, cx)
|
|
||||||
.into_any_element(),
|
|
||||||
ListEntry::ChannelNotes { channel_id } => self
|
ListEntry::ChannelNotes { channel_id } => self
|
||||||
.render_channel_notes(*channel_id, is_selected, cx)
|
.render_channel_notes(*channel_id, is_selected, cx)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
|
@ -2197,25 +2089,24 @@ impl CollabPanel {
|
||||||
is_collapsed: bool,
|
is_collapsed: bool,
|
||||||
cx: &ViewContext<Self>,
|
cx: &ViewContext<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
|
let mut channel_link = None;
|
||||||
let mut channel_tooltip_text = None;
|
let mut channel_tooltip_text = None;
|
||||||
let mut channel_icon = None;
|
let mut channel_icon = None;
|
||||||
|
|
||||||
let text = match section {
|
let text = match section {
|
||||||
Section::ActiveCall => {
|
Section::ActiveCall => {
|
||||||
let channel_name = maybe!({
|
let channel_name = maybe!({
|
||||||
let channel_id = ActiveCall::global(cx)
|
let channel_id = ActiveCall::global(cx).read(cx).channel_id(cx)?;
|
||||||
.read(cx)
|
|
||||||
.channel_id(cx)
|
|
||||||
.or_else(|| ActiveCall::global(cx).read(cx).pending_channel_id())?;
|
|
||||||
|
|
||||||
let channel = self.channel_store.read(cx).channel_for_id(channel_id)?;
|
let channel = self.channel_store.read(cx).channel_for_id(channel_id)?;
|
||||||
|
|
||||||
|
channel_link = Some(channel.link());
|
||||||
(channel_icon, channel_tooltip_text) = match channel.visibility {
|
(channel_icon, channel_tooltip_text) = match channel.visibility {
|
||||||
proto::ChannelVisibility::Public => {
|
proto::ChannelVisibility::Public => {
|
||||||
(Some(IconName::Public), Some("Close Channel"))
|
(Some("icons/public.svg"), Some("Copy public channel link."))
|
||||||
}
|
}
|
||||||
proto::ChannelVisibility::Members => {
|
proto::ChannelVisibility::Members => {
|
||||||
(Some(IconName::Hash), Some("Close Channel"))
|
(Some("icons/hash.svg"), Some("Copy private channel link."))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2237,10 +2128,17 @@ impl CollabPanel {
|
||||||
};
|
};
|
||||||
|
|
||||||
let button = match section {
|
let button = match section {
|
||||||
Section::ActiveCall => channel_icon.map(|_| {
|
Section::ActiveCall => channel_link.map(|channel_link| {
|
||||||
IconButton::new("channel-link", IconName::Close)
|
let channel_link_copy = channel_link.clone();
|
||||||
.on_click(move |_, cx| Self::leave_call(cx))
|
IconButton::new("channel-link", IconName::Copy)
|
||||||
.tooltip(|cx| Tooltip::text("Close channel", cx))
|
.icon_size(IconSize::Small)
|
||||||
|
.size(ButtonSize::None)
|
||||||
|
.visible_on_hover("section-header")
|
||||||
|
.on_click(move |_, cx| {
|
||||||
|
let item = ClipboardItem::new(channel_link_copy.clone());
|
||||||
|
cx.write_to_clipboard(item)
|
||||||
|
})
|
||||||
|
.tooltip(|cx| Tooltip::text("Copy channel link", cx))
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}),
|
}),
|
||||||
Section::Contacts => Some(
|
Section::Contacts => Some(
|
||||||
|
@ -2275,9 +2173,6 @@ impl CollabPanel {
|
||||||
this.toggle_section_expanded(section, cx);
|
this.toggle_section_expanded(section, cx);
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
.when_some(channel_icon, |el, channel_icon| {
|
|
||||||
el.start_slot(Icon::new(channel_icon).color(Color::Muted))
|
|
||||||
})
|
|
||||||
.inset(true)
|
.inset(true)
|
||||||
.end_slot::<AnyElement>(button)
|
.end_slot::<AnyElement>(button)
|
||||||
.selected(is_selected),
|
.selected(is_selected),
|
||||||
|
@ -2583,7 +2478,11 @@ impl CollabPanel {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.on_click(cx.listener(move |this, _, cx| {
|
.on_click(cx.listener(move |this, _, cx| {
|
||||||
this.open_channel(channel_id, cx);
|
if is_active {
|
||||||
|
this.open_channel_notes(channel_id, cx)
|
||||||
|
} else {
|
||||||
|
this.join_channel(channel_id, cx)
|
||||||
|
}
|
||||||
}))
|
}))
|
||||||
.on_secondary_mouse_down(cx.listener(
|
.on_secondary_mouse_down(cx.listener(
|
||||||
move |this, event: &MouseDownEvent, cx| {
|
move |this, event: &MouseDownEvent, cx| {
|
||||||
|
@ -2600,24 +2499,61 @@ impl CollabPanel {
|
||||||
.color(Color::Muted),
|
.color(Color::Muted),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
h_flex().id(channel_id as usize).child(
|
h_flex()
|
||||||
div()
|
.id(channel_id as usize)
|
||||||
.text_ui()
|
.child(Label::new(channel.name.clone()))
|
||||||
.when(has_messages_notification || has_notes_notification, |el| {
|
.children(face_pile.map(|face_pile| face_pile.p_1())),
|
||||||
el.font_weight(FontWeight::SEMIBOLD)
|
|
||||||
})
|
|
||||||
.child(channel.name.clone()),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.children(face_pile.map(|face_pile| {
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.absolute()
|
.absolute()
|
||||||
.right(rems(0.))
|
.right(rems(0.))
|
||||||
.z_index(1)
|
.z_index(1)
|
||||||
.h_full()
|
.h_full()
|
||||||
.child(face_pile.p_1())
|
.child(
|
||||||
}))
|
h_flex()
|
||||||
|
.h_full()
|
||||||
|
.gap_1()
|
||||||
|
.px_1()
|
||||||
|
.child(
|
||||||
|
IconButton::new("channel_chat", IconName::MessageBubbles)
|
||||||
|
.style(ButtonStyle::Filled)
|
||||||
|
.shape(ui::IconButtonShape::Square)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.icon_color(if has_messages_notification {
|
||||||
|
Color::Default
|
||||||
|
} else {
|
||||||
|
Color::Muted
|
||||||
|
})
|
||||||
|
.on_click(cx.listener(move |this, _, cx| {
|
||||||
|
this.join_channel_chat(channel_id, cx)
|
||||||
|
}))
|
||||||
|
.tooltip(|cx| Tooltip::text("Open channel chat", cx))
|
||||||
|
.when(!has_messages_notification, |this| {
|
||||||
|
this.visible_on_hover("")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new("channel_notes", IconName::File)
|
||||||
|
.style(ButtonStyle::Filled)
|
||||||
|
.shape(ui::IconButtonShape::Square)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.icon_color(if has_notes_notification {
|
||||||
|
Color::Default
|
||||||
|
} else {
|
||||||
|
Color::Muted
|
||||||
|
})
|
||||||
|
.on_click(cx.listener(move |this, _, cx| {
|
||||||
|
this.open_channel_notes(channel_id, cx)
|
||||||
|
}))
|
||||||
|
.tooltip(|cx| Tooltip::text("Open channel notes", cx))
|
||||||
|
.when(!has_notes_notification, |this| {
|
||||||
|
this.visible_on_hover("")
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
.tooltip({
|
.tooltip({
|
||||||
let channel_store = self.channel_store.clone();
|
let channel_store = self.channel_store.clone();
|
||||||
move |cx| {
|
move |cx| {
|
||||||
|
@ -2821,14 +2757,6 @@ impl PartialEq for ListEntry {
|
||||||
return channel_1.id == channel_2.id;
|
return channel_1.id == channel_2.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ListEntry::ChannelCall { channel_id } => {
|
|
||||||
if let ListEntry::ChannelCall {
|
|
||||||
channel_id: other_id,
|
|
||||||
} = other
|
|
||||||
{
|
|
||||||
return channel_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,
|
||||||
|
@ -2927,7 +2855,7 @@ impl Render for JoinChannelTooltip {
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.channel_participants(self.channel_id);
|
.channel_participants(self.channel_id);
|
||||||
|
|
||||||
div.child(Label::new("Open Channel"))
|
div.child(Label::new("Join Channel"))
|
||||||
.children(participants.iter().map(|participant| {
|
.children(participants.iter().map(|participant| {
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
|
|
|
@ -102,10 +102,6 @@ impl Render for CollabTitlebarItem {
|
||||||
room.remote_participants().values().collect::<Vec<_>>();
|
room.remote_participants().values().collect::<Vec<_>>();
|
||||||
remote_participants.sort_by_key(|p| p.participant_index.0);
|
remote_participants.sort_by_key(|p| p.participant_index.0);
|
||||||
|
|
||||||
if !room.in_call() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
let current_user_face_pile = self.render_collaborator(
|
let current_user_face_pile = self.render_collaborator(
|
||||||
¤t_user,
|
¤t_user,
|
||||||
peer_id,
|
peer_id,
|
||||||
|
@ -137,10 +133,6 @@ impl Render for CollabTitlebarItem {
|
||||||
== ParticipantLocation::SharedProject { project_id }
|
== ParticipantLocation::SharedProject { project_id }
|
||||||
});
|
});
|
||||||
|
|
||||||
if !collaborator.in_call {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let face_pile = self.render_collaborator(
|
let face_pile = self.render_collaborator(
|
||||||
&collaborator.user,
|
&collaborator.user,
|
||||||
collaborator.peer_id,
|
collaborator.peer_id,
|
||||||
|
@ -193,7 +185,7 @@ impl Render for CollabTitlebarItem {
|
||||||
let is_local = project.is_local();
|
let is_local = project.is_local();
|
||||||
let is_shared = is_local && project.is_shared();
|
let is_shared = is_local && project.is_shared();
|
||||||
let is_muted = room.is_muted();
|
let is_muted = room.is_muted();
|
||||||
let is_connected_to_livekit = room.in_call();
|
let is_deafened = room.is_deafened().unwrap_or(false);
|
||||||
let is_screen_sharing = room.is_screen_sharing();
|
let is_screen_sharing = room.is_screen_sharing();
|
||||||
let read_only = room.read_only();
|
let read_only = room.read_only();
|
||||||
|
|
||||||
|
@ -228,28 +220,22 @@ impl Render for CollabTitlebarItem {
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.when(is_connected_to_livekit, |el| {
|
.child(
|
||||||
el.child(
|
div()
|
||||||
div()
|
.child(
|
||||||
.child(
|
IconButton::new("leave-call", ui::IconName::Exit)
|
||||||
IconButton::new("leave-call", ui::IconName::Exit)
|
.style(ButtonStyle::Subtle)
|
||||||
.style(ButtonStyle::Subtle)
|
.tooltip(|cx| Tooltip::text("Leave call", cx))
|
||||||
.tooltip(|cx| Tooltip::text("Leave call", cx))
|
.icon_size(IconSize::Small)
|
||||||
.icon_size(IconSize::Small)
|
.on_click(move |_, cx| {
|
||||||
.on_click(move |_, cx| {
|
ActiveCall::global(cx)
|
||||||
ActiveCall::global(cx).update(cx, |call, cx| {
|
.update(cx, |call, cx| call.hang_up(cx))
|
||||||
if let Some(room) = call.room() {
|
.detach_and_log_err(cx);
|
||||||
room.update(cx, |room, cx| {
|
}),
|
||||||
room.leave_call(cx)
|
)
|
||||||
})
|
.pr_2(),
|
||||||
}
|
)
|
||||||
})
|
.when(!read_only, |this| {
|
||||||
}),
|
|
||||||
)
|
|
||||||
.pl_2(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.when(!read_only && is_connected_to_livekit, |this| {
|
|
||||||
this.child(
|
this.child(
|
||||||
IconButton::new(
|
IconButton::new(
|
||||||
"mute-microphone",
|
"mute-microphone",
|
||||||
|
@ -276,7 +262,34 @@ impl Render for CollabTitlebarItem {
|
||||||
.on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
|
.on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.when(!read_only && is_connected_to_livekit, |this| {
|
.child(
|
||||||
|
IconButton::new(
|
||||||
|
"mute-sound",
|
||||||
|
if is_deafened {
|
||||||
|
ui::IconName::AudioOff
|
||||||
|
} else {
|
||||||
|
ui::IconName::AudioOn
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.style(ButtonStyle::Subtle)
|
||||||
|
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.selected(is_deafened)
|
||||||
|
.tooltip(move |cx| {
|
||||||
|
if !read_only {
|
||||||
|
Tooltip::with_meta(
|
||||||
|
"Deafen Audio",
|
||||||
|
None,
|
||||||
|
"Mic will be muted",
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Tooltip::text("Deafen Audio", cx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on_click(move |_, cx| crate::toggle_deafen(&Default::default(), cx)),
|
||||||
|
)
|
||||||
|
.when(!read_only, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
IconButton::new("screen-share", ui::IconName::Screen)
|
IconButton::new("screen-share", ui::IconName::Screen)
|
||||||
.style(ButtonStyle::Subtle)
|
.style(ButtonStyle::Subtle)
|
||||||
|
|
|
@ -22,7 +22,10 @@ pub use panel_settings::{
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use workspace::{notifications::DetachAndPromptErr, AppState};
|
use workspace::{notifications::DetachAndPromptErr, AppState};
|
||||||
|
|
||||||
actions!(collab, [ToggleScreenSharing, ToggleMute, LeaveCall]);
|
actions!(
|
||||||
|
collab,
|
||||||
|
[ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
|
||||||
|
);
|
||||||
|
|
||||||
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||||
CollaborationPanelSettings::register(cx);
|
CollaborationPanelSettings::register(cx);
|
||||||
|
@ -82,6 +85,12 @@ pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
|
||||||
|
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
|
||||||
|
room.update(cx, |room, cx| room.toggle_deafen(cx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn notification_window_options(
|
fn notification_window_options(
|
||||||
screen: Rc<dyn PlatformDisplay>,
|
screen: Rc<dyn PlatformDisplay>,
|
||||||
window_size: Size<Pixels>,
|
window_size: Size<Pixels>,
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//! Not literally though - rendering, layout and all that jazz is a responsibility of [`EditorElement`][EditorElement].
|
//! Not literally though - rendering, layout and all that jazz is a responsibility of [`EditorElement`][EditorElement].
|
||||||
//! Instead, [`DisplayMap`] decides where Inlays/Inlay hints are displayed, when
|
//! Instead, [`DisplayMap`] decides where Inlays/Inlay hints are displayed, when
|
||||||
//! to apply a soft wrap, where to add fold indicators, whether there are any tabs in the buffer that
|
//! to apply a soft wrap, where to add fold indicators, whether there are any tabs in the buffer that
|
||||||
//! we display as spaces and where to display custom blocks (like diagnostics)
|
//! we display as spaces and where to display custom blocks (like diagnostics).
|
||||||
//! Seems like a lot? That's because it is. [`DisplayMap`] is conceptually made up
|
//! Seems like a lot? That's because it is. [`DisplayMap`] is conceptually made up
|
||||||
//! of several smaller structures that form a hierarchy (starting at the bottom):
|
//! of several smaller structures that form a hierarchy (starting at the bottom):
|
||||||
//! - [`InlayMap`] that decides where the [`Inlay`]s should be displayed.
|
//! - [`InlayMap`] that decides where the [`Inlay`]s should be displayed.
|
||||||
|
|
|
@ -54,7 +54,7 @@ impl TestServer {
|
||||||
Ok(SERVERS
|
Ok(SERVERS
|
||||||
.lock()
|
.lock()
|
||||||
.get(url)
|
.get(url)
|
||||||
.ok_or_else(|| anyhow!("no server found for url: {}", url))?
|
.ok_or_else(|| anyhow!("no server found for url"))?
|
||||||
.clone())
|
.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,6 +160,7 @@ impl TestServer {
|
||||||
|
|
||||||
async fn remove_participant(&self, room_name: String, identity: String) -> Result<()> {
|
async fn remove_participant(&self, room_name: String, identity: String) -> Result<()> {
|
||||||
// TODO: clear state associated with the `Room`.
|
// TODO: clear state associated with the `Room`.
|
||||||
|
|
||||||
self.executor.simulate_random_delay().await;
|
self.executor.simulate_random_delay().await;
|
||||||
let mut server_rooms = self.rooms.lock();
|
let mut server_rooms = self.rooms.lock();
|
||||||
let room = server_rooms
|
let room = server_rooms
|
||||||
|
@ -413,15 +414,6 @@ struct TestServerRoom {
|
||||||
participant_permissions: HashMap<Sid, proto::ParticipantPermission>,
|
participant_permissions: HashMap<Sid, proto::ParticipantPermission>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for TestServerRoom {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
for room in self.client_rooms.values() {
|
|
||||||
let mut state = room.0.lock();
|
|
||||||
*state.connection.0.borrow_mut() = ConnectionState::Disconnected;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct TestServerVideoTrack {
|
struct TestServerVideoTrack {
|
||||||
sid: Sid,
|
sid: Sid,
|
||||||
|
@ -702,15 +694,11 @@ impl LocalTrackPublication {
|
||||||
|
|
||||||
pub fn is_muted(&self) -> bool {
|
pub fn is_muted(&self) -> bool {
|
||||||
if let Some(room) = self.room.upgrade() {
|
if let Some(room) = self.room.upgrade() {
|
||||||
if room.is_connected() {
|
room.test_server()
|
||||||
room.test_server()
|
.is_track_muted(&room.token(), &self.sid)
|
||||||
.is_track_muted(&room.token(), &self.sid)
|
.unwrap_or(false)
|
||||||
.unwrap_or(true)
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
true
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
4
crates/rpc/proto/buf.yaml
Normal file
4
crates/rpc/proto/buf.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
version: v1
|
||||||
|
breaking:
|
||||||
|
use:
|
||||||
|
- WIRE
|
|
@ -184,12 +184,9 @@ message Envelope {
|
||||||
SetRoomParticipantRole set_room_participant_role = 156;
|
SetRoomParticipantRole set_room_participant_role = 156;
|
||||||
|
|
||||||
UpdateUserChannels update_user_channels = 157;
|
UpdateUserChannels update_user_channels = 157;
|
||||||
|
|
||||||
JoinChannel2 join_channel2 = 158;
|
|
||||||
JoinChannelCall join_channel_call = 159;
|
|
||||||
JoinChannelCallResponse join_channel_call_response = 160;
|
|
||||||
LeaveChannelCall leave_channel_call = 161; // current max
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reserved 158 to 161;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Messages
|
// Messages
|
||||||
|
@ -296,7 +293,7 @@ message Participant {
|
||||||
ParticipantLocation location = 4;
|
ParticipantLocation location = 4;
|
||||||
uint32 participant_index = 5;
|
uint32 participant_index = 5;
|
||||||
ChannelRole role = 6;
|
ChannelRole role = 6;
|
||||||
bool in_call = 7;
|
reserved 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PendingParticipant {
|
message PendingParticipant {
|
||||||
|
@ -1039,22 +1036,6 @@ message JoinChannel {
|
||||||
uint64 channel_id = 1;
|
uint64 channel_id = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message JoinChannel2 {
|
|
||||||
uint64 channel_id = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message JoinChannelCall {
|
|
||||||
uint64 channel_id = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message JoinChannelCallResponse {
|
|
||||||
LiveKitConnectionInfo live_kit_connection_info = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message LeaveChannelCall {
|
|
||||||
uint64 channel_id = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message DeleteChannel {
|
message DeleteChannel {
|
||||||
uint64 channel_id = 1;
|
uint64 channel_id = 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -198,7 +198,6 @@ messages!(
|
||||||
(InlayHints, Background),
|
(InlayHints, Background),
|
||||||
(InlayHintsResponse, Background),
|
(InlayHintsResponse, Background),
|
||||||
(InviteChannelMember, Foreground),
|
(InviteChannelMember, Foreground),
|
||||||
(JoinChannel2, Foreground),
|
|
||||||
(JoinChannel, Foreground),
|
(JoinChannel, Foreground),
|
||||||
(JoinChannelBuffer, Foreground),
|
(JoinChannelBuffer, Foreground),
|
||||||
(JoinChannelBufferResponse, Foreground),
|
(JoinChannelBufferResponse, Foreground),
|
||||||
|
@ -209,9 +208,6 @@ messages!(
|
||||||
(JoinRoom, Foreground),
|
(JoinRoom, Foreground),
|
||||||
(JoinRoomResponse, Foreground),
|
(JoinRoomResponse, Foreground),
|
||||||
(LeaveChannelBuffer, Background),
|
(LeaveChannelBuffer, Background),
|
||||||
(JoinChannelCall, Foreground),
|
|
||||||
(JoinChannelCallResponse, Foreground),
|
|
||||||
(LeaveChannelCall, Foreground),
|
|
||||||
(LeaveChannelChat, Foreground),
|
(LeaveChannelChat, Foreground),
|
||||||
(LeaveProject, Foreground),
|
(LeaveProject, Foreground),
|
||||||
(LeaveRoom, Foreground),
|
(LeaveRoom, Foreground),
|
||||||
|
@ -328,9 +324,6 @@ request_messages!(
|
||||||
(InlayHints, InlayHintsResponse),
|
(InlayHints, InlayHintsResponse),
|
||||||
(InviteChannelMember, Ack),
|
(InviteChannelMember, Ack),
|
||||||
(JoinChannel, JoinRoomResponse),
|
(JoinChannel, JoinRoomResponse),
|
||||||
(JoinChannel2, JoinRoomResponse),
|
|
||||||
(JoinChannelCall, JoinChannelCallResponse),
|
|
||||||
(LeaveChannelCall, Ack),
|
|
||||||
(JoinChannelBuffer, JoinChannelBufferResponse),
|
(JoinChannelBuffer, JoinChannelBufferResponse),
|
||||||
(JoinChannelChat, JoinChannelChatResponse),
|
(JoinChannelChat, JoinChannelChatResponse),
|
||||||
(JoinProject, JoinProjectResponse),
|
(JoinProject, JoinProjectResponse),
|
||||||
|
|
|
@ -762,7 +762,6 @@ impl Pane {
|
||||||
save_intent: SaveIntent,
|
save_intent: SaveIntent,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
println!("{}", std::backtrace::Backtrace::force_capture());
|
|
||||||
self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close)
|
self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ mod toolbar;
|
||||||
mod workspace_settings;
|
mod workspace_settings;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
use call::ActiveCall;
|
use call::{call_settings::CallSettings, ActiveCall};
|
||||||
use client::{
|
use client::{
|
||||||
proto::{self, ErrorCode, PeerId},
|
proto::{self, ErrorCode, PeerId},
|
||||||
Client, ErrorExt, Status, TypedEnvelope, UserStore,
|
Client, ErrorExt, Status, TypedEnvelope, UserStore,
|
||||||
|
@ -1217,9 +1217,7 @@ impl Workspace {
|
||||||
if let Some(active_call) = active_call {
|
if let Some(active_call) = active_call {
|
||||||
if !quitting
|
if !quitting
|
||||||
&& workspace_count == 1
|
&& workspace_count == 1
|
||||||
&& active_call.read_with(&cx, |call, cx| {
|
&& active_call.read_with(&cx, |call, _| call.room().is_some())?
|
||||||
call.room().is_some_and(|room| room.read(cx).in_call())
|
|
||||||
})?
|
|
||||||
{
|
{
|
||||||
let answer = window.update(&mut cx, |_, cx| {
|
let answer = window.update(&mut cx, |_, cx| {
|
||||||
cx.prompt(
|
cx.prompt(
|
||||||
|
@ -1232,11 +1230,12 @@ impl Workspace {
|
||||||
|
|
||||||
if answer.await.log_err() == Some(1) {
|
if answer.await.log_err() == Some(1) {
|
||||||
return anyhow::Ok(false);
|
return anyhow::Ok(false);
|
||||||
|
} else {
|
||||||
|
active_call
|
||||||
|
.update(&mut cx, |call, cx| call.hang_up(cx))?
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
}
|
}
|
||||||
active_call
|
|
||||||
.update(&mut cx, |call, cx| call.hang_up(cx))?
|
|
||||||
.await
|
|
||||||
.log_err();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3999,6 +3998,8 @@ pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
|
||||||
DB.last_workspace().await.log_err().flatten()
|
DB.last_workspace().await.log_err().flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actions!(collab, [OpenChannelNotes]);
|
||||||
|
|
||||||
async fn join_channel_internal(
|
async fn join_channel_internal(
|
||||||
channel_id: u64,
|
channel_id: u64,
|
||||||
app_state: &Arc<AppState>,
|
app_state: &Arc<AppState>,
|
||||||
|
@ -4100,6 +4101,36 @@ async fn join_channel_internal(
|
||||||
return Some(join_remote_project(project, host, app_state.clone(), cx));
|
return Some(join_remote_project(project, host, app_state.clone(), cx));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if you are the first to join a channel, share your project
|
||||||
|
if room.remote_participants().len() == 0 && !room.local_participant_is_guest() {
|
||||||
|
if let Some(workspace) = requesting_window {
|
||||||
|
let project = workspace.update(cx, |workspace, cx| {
|
||||||
|
if !CallSettings::get_global(cx).share_on_join {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let project = workspace.project.read(cx);
|
||||||
|
if project.is_local()
|
||||||
|
&& project.visible_worktrees(cx).any(|tree| {
|
||||||
|
tree.read(cx)
|
||||||
|
.root_entry()
|
||||||
|
.map_or(false, |entry| entry.is_dir())
|
||||||
|
})
|
||||||
|
{
|
||||||
|
Some(workspace.project.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if let Ok(Some(project)) = project {
|
||||||
|
return Some(cx.spawn(|room, mut cx| async move {
|
||||||
|
room.update(&mut cx, |room, cx| room.share_project(project, cx))?
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
})?;
|
})?;
|
||||||
if let Some(task) = task {
|
if let Some(task) = task {
|
||||||
|
@ -4109,7 +4140,7 @@ async fn join_channel_internal(
|
||||||
anyhow::Ok(false)
|
anyhow::Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_channel(
|
pub fn join_channel(
|
||||||
channel_id: u64,
|
channel_id: u64,
|
||||||
app_state: Arc<AppState>,
|
app_state: Arc<AppState>,
|
||||||
requesting_window: Option<WindowHandle<Workspace>>,
|
requesting_window: Option<WindowHandle<Workspace>>,
|
||||||
|
@ -4142,6 +4173,12 @@ pub fn open_channel(
|
||||||
})?
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
if result.is_ok() {
|
||||||
|
cx.update(|cx| {
|
||||||
|
cx.dispatch_action(&OpenChannelNotes);
|
||||||
|
}).log_err();
|
||||||
|
}
|
||||||
|
|
||||||
active_window = Some(window_handle);
|
active_window = Some(window_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -321,7 +321,7 @@ 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::open_channel(channel_id, app_state, None, cx))?
|
cx.update(|cx| workspace::join_channel(channel_id, app_state, None, cx))?
|
||||||
.await?;
|
.await?;
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
})
|
})
|
||||||
|
@ -376,7 +376,7 @@ 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::open_channel(channel_id, app_state, None, cx)
|
workspace::join_channel(channel_id, app_state, None, cx)
|
||||||
})?
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue