In titlebar, render followers and allow following people
This commit is contained in:
parent
1fcd006b2a
commit
282e4398a0
2 changed files with 255 additions and 231 deletions
|
@ -1,41 +1,14 @@
|
||||||
// use crate::{
|
use crate::face_pile::FacePile;
|
||||||
// face_pile::FacePile, toggle_deafen, toggle_mute, toggle_screen_sharing, LeaveCall,
|
use call::{ActiveCall, Room};
|
||||||
// ToggleDeafen, ToggleMute, ToggleScreenSharing,
|
use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore};
|
||||||
// };
|
|
||||||
// use auto_update::AutoUpdateStatus;
|
|
||||||
// use call::{ActiveCall, ParticipantLocation, Room};
|
|
||||||
// use client::{proto::PeerId, Client, SignIn, SignOut, User, UserStore};
|
|
||||||
// use clock::ReplicaId;
|
|
||||||
// use context_menu::{ContextMenu, ContextMenuItem};
|
|
||||||
// use gpui::{
|
|
||||||
// actions,
|
|
||||||
// color::Color,
|
|
||||||
// elements::*,
|
|
||||||
// geometry::{rect::RectF, vector::vec2f, PathBuilder},
|
|
||||||
// json::{self, ToJson},
|
|
||||||
// platform::{CursorStyle, MouseButton},
|
|
||||||
// AppContext, Entity, ImageData, ModelHandle, Subscription, View, ViewContext, ViewHandle,
|
|
||||||
// WeakViewHandle,
|
|
||||||
// };
|
|
||||||
// use picker::PickerEvent;
|
|
||||||
// use project::{Project, RepositoryEntry};
|
|
||||||
// use recent_projects::{build_recent_projects, RecentProjects};
|
|
||||||
// use std::{ops::Range, sync::Arc};
|
|
||||||
// use theme::{AvatarStyle, Theme};
|
|
||||||
// use util::ResultExt;
|
|
||||||
// use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu};
|
|
||||||
// use workspace::{FollowNextCollaborator, Workspace, WORKSPACE_DB};
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use call::ActiveCall;
|
|
||||||
use client::{Client, UserStore};
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, px, rems, AppContext, Div, Element, InteractiveElement, IntoElement, Model,
|
actions, canvas, div, point, px, rems, AppContext, Div, Element, InteractiveElement,
|
||||||
MouseButton, ParentElement, Render, RenderOnce, Stateful, StatefulInteractiveElement, Styled,
|
IntoElement, Model, ParentElement, Path, Render, RenderOnce, Stateful,
|
||||||
Subscription, ViewContext, VisualContext, WeakView, WindowBounds,
|
StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext, WeakView,
|
||||||
|
WindowBounds,
|
||||||
};
|
};
|
||||||
use project::{Project, RepositoryEntry};
|
use project::{Project, RepositoryEntry};
|
||||||
|
use std::sync::Arc;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::{
|
use ui::{
|
||||||
h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon,
|
h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon,
|
||||||
|
@ -44,8 +17,6 @@ use ui::{
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{notifications::NotifyResultExt, Workspace};
|
use workspace::{notifications::NotifyResultExt, Workspace};
|
||||||
|
|
||||||
use crate::face_pile::FacePile;
|
|
||||||
|
|
||||||
const MAX_PROJECT_NAME_LENGTH: usize = 40;
|
const MAX_PROJECT_NAME_LENGTH: usize = 40;
|
||||||
const MAX_BRANCH_NAME_LENGTH: usize = 40;
|
const MAX_BRANCH_NAME_LENGTH: usize = 40;
|
||||||
|
|
||||||
|
@ -57,17 +28,6 @@ actions!(
|
||||||
SwitchBranch
|
SwitchBranch
|
||||||
);
|
);
|
||||||
|
|
||||||
// actions!(
|
|
||||||
// collab,
|
|
||||||
// [
|
|
||||||
// ToggleUserMenu,
|
|
||||||
// ToggleProjectMenu,
|
|
||||||
// SwitchBranch,
|
|
||||||
// ShareProject,
|
|
||||||
// UnshareProject,
|
|
||||||
// ]
|
|
||||||
// );
|
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
cx.observe_new_views(|workspace: &mut Workspace, cx| {
|
cx.observe_new_views(|workspace: &mut Workspace, cx| {
|
||||||
let titlebar_item = cx.build_view(|cx| CollabTitlebarItem::new(workspace, cx));
|
let titlebar_item = cx.build_view(|cx| CollabTitlebarItem::new(workspace, cx));
|
||||||
|
@ -99,28 +59,11 @@ impl Render for CollabTitlebarItem {
|
||||||
type Element = Stateful<Div>;
|
type Element = Stateful<Div>;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
let room = ActiveCall::global(cx).read(cx).room();
|
let room = ActiveCall::global(cx).read(cx).room().cloned();
|
||||||
let is_in_room = room.is_some();
|
|
||||||
let is_shared = is_in_room && self.project.read(cx).is_shared();
|
|
||||||
let current_user = self.user_store.read(cx).current_user();
|
let current_user = self.user_store.read(cx).current_user();
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let remote_participants = room.map(|room| {
|
let project_id = self.project.read(cx).remote_id();
|
||||||
room.read(cx)
|
|
||||||
.remote_participants()
|
|
||||||
.values()
|
|
||||||
.map(|participant| (participant.user.clone(), participant.peer_id))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
});
|
|
||||||
let is_muted = room.map_or(false, |room| room.read(cx).is_muted(cx));
|
|
||||||
let is_deafened = room
|
|
||||||
.and_then(|room| room.read(cx).is_deafened())
|
|
||||||
.unwrap_or(false);
|
|
||||||
let speakers_icon = if is_deafened {
|
|
||||||
ui::Icon::AudioOff
|
|
||||||
} else {
|
|
||||||
ui::Icon::AudioOn
|
|
||||||
};
|
|
||||||
let workspace = self.workspace.clone();
|
|
||||||
h_stack()
|
h_stack()
|
||||||
.id("titlebar")
|
.id("titlebar")
|
||||||
.justify_between()
|
.justify_between()
|
||||||
|
@ -141,182 +84,224 @@ impl Render for CollabTitlebarItem {
|
||||||
cx.zoom_window();
|
cx.zoom_window();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
// left side
|
||||||
.child(
|
.child(
|
||||||
h_stack()
|
h_stack()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.when(is_in_room, |this| {
|
.children(self.render_project_host(cx))
|
||||||
this.children(self.render_project_owner(cx))
|
|
||||||
})
|
|
||||||
.child(self.render_project_name(cx))
|
.child(self.render_project_name(cx))
|
||||||
.children(self.render_project_branch(cx)),
|
.children(self.render_project_branch(cx))
|
||||||
)
|
.when_some(
|
||||||
.when_some(
|
current_user.clone().zip(room.clone()).zip(project_id),
|
||||||
remote_participants.zip(current_user.clone()),
|
|this, ((current_user, room), project_id)| {
|
||||||
|this, (remote_participants, current_user)| {
|
let remote_participants = room
|
||||||
let mut pile = FacePile::default();
|
.read(cx)
|
||||||
pile.extend(
|
.remote_participants()
|
||||||
current_user
|
.values()
|
||||||
.avatar
|
.map(|participant| {
|
||||||
.clone()
|
(
|
||||||
.map(|avatar| {
|
participant.user.clone(),
|
||||||
div().child(Avatar::data(avatar.clone())).into_any_element()
|
participant.participant_index,
|
||||||
})
|
participant.peer_id,
|
||||||
.into_iter()
|
|
||||||
.chain(remote_participants.into_iter().filter_map(
|
|
||||||
|(user, peer_id)| {
|
|
||||||
let avatar = user.avatar.as_ref()?;
|
|
||||||
Some(
|
|
||||||
div()
|
|
||||||
.child(
|
|
||||||
Avatar::data(avatar.clone())
|
|
||||||
.into_element()
|
|
||||||
.into_any(),
|
|
||||||
)
|
|
||||||
.on_mouse_down(MouseButton::Left, {
|
|
||||||
let workspace = workspace.clone();
|
|
||||||
move |_, cx| {
|
|
||||||
workspace
|
|
||||||
.update(cx, |this, cx| {
|
|
||||||
this.open_shared_screen(peer_id, cx);
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.into_any_element(),
|
|
||||||
)
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
this.children(
|
||||||
|
self.render_collaborator(
|
||||||
|
¤t_user,
|
||||||
|
client.peer_id().expect("todo!()"),
|
||||||
|
&room,
|
||||||
|
project_id,
|
||||||
|
&remote_participants,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.map(|pile| pile.render(cx)),
|
||||||
|
)
|
||||||
|
.children(
|
||||||
|
remote_participants.iter().filter_map(
|
||||||
|
|(user, participant_index, peer_id)| {
|
||||||
|
let peer_id = *peer_id;
|
||||||
|
let face_pile = self
|
||||||
|
.render_collaborator(
|
||||||
|
user,
|
||||||
|
peer_id,
|
||||||
|
&room,
|
||||||
|
project_id,
|
||||||
|
&remote_participants,
|
||||||
|
cx,
|
||||||
|
)?
|
||||||
|
.render(cx);
|
||||||
|
Some(
|
||||||
|
v_stack()
|
||||||
|
.id(("collaborator", user.id))
|
||||||
|
.child(face_pile)
|
||||||
|
.child(render_color_ribbon(*participant_index, cx))
|
||||||
|
.cursor_pointer()
|
||||||
|
.on_click(cx.listener(move |this, _, cx| {
|
||||||
|
this.workspace
|
||||||
|
.update(cx, |workspace, cx| {
|
||||||
|
workspace.follow(peer_id, cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// right side
|
||||||
|
.child(
|
||||||
|
h_stack()
|
||||||
|
.gap_1()
|
||||||
|
.when_some(room, |this, room| {
|
||||||
|
let room = room.read(cx);
|
||||||
|
let is_shared = self.project.read(cx).is_shared();
|
||||||
|
let is_muted = room.is_muted(cx);
|
||||||
|
let is_deafened = room.is_deafened().unwrap_or(false);
|
||||||
|
|
||||||
|
this.child(
|
||||||
|
Button::new(
|
||||||
|
"toggle_sharing",
|
||||||
|
if is_shared { "Unshare" } else { "Share" },
|
||||||
|
)
|
||||||
|
.style(ButtonStyle::Subtle)
|
||||||
|
.on_click(cx.listener(
|
||||||
|
move |this, _, cx| {
|
||||||
|
if is_shared {
|
||||||
|
this.unshare_project(&Default::default(), cx);
|
||||||
|
} else {
|
||||||
|
this.share_project(&Default::default(), cx);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
);
|
|
||||||
this.child(pile.render(cx))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.child(div().flex_1())
|
|
||||||
.when(is_in_room, |this| {
|
|
||||||
this.child(
|
|
||||||
h_stack()
|
|
||||||
.gap_1()
|
|
||||||
.child(
|
|
||||||
h_stack()
|
|
||||||
.gap_1()
|
|
||||||
.child(
|
|
||||||
Button::new(
|
|
||||||
"toggle_sharing",
|
|
||||||
if is_shared { "Unshare" } else { "Share" },
|
|
||||||
)
|
|
||||||
.style(ButtonStyle::Subtle)
|
|
||||||
.on_click(cx.listener(
|
|
||||||
move |this, _, cx| {
|
|
||||||
if is_shared {
|
|
||||||
this.unshare_project(&Default::default(), cx);
|
|
||||||
} else {
|
|
||||||
this.share_project(&Default::default(), cx);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
IconButton::new("leave-call", ui::Icon::Exit)
|
|
||||||
.style(ButtonStyle::Subtle)
|
|
||||||
.on_click(move |_, cx| {
|
|
||||||
ActiveCall::global(cx)
|
|
||||||
.update(cx, |call, cx| call.hang_up(cx))
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
h_stack()
|
IconButton::new("leave-call", ui::Icon::Exit)
|
||||||
.gap_1()
|
.style(ButtonStyle::Subtle)
|
||||||
.child(
|
.on_click(move |_, cx| {
|
||||||
IconButton::new(
|
ActiveCall::global(cx)
|
||||||
"mute-microphone",
|
.update(cx, |call, cx| call.hang_up(cx))
|
||||||
if is_muted {
|
.detach_and_log_err(cx);
|
||||||
ui::Icon::MicMute
|
}),
|
||||||
} else {
|
)
|
||||||
ui::Icon::Mic
|
.child(
|
||||||
},
|
IconButton::new(
|
||||||
)
|
"mute-microphone",
|
||||||
.style(ButtonStyle::Subtle)
|
if is_muted {
|
||||||
.selected(is_muted)
|
ui::Icon::MicMute
|
||||||
.on_click(move |_, cx| {
|
} else {
|
||||||
crate::toggle_mute(&Default::default(), cx)
|
ui::Icon::Mic
|
||||||
}),
|
},
|
||||||
)
|
)
|
||||||
.child(
|
.style(ButtonStyle::Subtle)
|
||||||
IconButton::new("mute-sound", speakers_icon)
|
.selected(is_muted)
|
||||||
.style(ButtonStyle::Subtle)
|
.on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
|
||||||
.selected(is_deafened.clone())
|
)
|
||||||
.tooltip(move |cx| {
|
.child(
|
||||||
Tooltip::with_meta(
|
IconButton::new(
|
||||||
"Deafen Audio",
|
"mute-sound",
|
||||||
None,
|
if is_deafened {
|
||||||
"Mic will be muted",
|
ui::Icon::AudioOff
|
||||||
cx,
|
} else {
|
||||||
)
|
ui::Icon::AudioOn
|
||||||
})
|
},
|
||||||
.on_click(move |_, cx| {
|
)
|
||||||
crate::toggle_mute(&Default::default(), cx)
|
.style(ButtonStyle::Subtle)
|
||||||
}),
|
.selected(is_deafened.clone())
|
||||||
)
|
.tooltip(move |cx| {
|
||||||
.child(
|
Tooltip::with_meta("Deafen Audio", None, "Mic will be muted", cx)
|
||||||
IconButton::new("screen-share", ui::Icon::Screen)
|
})
|
||||||
.style(ButtonStyle::Subtle)
|
.on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
|
||||||
.on_click(move |_, cx| {
|
)
|
||||||
crate::toggle_screen_sharing(&Default::default(), cx)
|
.child(
|
||||||
}),
|
IconButton::new("screen-share", ui::Icon::Screen)
|
||||||
)
|
.style(ButtonStyle::Subtle)
|
||||||
.pl_2(),
|
.on_click(move |_, cx| {
|
||||||
),
|
crate::toggle_screen_sharing(&Default::default(), cx)
|
||||||
)
|
}),
|
||||||
})
|
|
||||||
.child(h_stack().px_1p5().map(|this| {
|
|
||||||
if let Some(user) = current_user {
|
|
||||||
this.when_some(user.avatar.clone(), |this, avatar| {
|
|
||||||
// TODO: Finish implementing user menu popover
|
|
||||||
//
|
|
||||||
this.child(
|
|
||||||
popover_menu("user-menu")
|
|
||||||
.menu(|cx| ContextMenu::build(cx, |menu, _| menu.header("ADADA")))
|
|
||||||
.trigger(
|
|
||||||
ButtonLike::new("user-menu")
|
|
||||||
.child(
|
|
||||||
h_stack().gap_0p5().child(Avatar::data(avatar)).child(
|
|
||||||
IconElement::new(Icon::ChevronDown)
|
|
||||||
.color(Color::Muted),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.style(ButtonStyle::Subtle)
|
|
||||||
.tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),
|
|
||||||
)
|
|
||||||
.anchor(gpui::AnchorCorner::TopRight),
|
|
||||||
)
|
)
|
||||||
// this.child(
|
|
||||||
// ButtonLike::new("user-menu")
|
|
||||||
// .child(
|
|
||||||
// h_stack().gap_0p5().child(Avatar::data(avatar)).child(
|
|
||||||
// IconElement::new(Icon::ChevronDown).color(Color::Muted),
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
// .style(ButtonStyle::Subtle)
|
|
||||||
// .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),
|
|
||||||
// )
|
|
||||||
})
|
})
|
||||||
} else {
|
.child(h_stack().px_1p5().map(|this| {
|
||||||
this.child(Button::new("sign_in", "Sign in").on_click(move |_, cx| {
|
if let Some(user) = current_user {
|
||||||
let client = client.clone();
|
this.when_some(user.avatar.clone(), |this, avatar| {
|
||||||
cx.spawn(move |mut cx| async move {
|
// TODO: Finish implementing user menu popover
|
||||||
client
|
//
|
||||||
.authenticate_and_connect(true, &cx)
|
this.child(
|
||||||
.await
|
popover_menu("user-menu")
|
||||||
.notify_async_err(&mut cx);
|
.menu(|cx| {
|
||||||
})
|
ContextMenu::build(cx, |menu, _| menu.header("ADADA"))
|
||||||
.detach();
|
})
|
||||||
}))
|
.trigger(
|
||||||
}
|
ButtonLike::new("user-menu")
|
||||||
}))
|
.child(
|
||||||
|
h_stack()
|
||||||
|
.gap_0p5()
|
||||||
|
.child(Avatar::data(avatar))
|
||||||
|
.child(
|
||||||
|
IconElement::new(Icon::ChevronDown)
|
||||||
|
.color(Color::Muted),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.style(ButtonStyle::Subtle)
|
||||||
|
.tooltip(move |cx| {
|
||||||
|
Tooltip::text("Toggle User Menu", cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.anchor(gpui::AnchorCorner::TopRight),
|
||||||
|
)
|
||||||
|
// this.child(
|
||||||
|
// ButtonLike::new("user-menu")
|
||||||
|
// .child(
|
||||||
|
// h_stack().gap_0p5().child(Avatar::data(avatar)).child(
|
||||||
|
// IconElement::new(Icon::ChevronDown).color(Color::Muted),
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// .style(ButtonStyle::Subtle)
|
||||||
|
// .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),
|
||||||
|
// )
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.child(Button::new("sign_in", "Sign in").on_click(move |_, cx| {
|
||||||
|
let client = client.clone();
|
||||||
|
cx.spawn(move |mut cx| async move {
|
||||||
|
client
|
||||||
|
.authenticate_and_connect(true, &cx)
|
||||||
|
.await
|
||||||
|
.notify_async_err(&mut cx);
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_color_ribbon(
|
||||||
|
participant_index: ParticipantIndex,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> gpui::Canvas {
|
||||||
|
let color = cx
|
||||||
|
.theme()
|
||||||
|
.players()
|
||||||
|
.color_for_participant(participant_index.0)
|
||||||
|
.cursor;
|
||||||
|
canvas(move |bounds, cx| {
|
||||||
|
let mut path = Path::new(bounds.lower_left());
|
||||||
|
let height = bounds.size.height;
|
||||||
|
path.curve_to(bounds.origin + point(height, px(0.)), bounds.origin);
|
||||||
|
path.line_to(bounds.upper_right() - point(height, px(0.)));
|
||||||
|
path.curve_to(bounds.lower_right(), bounds.upper_right());
|
||||||
|
path.line_to(bounds.lower_left());
|
||||||
|
cx.paint_path(path, color);
|
||||||
|
})
|
||||||
|
.h_1()
|
||||||
|
.w_full()
|
||||||
|
}
|
||||||
|
|
||||||
// impl Entity for CollabTitlebarItem {
|
// impl Entity for CollabTitlebarItem {
|
||||||
// type Event = ();
|
// type Event = ();
|
||||||
// }
|
// }
|
||||||
|
@ -435,7 +420,7 @@ impl CollabTitlebarItem {
|
||||||
// resolve if you are in a room -> render_project_owner
|
// resolve if you are in a room -> render_project_owner
|
||||||
// render_project_owner -> resolve if you are in a room -> Option<foo>
|
// render_project_owner -> resolve if you are in a room -> Option<foo>
|
||||||
|
|
||||||
pub fn render_project_owner(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
|
pub fn render_project_host(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
|
||||||
let host = self.project.read(cx).host()?;
|
let host = self.project.read(cx).host()?;
|
||||||
let host = self.user_store.read(cx).get_cached_user(host.user_id)?;
|
let host = self.user_store.read(cx).get_cached_user(host.user_id)?;
|
||||||
let participant_index = self
|
let participant_index = self
|
||||||
|
@ -509,6 +494,39 @@ impl CollabTitlebarItem {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_collaborator(
|
||||||
|
&self,
|
||||||
|
user: &Arc<User>,
|
||||||
|
peer_id: PeerId,
|
||||||
|
room: &Model<Room>,
|
||||||
|
project_id: u64,
|
||||||
|
collaborators: &[(Arc<User>, ParticipantIndex, PeerId)],
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> Option<FacePile> {
|
||||||
|
let room = room.read(cx);
|
||||||
|
let followers = room.followers_for(peer_id, project_id);
|
||||||
|
|
||||||
|
let mut pile = FacePile::default();
|
||||||
|
pile.extend(
|
||||||
|
user.avatar
|
||||||
|
.clone()
|
||||||
|
.map(|avatar| div().child(Avatar::data(avatar.clone())).into_any_element())
|
||||||
|
.into_iter()
|
||||||
|
.chain(followers.iter().filter_map(|follower_peer_id| {
|
||||||
|
let follower = collaborators
|
||||||
|
.iter()
|
||||||
|
.find(|(_, _, peer_id)| *peer_id == *follower_peer_id)?
|
||||||
|
.0
|
||||||
|
.clone();
|
||||||
|
follower
|
||||||
|
.avatar
|
||||||
|
.clone()
|
||||||
|
.map(|avatar| div().child(Avatar::data(avatar.clone())).into_any_element())
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
Some(pile)
|
||||||
|
}
|
||||||
|
|
||||||
// fn collect_title_root_names(
|
// fn collect_title_root_names(
|
||||||
// &self,
|
// &self,
|
||||||
// theme: Arc<Theme>,
|
// theme: Arc<Theme>,
|
||||||
|
|
|
@ -2839,3 +2839,9 @@ impl From<(&'static str, usize)> for ElementId {
|
||||||
ElementId::NamedInteger(name.into(), id)
|
ElementId::NamedInteger(name.into(), id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<(&'static str, u64)> for ElementId {
|
||||||
|
fn from((name, id): (&'static str, u64)) -> Self {
|
||||||
|
ElementId::NamedInteger(name.into(), id as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue