Allow clicking a titlebar avatar to initiate following
This commit is contained in:
parent
d860ed25c1
commit
e338da0271
3 changed files with 134 additions and 114 deletions
|
@ -184,6 +184,12 @@ macro_rules! action {
|
||||||
Box::new(self.clone())
|
Box::new(self.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<$arg> for $name {
|
||||||
|
fn from(arg: $arg) -> Self {
|
||||||
|
Self(arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
($name:ident) => {
|
($name:ident) => {
|
||||||
|
|
|
@ -4272,8 +4272,8 @@ mod tests {
|
||||||
workspace_b
|
workspace_b
|
||||||
.update(cx_b, |workspace, cx| {
|
.update(cx_b, |workspace, cx| {
|
||||||
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
||||||
let leader_id = project_b.read(cx).collaborators().keys().next().unwrap();
|
let leader_id = *project_b.read(cx).collaborators().keys().next().unwrap();
|
||||||
workspace.follow(*leader_id, cx)
|
workspace.follow(&leader_id.into(), cx).unwrap()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -4291,8 +4291,7 @@ mod tests {
|
||||||
});
|
});
|
||||||
workspace_b
|
workspace_b
|
||||||
.condition(cx_b, |workspace, cx| {
|
.condition(cx_b, |workspace, cx| {
|
||||||
let active_item = workspace.active_item(cx).unwrap();
|
workspace.active_item(cx).unwrap().id() == editor_b1.id()
|
||||||
active_item.project_path(cx) == Some((worktree_id, "1.txt").into())
|
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use theme::{Theme, ThemeRegistry};
|
use theme::{Theme, ThemeRegistry};
|
||||||
use util::{ResultExt, TryFutureExt};
|
use util::ResultExt;
|
||||||
|
|
||||||
type ProjectItemBuilders = HashMap<
|
type ProjectItemBuilders = HashMap<
|
||||||
TypeId,
|
TypeId,
|
||||||
|
@ -68,6 +68,7 @@ action!(Open, Arc<AppState>);
|
||||||
action!(OpenNew, Arc<AppState>);
|
action!(OpenNew, Arc<AppState>);
|
||||||
action!(OpenPaths, OpenParams);
|
action!(OpenPaths, OpenParams);
|
||||||
action!(ToggleShare);
|
action!(ToggleShare);
|
||||||
|
action!(FollowCollaborator, PeerId);
|
||||||
action!(JoinProject, JoinProjectParams);
|
action!(JoinProject, JoinProjectParams);
|
||||||
action!(Save);
|
action!(Save);
|
||||||
action!(DebugElements);
|
action!(DebugElements);
|
||||||
|
@ -88,6 +89,7 @@ pub fn init(client: &Arc<Client>, cx: &mut MutableAppContext) {
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.add_action(Workspace::toggle_share);
|
cx.add_action(Workspace::toggle_share);
|
||||||
|
cx.add_async_action(Workspace::follow);
|
||||||
cx.add_action(
|
cx.add_action(
|
||||||
|workspace: &mut Workspace, _: &Save, cx: &mut ViewContext<Workspace>| {
|
|workspace: &mut Workspace, _: &Save, cx: &mut ViewContext<Workspace>| {
|
||||||
workspace.save_active_item(cx).detach_and_log_err(cx);
|
workspace.save_active_item(cx).detach_and_log_err(cx);
|
||||||
|
@ -1192,88 +1194,90 @@ impl Workspace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn follow(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
|
pub fn follow(
|
||||||
if let Some(project_id) = self.project.read(cx).remote_id() {
|
&mut self,
|
||||||
let request = self.client.request(proto::Follow {
|
FollowCollaborator(leader_id): &FollowCollaborator,
|
||||||
project_id,
|
cx: &mut ViewContext<Self>,
|
||||||
leader_id: leader_id.0,
|
) -> Option<Task<Result<()>>> {
|
||||||
});
|
let leader_id = *leader_id;
|
||||||
cx.spawn_weak(|this, mut cx| async move {
|
let project_id = self.project.read(cx).remote_id()?;
|
||||||
let mut response = request.await?;
|
let request = self.client.request(proto::Follow {
|
||||||
if let Some(this) = this.upgrade(&cx) {
|
project_id,
|
||||||
let mut item_tasks = Vec::new();
|
leader_id: leader_id.0,
|
||||||
let (project, pane) = this.read_with(&cx, |this, _| {
|
});
|
||||||
(this.project.clone(), this.active_pane().clone())
|
Some(cx.spawn_weak(|this, mut cx| async move {
|
||||||
});
|
let mut response = request.await?;
|
||||||
let item_builders = cx.update(|cx| {
|
if let Some(this) = this.upgrade(&cx) {
|
||||||
cx.default_global::<FollowableItemBuilders>()
|
let mut item_tasks = Vec::new();
|
||||||
.values()
|
let (project, pane) = this.read_with(&cx, |this, _| {
|
||||||
.map(|b| b.0)
|
(this.project.clone(), this.active_pane().clone())
|
||||||
.collect::<Vec<_>>()
|
});
|
||||||
.clone()
|
let item_builders = cx.update(|cx| {
|
||||||
});
|
cx.default_global::<FollowableItemBuilders>()
|
||||||
for view in &mut response.views {
|
.values()
|
||||||
let variant = view
|
.map(|b| b.0)
|
||||||
.variant
|
.collect::<Vec<_>>()
|
||||||
.take()
|
.clone()
|
||||||
.ok_or_else(|| anyhow!("missing variant"))?;
|
});
|
||||||
cx.update(|cx| {
|
for view in &mut response.views {
|
||||||
let mut variant = Some(variant);
|
let variant = view
|
||||||
for build_item in &item_builders {
|
.variant
|
||||||
if let Some(task) =
|
.take()
|
||||||
build_item(pane.clone(), project.clone(), &mut variant, cx)
|
.ok_or_else(|| anyhow!("missing variant"))?;
|
||||||
{
|
cx.update(|cx| {
|
||||||
item_tasks.push(task);
|
let mut variant = Some(variant);
|
||||||
break;
|
for build_item in &item_builders {
|
||||||
} else {
|
if let Some(task) =
|
||||||
assert!(variant.is_some());
|
build_item(pane.clone(), project.clone(), &mut variant, cx)
|
||||||
}
|
{
|
||||||
|
item_tasks.push(task);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
assert!(variant.is_some());
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.follower_states_by_leader
|
|
||||||
.entry(leader_id)
|
|
||||||
.or_default()
|
|
||||||
.insert(
|
|
||||||
pane.downgrade(),
|
|
||||||
FollowerState {
|
|
||||||
active_view_id: response.active_view_id.map(|id| id as usize),
|
|
||||||
items_by_leader_view_id: Default::default(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
let items = futures::future::try_join_all(item_tasks).await?;
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
let follower_state = this
|
|
||||||
.follower_states_by_leader
|
|
||||||
.entry(leader_id)
|
|
||||||
.or_default()
|
|
||||||
.entry(pane.downgrade())
|
|
||||||
.or_default();
|
|
||||||
for (id, item) in response.views.iter().map(|v| v.id as usize).zip(items) {
|
|
||||||
let prev_state = follower_state.items_by_leader_view_id.remove(&id);
|
|
||||||
if let Some(FollowerItem::Loading(updates)) = prev_state {
|
|
||||||
for update in updates {
|
|
||||||
item.apply_update_message(update, cx)
|
|
||||||
.context("failed to apply view update")
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
follower_state
|
|
||||||
.items_by_leader_view_id
|
|
||||||
.insert(id, FollowerItem::Loaded(item));
|
|
||||||
}
|
}
|
||||||
this.leader_updated(leader_id, cx);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
})
|
this.update(&mut cx, |this, cx| {
|
||||||
} else {
|
this.follower_states_by_leader
|
||||||
Task::ready(Err(anyhow!("project is not remote")))
|
.entry(leader_id)
|
||||||
}
|
.or_default()
|
||||||
|
.insert(
|
||||||
|
pane.downgrade(),
|
||||||
|
FollowerState {
|
||||||
|
active_view_id: response.active_view_id.map(|id| id as usize),
|
||||||
|
items_by_leader_view_id: Default::default(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
let items = futures::future::try_join_all(item_tasks).await?;
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
let follower_state = this
|
||||||
|
.follower_states_by_leader
|
||||||
|
.entry(leader_id)
|
||||||
|
.or_default()
|
||||||
|
.entry(pane.downgrade())
|
||||||
|
.or_default();
|
||||||
|
for (id, item) in response.views.iter().map(|v| v.id as usize).zip(items) {
|
||||||
|
let prev_state = follower_state.items_by_leader_view_id.remove(&id);
|
||||||
|
if let Some(FollowerItem::Loading(updates)) = prev_state {
|
||||||
|
for update in updates {
|
||||||
|
item.apply_update_message(update, cx)
|
||||||
|
.context("failed to apply view update")
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
follower_state
|
||||||
|
.items_by_leader_view_id
|
||||||
|
.insert(id, FollowerItem::Loaded(item));
|
||||||
|
}
|
||||||
|
this.leader_updated(leader_id, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_followers(
|
fn update_followers(
|
||||||
|
@ -1383,7 +1387,9 @@ impl Workspace {
|
||||||
Some(self.render_avatar(
|
Some(self.render_avatar(
|
||||||
collaborator.user.avatar.clone()?,
|
collaborator.user.avatar.clone()?,
|
||||||
collaborator.replica_id,
|
collaborator.replica_id,
|
||||||
|
Some(collaborator.peer_id),
|
||||||
theme,
|
theme,
|
||||||
|
cx,
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -1397,7 +1403,7 @@ impl Workspace {
|
||||||
cx: &mut RenderContext<Self>,
|
cx: &mut RenderContext<Self>,
|
||||||
) -> ElementBox {
|
) -> ElementBox {
|
||||||
if let Some(avatar) = user.and_then(|user| user.avatar.clone()) {
|
if let Some(avatar) = user.and_then(|user| user.avatar.clone()) {
|
||||||
self.render_avatar(avatar, replica_id, theme)
|
self.render_avatar(avatar, replica_id, None, theme, cx)
|
||||||
} else {
|
} else {
|
||||||
MouseEventHandler::new::<Authenticate, _, _>(0, cx, |state, _| {
|
MouseEventHandler::new::<Authenticate, _, _>(0, cx, |state, _| {
|
||||||
let style = if state.hovered {
|
let style = if state.hovered {
|
||||||
|
@ -1421,52 +1427,61 @@ impl Workspace {
|
||||||
&self,
|
&self,
|
||||||
avatar: Arc<ImageData>,
|
avatar: Arc<ImageData>,
|
||||||
replica_id: ReplicaId,
|
replica_id: ReplicaId,
|
||||||
|
peer_id: Option<PeerId>,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
|
cx: &mut RenderContext<Self>,
|
||||||
) -> ElementBox {
|
) -> ElementBox {
|
||||||
ConstrainedBox::new(
|
let content = Stack::new()
|
||||||
Stack::new()
|
.with_child(
|
||||||
.with_child(
|
Image::new(avatar)
|
||||||
ConstrainedBox::new(
|
.with_style(theme.workspace.titlebar.avatar)
|
||||||
Image::new(avatar)
|
.constrained()
|
||||||
.with_style(theme.workspace.titlebar.avatar)
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.with_width(theme.workspace.titlebar.avatar_width)
|
.with_width(theme.workspace.titlebar.avatar_width)
|
||||||
.aligned()
|
.aligned()
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
.with_child(
|
.with_child(
|
||||||
AvatarRibbon::new(theme.editor.replica_selection_style(replica_id).cursor)
|
AvatarRibbon::new(theme.editor.replica_selection_style(replica_id).cursor)
|
||||||
.constrained()
|
.constrained()
|
||||||
.with_width(theme.workspace.titlebar.avatar_ribbon.width)
|
.with_width(theme.workspace.titlebar.avatar_ribbon.width)
|
||||||
.with_height(theme.workspace.titlebar.avatar_ribbon.height)
|
.with_height(theme.workspace.titlebar.avatar_ribbon.height)
|
||||||
.aligned()
|
.aligned()
|
||||||
.bottom()
|
.bottom()
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
.boxed(),
|
.constrained()
|
||||||
)
|
.with_width(theme.workspace.right_sidebar.width)
|
||||||
.with_width(theme.workspace.right_sidebar.width)
|
.boxed();
|
||||||
.boxed()
|
|
||||||
|
if let Some(peer_id) = peer_id {
|
||||||
|
MouseEventHandler::new::<FollowCollaborator, _, _>(
|
||||||
|
replica_id.into(),
|
||||||
|
cx,
|
||||||
|
move |_, _| content,
|
||||||
|
)
|
||||||
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
|
.on_click(move |cx| cx.dispatch_action(FollowCollaborator(peer_id)))
|
||||||
|
.boxed()
|
||||||
|
} else {
|
||||||
|
content
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_share_icon(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Option<ElementBox> {
|
fn render_share_icon(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Option<ElementBox> {
|
||||||
if self.project().read(cx).is_local() && self.client.user_id().is_some() {
|
if self.project().read(cx).is_local() && self.client.user_id().is_some() {
|
||||||
enum Share {}
|
|
||||||
|
|
||||||
let color = if self.project().read(cx).is_shared() {
|
let color = if self.project().read(cx).is_shared() {
|
||||||
theme.workspace.titlebar.share_icon_active_color
|
theme.workspace.titlebar.share_icon_active_color
|
||||||
} else {
|
} else {
|
||||||
theme.workspace.titlebar.share_icon_color
|
theme.workspace.titlebar.share_icon_color
|
||||||
};
|
};
|
||||||
Some(
|
Some(
|
||||||
MouseEventHandler::new::<Share, _, _>(0, cx, |_, _| {
|
MouseEventHandler::new::<ToggleShare, _, _>(0, cx, |_, _| {
|
||||||
Align::new(
|
Align::new(
|
||||||
ConstrainedBox::new(
|
Svg::new("icons/broadcast-24.svg")
|
||||||
Svg::new("icons/broadcast-24.svg").with_color(color).boxed(),
|
.with_color(color)
|
||||||
)
|
.constrained()
|
||||||
.with_width(24.)
|
.with_width(24.)
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
.boxed()
|
.boxed()
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue