mirror of
https://github.com/zed-industries/zed.git
synced 2024-10-25 07:55:56 +00:00
Show participant projects in contacts popover
This commit is contained in:
parent
8e7f96cebc
commit
feb17c29ec
5 changed files with 279 additions and 19 deletions
|
@ -20,6 +20,11 @@ impl ParticipantLocation {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct LocalParticipant {
|
||||
pub projects: Vec<proto::ParticipantProject>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RemoteParticipant {
|
||||
pub user: Arc<User>,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
participant::{ParticipantLocation, RemoteParticipant},
|
||||
participant::{LocalParticipant, ParticipantLocation, RemoteParticipant},
|
||||
IncomingCall,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
|
@ -27,6 +27,7 @@ pub enum Event {
|
|||
pub struct Room {
|
||||
id: u64,
|
||||
status: RoomStatus,
|
||||
local_participant: LocalParticipant,
|
||||
remote_participants: BTreeMap<PeerId, RemoteParticipant>,
|
||||
pending_participants: Vec<Arc<User>>,
|
||||
participant_user_ids: HashSet<u64>,
|
||||
|
@ -72,6 +73,7 @@ impl Room {
|
|||
id,
|
||||
status: RoomStatus::Online,
|
||||
participant_user_ids: Default::default(),
|
||||
local_participant: Default::default(),
|
||||
remote_participants: Default::default(),
|
||||
pending_participants: Default::default(),
|
||||
pending_call_count: 0,
|
||||
|
@ -170,6 +172,10 @@ impl Room {
|
|||
self.status
|
||||
}
|
||||
|
||||
pub fn local_participant(&self) -> &LocalParticipant {
|
||||
&self.local_participant
|
||||
}
|
||||
|
||||
pub fn remote_participants(&self) -> &BTreeMap<PeerId, RemoteParticipant> {
|
||||
&self.remote_participants
|
||||
}
|
||||
|
@ -201,8 +207,11 @@ impl Room {
|
|||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<()> {
|
||||
// Filter ourselves out from the room's participants.
|
||||
room.participants
|
||||
.retain(|participant| Some(participant.user_id) != self.client.user_id());
|
||||
let local_participant_ix = room
|
||||
.participants
|
||||
.iter()
|
||||
.position(|participant| Some(participant.user_id) == self.client.user_id());
|
||||
let local_participant = local_participant_ix.map(|ix| room.participants.swap_remove(ix));
|
||||
|
||||
let remote_participant_user_ids = room
|
||||
.participants
|
||||
|
@ -223,6 +232,12 @@ impl Room {
|
|||
this.update(&mut cx, |this, cx| {
|
||||
this.participant_user_ids.clear();
|
||||
|
||||
if let Some(participant) = local_participant {
|
||||
this.local_participant.projects = participant.projects;
|
||||
} else {
|
||||
this.local_participant.projects.clear();
|
||||
}
|
||||
|
||||
if let Some(participants) = remote_participants.log_err() {
|
||||
for (participant, user) in room.participants.into_iter().zip(participants) {
|
||||
let peer_id = PeerId(participant.peer_id);
|
||||
|
@ -280,8 +295,6 @@ impl Room {
|
|||
false
|
||||
}
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
if let Some(pending_participants) = pending_participants.log_err() {
|
||||
|
@ -289,7 +302,6 @@ impl Room {
|
|||
for participant in &this.pending_participants {
|
||||
this.participant_user_ids.insert(participant.id);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
this.pending_room_update.take();
|
||||
|
@ -298,6 +310,7 @@ impl Room {
|
|||
}
|
||||
|
||||
this.check_invariants();
|
||||
cx.notify();
|
||||
});
|
||||
}));
|
||||
|
||||
|
|
|
@ -6,9 +6,11 @@ use client::{Contact, PeerId, User, UserStore};
|
|||
use editor::{Cancel, Editor};
|
||||
use fuzzy::{match_strings, StringMatchCandidate};
|
||||
use gpui::{
|
||||
elements::*, impl_actions, impl_internal_actions, keymap, AppContext, ClipboardItem,
|
||||
CursorStyle, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription,
|
||||
View, ViewContext, ViewHandle,
|
||||
elements::*,
|
||||
geometry::{rect::RectF, vector::vec2f},
|
||||
impl_actions, impl_internal_actions, keymap, AppContext, ClipboardItem, CursorStyle, Entity,
|
||||
ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, View, ViewContext,
|
||||
ViewHandle,
|
||||
};
|
||||
use menu::{Confirm, SelectNext, SelectPrev};
|
||||
use project::Project;
|
||||
|
@ -16,6 +18,7 @@ use serde::Deserialize;
|
|||
use settings::Settings;
|
||||
use theme::IconButton;
|
||||
use util::ResultExt;
|
||||
use workspace::JoinProject;
|
||||
|
||||
impl_actions!(contact_list, [RemoveContact, RespondToContactRequest]);
|
||||
impl_internal_actions!(contact_list, [ToggleExpanded, Call, LeaveCall]);
|
||||
|
@ -55,7 +58,17 @@ enum Section {
|
|||
#[derive(Clone)]
|
||||
enum ContactEntry {
|
||||
Header(Section),
|
||||
CallParticipant { user: Arc<User>, is_pending: bool },
|
||||
CallParticipant {
|
||||
user: Arc<User>,
|
||||
is_pending: bool,
|
||||
},
|
||||
ParticipantProject {
|
||||
project_id: u64,
|
||||
worktree_root_names: Vec<String>,
|
||||
host_user_id: u64,
|
||||
is_host: bool,
|
||||
is_last: bool,
|
||||
},
|
||||
IncomingRequest(Arc<User>),
|
||||
OutgoingRequest(Arc<User>),
|
||||
Contact(Arc<Contact>),
|
||||
|
@ -74,6 +87,18 @@ impl PartialEq for ContactEntry {
|
|||
return user_1.id == user_2.id;
|
||||
}
|
||||
}
|
||||
ContactEntry::ParticipantProject {
|
||||
project_id: project_id_1,
|
||||
..
|
||||
} => {
|
||||
if let ContactEntry::ParticipantProject {
|
||||
project_id: project_id_2,
|
||||
..
|
||||
} = other
|
||||
{
|
||||
return project_id_1 == project_id_2;
|
||||
}
|
||||
}
|
||||
ContactEntry::IncomingRequest(user_1) => {
|
||||
if let ContactEntry::IncomingRequest(user_2) = other {
|
||||
return user_1.id == user_2.id;
|
||||
|
@ -177,6 +202,22 @@ impl ContactList {
|
|||
&theme.contact_list,
|
||||
)
|
||||
}
|
||||
ContactEntry::ParticipantProject {
|
||||
project_id,
|
||||
worktree_root_names,
|
||||
host_user_id,
|
||||
is_host,
|
||||
is_last,
|
||||
} => Self::render_participant_project(
|
||||
*project_id,
|
||||
worktree_root_names,
|
||||
*host_user_id,
|
||||
*is_host,
|
||||
*is_last,
|
||||
is_selected,
|
||||
&theme.contact_list,
|
||||
cx,
|
||||
),
|
||||
ContactEntry::IncomingRequest(user) => Self::render_contact_request(
|
||||
user.clone(),
|
||||
this.user_store.clone(),
|
||||
|
@ -298,6 +339,19 @@ impl ContactList {
|
|||
);
|
||||
}
|
||||
}
|
||||
ContactEntry::ParticipantProject {
|
||||
project_id,
|
||||
host_user_id,
|
||||
is_host,
|
||||
..
|
||||
} => {
|
||||
if !is_host {
|
||||
cx.dispatch_global_action(JoinProject {
|
||||
project_id: *project_id,
|
||||
follow_user_id: *host_user_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -324,7 +378,7 @@ impl ContactList {
|
|||
|
||||
if let Some(room) = ActiveCall::global(cx).read(cx).room() {
|
||||
let room = room.read(cx);
|
||||
let mut call_participants = Vec::new();
|
||||
let mut participant_entries = Vec::new();
|
||||
|
||||
// Populate the active user.
|
||||
if let Some(user) = user_store.current_user() {
|
||||
|
@ -343,10 +397,21 @@ impl ContactList {
|
|||
executor.clone(),
|
||||
));
|
||||
if !matches.is_empty() {
|
||||
call_participants.push(ContactEntry::CallParticipant {
|
||||
let user_id = user.id;
|
||||
participant_entries.push(ContactEntry::CallParticipant {
|
||||
user,
|
||||
is_pending: false,
|
||||
});
|
||||
let mut projects = room.local_participant().projects.iter().peekable();
|
||||
while let Some(project) = projects.next() {
|
||||
participant_entries.push(ContactEntry::ParticipantProject {
|
||||
project_id: project.id,
|
||||
worktree_root_names: project.worktree_root_names.clone(),
|
||||
host_user_id: user_id,
|
||||
is_host: true,
|
||||
is_last: projects.peek().is_none(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -370,14 +435,25 @@ impl ContactList {
|
|||
&Default::default(),
|
||||
executor.clone(),
|
||||
));
|
||||
call_participants.extend(matches.iter().map(|mat| {
|
||||
ContactEntry::CallParticipant {
|
||||
for mat in matches {
|
||||
let participant = &room.remote_participants()[&PeerId(mat.candidate_id as u32)];
|
||||
participant_entries.push(ContactEntry::CallParticipant {
|
||||
user: room.remote_participants()[&PeerId(mat.candidate_id as u32)]
|
||||
.user
|
||||
.clone(),
|
||||
is_pending: false,
|
||||
});
|
||||
let mut projects = participant.projects.iter().peekable();
|
||||
while let Some(project) = projects.next() {
|
||||
participant_entries.push(ContactEntry::ParticipantProject {
|
||||
project_id: project.id,
|
||||
worktree_root_names: project.worktree_root_names.clone(),
|
||||
host_user_id: participant.user.id,
|
||||
is_host: false,
|
||||
is_last: projects.peek().is_none(),
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// Populate pending participants.
|
||||
self.match_candidates.clear();
|
||||
|
@ -400,15 +476,15 @@ impl ContactList {
|
|||
&Default::default(),
|
||||
executor.clone(),
|
||||
));
|
||||
call_participants.extend(matches.iter().map(|mat| ContactEntry::CallParticipant {
|
||||
participant_entries.extend(matches.iter().map(|mat| ContactEntry::CallParticipant {
|
||||
user: room.pending_participants()[mat.candidate_id].clone(),
|
||||
is_pending: true,
|
||||
}));
|
||||
|
||||
if !call_participants.is_empty() {
|
||||
if !participant_entries.is_empty() {
|
||||
self.entries.push(ContactEntry::Header(Section::ActiveCall));
|
||||
if !self.collapsed_sections.contains(&Section::ActiveCall) {
|
||||
self.entries.extend(call_participants);
|
||||
self.entries.extend(participant_entries);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -588,6 +664,108 @@ impl ContactList {
|
|||
.boxed()
|
||||
}
|
||||
|
||||
fn render_participant_project(
|
||||
project_id: u64,
|
||||
worktree_root_names: &[String],
|
||||
host_user_id: u64,
|
||||
is_host: bool,
|
||||
is_last: bool,
|
||||
is_selected: bool,
|
||||
theme: &theme::ContactList,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
let font_cache = cx.font_cache();
|
||||
let host_avatar_height = theme
|
||||
.contact_avatar
|
||||
.width
|
||||
.or(theme.contact_avatar.height)
|
||||
.unwrap_or(0.);
|
||||
let row = &theme.project_row.default;
|
||||
let tree_branch = theme.tree_branch;
|
||||
let line_height = row.name.text.line_height(font_cache);
|
||||
let cap_height = row.name.text.cap_height(font_cache);
|
||||
let baseline_offset =
|
||||
row.name.text.baseline_offset(font_cache) + (theme.row_height - line_height) / 2.;
|
||||
let project_name = if worktree_root_names.is_empty() {
|
||||
"untitled".to_string()
|
||||
} else {
|
||||
worktree_root_names.join(", ")
|
||||
};
|
||||
|
||||
MouseEventHandler::<JoinProject>::new(project_id as usize, cx, |mouse_state, _| {
|
||||
let tree_branch = *tree_branch.style_for(mouse_state, is_selected);
|
||||
let row = theme.project_row.style_for(mouse_state, is_selected);
|
||||
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Stack::new()
|
||||
.with_child(
|
||||
Canvas::new(move |bounds, _, cx| {
|
||||
let start_x = bounds.min_x() + (bounds.width() / 2.)
|
||||
- (tree_branch.width / 2.);
|
||||
let end_x = bounds.max_x();
|
||||
let start_y = bounds.min_y();
|
||||
let end_y = bounds.min_y() + baseline_offset - (cap_height / 2.);
|
||||
|
||||
cx.scene.push_quad(gpui::Quad {
|
||||
bounds: RectF::from_points(
|
||||
vec2f(start_x, start_y),
|
||||
vec2f(
|
||||
start_x + tree_branch.width,
|
||||
if is_last { end_y } else { bounds.max_y() },
|
||||
),
|
||||
),
|
||||
background: Some(tree_branch.color),
|
||||
border: gpui::Border::default(),
|
||||
corner_radius: 0.,
|
||||
});
|
||||
cx.scene.push_quad(gpui::Quad {
|
||||
bounds: RectF::from_points(
|
||||
vec2f(start_x, end_y),
|
||||
vec2f(end_x, end_y + tree_branch.width),
|
||||
),
|
||||
background: Some(tree_branch.color),
|
||||
border: gpui::Border::default(),
|
||||
corner_radius: 0.,
|
||||
});
|
||||
})
|
||||
.boxed(),
|
||||
)
|
||||
.constrained()
|
||||
.with_width(host_avatar_height)
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(
|
||||
Label::new(project_name, row.name.text.clone())
|
||||
.aligned()
|
||||
.left()
|
||||
.contained()
|
||||
.with_style(row.name.container)
|
||||
.flex(1., false)
|
||||
.boxed(),
|
||||
)
|
||||
.constrained()
|
||||
.with_height(theme.row_height)
|
||||
.contained()
|
||||
.with_style(row.container)
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(if !is_host {
|
||||
CursorStyle::PointingHand
|
||||
} else {
|
||||
CursorStyle::Arrow
|
||||
})
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
if !is_host {
|
||||
cx.dispatch_global_action(JoinProject {
|
||||
project_id,
|
||||
follow_user_id: host_user_id,
|
||||
});
|
||||
}
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn render_header(
|
||||
section: Section,
|
||||
theme: &theme::ContactList,
|
||||
|
|
|
@ -100,6 +100,8 @@ pub struct ContactList {
|
|||
pub leave_call: Interactive<ContainedText>,
|
||||
pub contact_row: Interactive<ContainerStyle>,
|
||||
pub row_height: f32,
|
||||
pub project_row: Interactive<ProjectRow>,
|
||||
pub tree_branch: Interactive<TreeBranch>,
|
||||
pub contact_avatar: ImageStyle,
|
||||
pub contact_status_free: ContainerStyle,
|
||||
pub contact_status_busy: ContainerStyle,
|
||||
|
@ -112,6 +114,19 @@ pub struct ContactList {
|
|||
pub calling_indicator: ContainedText,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
pub struct ProjectRow {
|
||||
#[serde(flatten)]
|
||||
pub container: ContainerStyle,
|
||||
pub name: ContainedText,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Clone, Copy)]
|
||||
pub struct TreeBranch {
|
||||
pub width: f32,
|
||||
pub color: Color,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
pub struct ContactFinder {
|
||||
pub picker: Picker,
|
||||
|
|
|
@ -12,6 +12,31 @@ export default function contactList(theme: Theme) {
|
|||
buttonWidth: 16,
|
||||
cornerRadius: 8,
|
||||
};
|
||||
const projectRow = {
|
||||
guestAvatarSpacing: 4,
|
||||
height: 24,
|
||||
guestAvatar: {
|
||||
cornerRadius: 8,
|
||||
width: 14,
|
||||
},
|
||||
name: {
|
||||
...text(theme, "mono", "placeholder", { size: "sm" }),
|
||||
margin: {
|
||||
left: nameMargin,
|
||||
right: 6,
|
||||
},
|
||||
},
|
||||
guests: {
|
||||
margin: {
|
||||
left: nameMargin,
|
||||
right: nameMargin,
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
left: sidePadding,
|
||||
right: sidePadding,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
userQueryEditor: {
|
||||
|
@ -129,6 +154,30 @@ export default function contactList(theme: Theme) {
|
|||
},
|
||||
callingIndicator: {
|
||||
...text(theme, "mono", "muted", { size: "xs" })
|
||||
}
|
||||
},
|
||||
treeBranch: {
|
||||
color: borderColor(theme, "active"),
|
||||
width: 1,
|
||||
hover: {
|
||||
color: borderColor(theme, "active"),
|
||||
},
|
||||
active: {
|
||||
color: borderColor(theme, "active"),
|
||||
},
|
||||
},
|
||||
projectRow: {
|
||||
...projectRow,
|
||||
background: backgroundColor(theme, 300),
|
||||
name: {
|
||||
...projectRow.name,
|
||||
...text(theme, "mono", "secondary", { size: "sm" }),
|
||||
},
|
||||
hover: {
|
||||
background: backgroundColor(theme, 300, "hovered"),
|
||||
},
|
||||
active: {
|
||||
background: backgroundColor(theme, 300, "active"),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue