collab panel2 (#3447)

[[PR Description]]

Release Notes:

- N/A
This commit is contained in:
Conrad Irwin 2023-11-29 16:38:35 -07:00 committed by GitHub
commit 481e42ade9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 144 additions and 175 deletions

View file

@ -175,7 +175,7 @@ use gpui::{
Point, PromptLevel, Render, RenderOnce, SharedString, Stateful, Styled, Subscription, Task,
View, ViewContext, VisualContext, WeakView,
};
use project::Fs;
use project::{Fs, Project};
use serde_derive::{Deserialize, Serialize};
use settings::{Settings, SettingsStore};
use ui::prelude::*;
@ -300,7 +300,7 @@ pub struct CollabPanel {
channel_store: Model<ChannelStore>,
user_store: Model<UserStore>,
client: Arc<Client>,
// project: ModelHandle<Project>,
project: Model<Project>,
match_candidates: Vec<StringMatchCandidate>,
// list_state: ListState<Self>,
subscriptions: Vec<Subscription>,
@ -583,7 +583,7 @@ impl CollabPanel {
selection: None,
channel_store: ChannelStore::global(cx),
user_store: workspace.user_store().clone(),
// project: workspace.project().clone(),
project: workspace.project().clone(),
subscriptions: Vec::default(),
match_candidates: Vec::default(),
collapsed_sections: vec![Section::Offline],
@ -2281,18 +2281,13 @@ impl CollabPanel {
// .detach();
// }
// fn call(
// &mut self,
// recipient_user_id: u64,
// initial_project: Option<ModelHandle<Project>>,
// cx: &mut ViewContext<Self>,
// ) {
// ActiveCall::global(cx)
// .update(cx, |call, cx| {
// call.invite(recipient_user_id, initial_project, cx)
// })
// .detach_and_log_err(cx);
// }
fn call(&mut self, recipient_user_id: u64, cx: &mut ViewContext<Self>) {
ActiveCall::global(cx)
.update(cx, |call, cx| {
call.invite(recipient_user_id, Some(self.project.clone()), cx)
})
.detach_and_log_err(cx);
}
fn join_channel(&self, channel_id: u64, cx: &mut ViewContext<Self>) {
let Some(handle) = cx.window_handle().downcast::<Workspace>() else {
@ -2476,23 +2471,11 @@ impl CollabPanel {
.on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx)))
.tooltip(|cx| Tooltip::text("Search for new contact", cx)),
),
Section::Channels => {
// todo!()
// if cx
// .global::<DragAndDrop<Workspace>>()
// .currently_dragged::<Channel>(cx.window())
// .is_some()
// && self.drag_target_channel == ChannelDragTarget::Root
// {
// is_dragged_over = true;
// }
Some(
IconButton::new("add-channel", Icon::Plus)
.on_click(cx.listener(|this, _, cx| this.new_root_channel(cx)))
.tooltip(|cx| Tooltip::text("Create a channel", cx)),
)
}
Section::Channels => Some(
IconButton::new("add-channel", Icon::Plus)
.on_click(cx.listener(|this, _, cx| this.new_root_channel(cx)))
.tooltip(|cx| Tooltip::text("Create a channel", cx)),
),
_ => None,
};
@ -2504,18 +2487,26 @@ impl CollabPanel {
| Section::Offline => true,
};
let header = ListHeader::new(text)
.when_some(button, |el, button| el.right_button(button))
.selected(is_selected)
.when(can_collapse, |el| {
el.toggle(is_collapsed).on_toggle(
cx.listener(move |this, _, cx| this.toggle_section_expanded(section, cx)),
)
});
h_stack()
.w_full()
.child(header)
.map(|el| {
if can_collapse {
el.child(
ListItem::new(text.clone())
.child(div().w_full().child(Label::new(text)))
.toggle(Some(!is_collapsed))
.on_click(cx.listener(move |this, _, cx| {
this.toggle_section_expanded(section, cx)
})),
)
} else {
el.child(
ListHeader::new(text)
.when_some(button, |el, button| el.right_button(button))
.selected(is_selected),
)
}
})
.when(section == Section::Channels, |el| {
el.drag_over::<DraggedChannelView>(|style| {
style.bg(cx.theme().colors().ghost_element_hover)
@ -2560,113 +2551,57 @@ impl CollabPanel {
.w_full()
.justify_between()
.child(Label::new(github_login.clone()))
.child(
div()
.id("remove_contact")
.invisible()
.group_hover("", |style| style.visible())
.child(
IconButton::new("remove_contact", Icon::Close)
.icon_color(Color::Muted)
.tooltip(|cx| Tooltip::text("Remove Contact", cx))
.on_click(cx.listener(move |this, _, cx| {
this.remove_contact(user_id, &github_login, cx);
})),
),
),
);
.when(calling, |el| {
el.child(Label::new("Calling").color(Color::Muted))
})
.when(!calling, |el| {
el.child(
div()
.id("remove_contact")
.invisible()
.group_hover("", |style| style.visible())
.child(
IconButton::new("remove_contact", Icon::Close)
.icon_color(Color::Muted)
.tooltip(|cx| Tooltip::text("Remove Contact", cx))
.on_click(cx.listener({
let github_login = github_login.clone();
move |this, _, cx| {
this.remove_contact(user_id, &github_login, cx);
}
})),
),
)
}),
)
.left_child(
// todo!() handle contacts with no avatar
Avatar::data(contact.user.avatar.clone().unwrap())
.availability_indicator(if online { Some(!busy) } else { None }),
)
.when(online && !busy, |el| {
el.on_click(cx.listener(move |this, _, cx| this.call(user_id, cx)))
});
if let Some(avatar) = contact.user.avatar.clone() {
item = item.left_avatar(avatar);
}
div().group("").child(item)
// let event_handler =
// MouseEventHandler::new::<Contact, _>(contact.user.id as usize, cx, |state, cx| {
// Flex::row()
// .with_children(contact.user.avatar.clone().map(|avatar| {
// let status_badge = if contact.online {
// Some(
// Empty::new()
// .collapsed()
// .contained()
// .with_style(if busy {
// collab_theme.contact_status_busy
// } else {
// collab_theme.contact_status_free
// })
// .aligned(),
// )
// } else {
// None
// };
// Stack::new()
// .with_child(
// Image::from_data(avatar)
// .with_style(collab_theme.contact_avatar)
// .aligned()
// .left(),
// )
// .with_children(status_badge)
// }))
// .with_children(if calling {
// Some(
// Label::new("Calling", collab_theme.calling_indicator.text.clone())
// .contained()
// .with_style(collab_theme.calling_indicator.container)
// .aligned(),
// )
// } else {
// None
// })
// .constrained()
// .with_height(collab_theme.row_height)
// .contained()
// .with_style(
// *collab_theme
// .contact_row
// .in_state(is_selected)
// .style_for(state),
// )
// });
// if online && !busy {
// let room = ActiveCall::global(cx).read(cx).room();
// let label = if room.is_some() {
// format!("Invite {} to join call", contact.user.github_login)
// } else {
// format!("Call {}", contact.user.github_login)
// };
// event_handler
// .on_click(MouseButton::Left, move |_, this, cx| {
// this.call(user_id, Some(initial_project.clone()), cx);
// })
// .with_cursor_style(CursorStyle::PointingHand)
// .with_tooltip::<ContactTooltip>(
// contact.user.id as usize,
// label,
// None,
// theme.tooltip.clone(),
// cx,
// )
// .into_any()
// } else {
// event_handler
// .with_tooltip::<ContactTooltip>(
// contact.user.id as usize,
// format!(
// "{} is {}",
// contact.user.github_login,
// if busy { "on a call" } else { "offline" }
// ),
// None,
// theme.tooltip.clone(),
// cx,
// )
// .into_any()
// };
div()
.id(github_login.clone())
.group("")
.child(item)
.tooltip(move |cx| {
let text = if !online {
format!(" {} is offline", &github_login)
} else if busy {
format!(" {} is on a call", &github_login)
} else {
let room = ActiveCall::global(cx).read(cx).room();
if room.is_some() {
format!("Invite {} to join call", &github_login)
} else {
format!("Call {}", &github_login)
}
};
Tooltip::text(text, cx)
})
}
fn render_contact_request(
@ -2834,8 +2769,7 @@ impl CollabPanel {
h_stack()
.id(channel_id as usize)
.child(Label::new(channel.name.clone()))
.children(face_pile.map(|face_pile| face_pile.render(cx)))
.tooltip(|cx| Tooltip::text("Join channel", cx)),
.children(face_pile.map(|face_pile| face_pile.render(cx))),
)
.child(
h_stack()
@ -2897,6 +2831,7 @@ impl CollabPanel {
},
)),
)
.tooltip(|cx| Tooltip::text("Join channel", cx))
// let channel_id = channel.id;
// let collab_theme = &theme.collab_panel;
@ -3279,12 +3214,15 @@ impl CollabPanel {
// }
impl Render for CollabPanel {
type Element = Focusable<Div>;
type Element = Focusable<Stateful<Div>>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
div()
.id("collab-panel")
.key_context("CollabPanel")
.track_focus(&self.focus_handle)
.size_full()
.overflow_scroll()
.on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::insert_space))
.map(|el| {

View file

@ -1034,7 +1034,7 @@ impl sqlez::bindable::Bind for GlobalPixels {
}
#[derive(Clone, Copy, Default, Add, Sub, Mul, Div, Neg)]
pub struct Rems(f32);
pub struct Rems(pub f32);
impl Mul<Pixels> for Rems {
type Output = Pixels;

View file

@ -1,7 +1,7 @@
use std::sync::Arc;
use crate::prelude::*;
use gpui::{img, ImageData, ImageSource, Img, IntoElement};
use gpui::{img, rems, Div, ImageData, ImageSource, IntoElement, Styled};
#[derive(Debug, Default, PartialEq, Clone)]
pub enum Shape {
@ -13,13 +13,14 @@ pub enum Shape {
#[derive(IntoElement)]
pub struct Avatar {
src: ImageSource,
is_available: Option<bool>,
shape: Shape,
}
impl RenderOnce for Avatar {
type Rendered = Img;
type Rendered = Div;
fn render(self, _: &mut WindowContext) -> Self::Rendered {
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let mut img = img();
if self.shape == Shape::Circle {
@ -28,10 +29,29 @@ impl RenderOnce for Avatar {
img = img.rounded_md();
}
img.source(self.src.clone())
.size_4()
// todo!(Pull the avatar fallback background from the theme.)
.bg(gpui::red())
let size = rems(1.0);
div()
.size(size)
.child(
img.source(self.src.clone())
.size(size)
// todo!(Pull the avatar fallback background from the theme.)
.bg(gpui::red()),
)
.children(self.is_available.map(|is_free| {
// HACK: non-integer sizes result in oval indicators.
let indicator_size = (size.0 * cx.rem_size() * 0.4).round();
div()
.absolute()
.z_index(1)
.bg(if is_free { gpui::green() } else { gpui::red() })
.size(indicator_size)
.rounded(indicator_size)
.bottom_0()
.right_0()
}))
}
}
@ -40,12 +60,14 @@ impl Avatar {
Self {
src: src.into().into(),
shape: Shape::Circle,
is_available: None,
}
}
pub fn data(src: Arc<ImageData>) -> Self {
Self {
src: src.into(),
shape: Shape::Circle,
is_available: None,
}
}
@ -53,10 +75,15 @@ impl Avatar {
Self {
src,
shape: Shape::Circle,
is_available: None,
}
}
pub fn shape(mut self, shape: Shape) -> Self {
self.shape = shape;
self
}
pub fn availability_indicator(mut self, is_available: impl Into<Option<bool>>) -> Self {
self.is_available = is_available.into();
self
}
}

View file

@ -6,7 +6,7 @@ use gpui::{
use smallvec::SmallVec;
use crate::prelude::*;
use crate::{Avatar, Disclosure, GraphicSlot, Icon, IconElement, IconSize};
use crate::{Avatar, Disclosure, Icon, IconElement, IconSize};
#[derive(IntoElement)]
pub struct ListItem {
@ -16,7 +16,7 @@ pub struct ListItem {
// disclosure_control_style: DisclosureControlVisibility,
indent_level: usize,
indent_step_size: Pixels,
left_slot: Option<GraphicSlot>,
left_slot: Option<AnyElement>,
toggle: Option<bool>,
inset: bool,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
@ -88,18 +88,23 @@ impl ListItem {
self
}
pub fn left_content(mut self, left_content: GraphicSlot) -> Self {
self.left_slot = Some(left_content);
pub fn left_child(mut self, left_content: impl IntoElement) -> Self {
self.left_slot = Some(left_content.into_any_element());
self
}
pub fn left_icon(mut self, left_icon: Icon) -> Self {
self.left_slot = Some(GraphicSlot::Icon(left_icon));
self.left_slot = Some(
IconElement::new(left_icon)
.size(IconSize::Small)
.color(Color::Muted)
.into_any_element(),
);
self
}
pub fn left_avatar(mut self, left_avatar: impl Into<ImageSource>) -> Self {
self.left_slot = Some(GraphicSlot::Avatar(left_avatar.into()));
self.left_slot = Some(Avatar::source(left_avatar.into()).into_any_element());
self
}
}
@ -154,16 +159,7 @@ impl RenderOnce for ListItem {
self.toggle
.map(|is_open| Disclosure::new(is_open).on_toggle(self.on_toggle)),
)
.map(|this| match self.left_slot {
Some(GraphicSlot::Icon(i)) => this.child(
IconElement::new(i)
.size(IconSize::Small)
.color(Color::Muted),
),
Some(GraphicSlot::Avatar(src)) => this.child(Avatar::source(src)),
Some(GraphicSlot::PublicActor(src)) => this.child(Avatar::uri(src)),
None => this,
})
.children(self.left_slot)
.children(self.children),
)
}

View file

@ -19,5 +19,13 @@ impl Render for AvatarStory {
.child(Avatar::uri(
"https://avatars.githubusercontent.com/u/326587?v=4",
))
.child(
Avatar::uri("https://avatars.githubusercontent.com/u/326587?v=4")
.availability_indicator(true),
)
.child(
Avatar::uri("https://avatars.githubusercontent.com/u/326587?v=4")
.availability_indicator(false),
)
}
}