Availability indicators
This commit is contained in:
parent
4ebae4d8bd
commit
3bdd51cb2a
5 changed files with 111 additions and 149 deletions
|
@ -175,7 +175,7 @@ use gpui::{
|
||||||
Point, PromptLevel, Render, RenderOnce, SharedString, Stateful, Styled, Subscription, Task,
|
Point, PromptLevel, Render, RenderOnce, SharedString, Stateful, Styled, Subscription, Task,
|
||||||
View, ViewContext, VisualContext, WeakView,
|
View, ViewContext, VisualContext, WeakView,
|
||||||
};
|
};
|
||||||
use project::Fs;
|
use project::{Fs, Project};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
use ui::{
|
use ui::{
|
||||||
|
@ -299,7 +299,7 @@ pub struct CollabPanel {
|
||||||
channel_store: Model<ChannelStore>,
|
channel_store: Model<ChannelStore>,
|
||||||
user_store: Model<UserStore>,
|
user_store: Model<UserStore>,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
// project: ModelHandle<Project>,
|
project: Model<Project>,
|
||||||
match_candidates: Vec<StringMatchCandidate>,
|
match_candidates: Vec<StringMatchCandidate>,
|
||||||
// list_state: ListState<Self>,
|
// list_state: ListState<Self>,
|
||||||
subscriptions: Vec<Subscription>,
|
subscriptions: Vec<Subscription>,
|
||||||
|
@ -582,7 +582,7 @@ impl CollabPanel {
|
||||||
selection: None,
|
selection: None,
|
||||||
channel_store: ChannelStore::global(cx),
|
channel_store: ChannelStore::global(cx),
|
||||||
user_store: workspace.user_store().clone(),
|
user_store: workspace.user_store().clone(),
|
||||||
// project: workspace.project().clone(),
|
project: workspace.project().clone(),
|
||||||
subscriptions: Vec::default(),
|
subscriptions: Vec::default(),
|
||||||
match_candidates: Vec::default(),
|
match_candidates: Vec::default(),
|
||||||
collapsed_sections: vec![Section::Offline],
|
collapsed_sections: vec![Section::Offline],
|
||||||
|
@ -2280,18 +2280,13 @@ impl CollabPanel {
|
||||||
// .detach();
|
// .detach();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fn call(
|
fn call(&mut self, recipient_user_id: u64, cx: &mut ViewContext<Self>) {
|
||||||
// &mut self,
|
ActiveCall::global(cx)
|
||||||
// recipient_user_id: u64,
|
.update(cx, |call, cx| {
|
||||||
// initial_project: Option<ModelHandle<Project>>,
|
call.invite(recipient_user_id, Some(self.project.clone()), cx)
|
||||||
// cx: &mut ViewContext<Self>,
|
})
|
||||||
// ) {
|
.detach_and_log_err(cx);
|
||||||
// ActiveCall::global(cx)
|
}
|
||||||
// .update(cx, |call, cx| {
|
|
||||||
// call.invite(recipient_user_id, initial_project, cx)
|
|
||||||
// })
|
|
||||||
// .detach_and_log_err(cx);
|
|
||||||
// }
|
|
||||||
|
|
||||||
fn join_channel(&self, channel_id: u64, cx: &mut ViewContext<Self>) {
|
fn join_channel(&self, channel_id: u64, cx: &mut ViewContext<Self>) {
|
||||||
let Some(handle) = cx.window_handle().downcast::<Workspace>() else {
|
let Some(handle) = cx.window_handle().downcast::<Workspace>() else {
|
||||||
|
@ -2473,23 +2468,11 @@ impl CollabPanel {
|
||||||
.on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx)))
|
.on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx)))
|
||||||
.tooltip(|cx| Tooltip::text("Search for new contact", cx)),
|
.tooltip(|cx| Tooltip::text("Search for new contact", cx)),
|
||||||
),
|
),
|
||||||
Section::Channels => {
|
Section::Channels => Some(
|
||||||
// todo!()
|
IconButton::new("add-channel", Icon::Plus)
|
||||||
// if cx
|
.on_click(cx.listener(|this, _, cx| this.new_root_channel(cx)))
|
||||||
// .global::<DragAndDrop<Workspace>>()
|
.tooltip(|cx| Tooltip::text("Create a channel", cx)),
|
||||||
// .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)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2557,113 +2540,57 @@ impl CollabPanel {
|
||||||
.w_full()
|
.w_full()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.child(Label::new(github_login.clone()))
|
.child(Label::new(github_login.clone()))
|
||||||
.child(
|
.when(calling, |el| {
|
||||||
div()
|
el.child(Label::new("Calling").color(Color::Muted))
|
||||||
.id("remove_contact")
|
})
|
||||||
.invisible()
|
.when(!calling, |el| {
|
||||||
.group_hover("", |style| style.visible())
|
el.child(
|
||||||
.child(
|
div()
|
||||||
IconButton::new("remove_contact", Icon::Close)
|
.id("remove_contact")
|
||||||
.color(Color::Muted)
|
.invisible()
|
||||||
.tooltip(|cx| Tooltip::text("Remove Contact", cx))
|
.group_hover("", |style| style.visible())
|
||||||
.on_click(cx.listener(move |this, _, cx| {
|
.child(
|
||||||
this.remove_contact(user_id, &github_login, cx);
|
IconButton::new("remove_contact", Icon::Close)
|
||||||
})),
|
.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() {
|
div()
|
||||||
item = item.left_avatar(avatar);
|
.id(github_login.clone())
|
||||||
}
|
.group("")
|
||||||
|
.child(item)
|
||||||
div().group("").child(item)
|
.tooltip(move |cx| {
|
||||||
// let event_handler =
|
let text = if !online {
|
||||||
// MouseEventHandler::new::<Contact, _>(contact.user.id as usize, cx, |state, cx| {
|
format!(" {} is offline", &github_login)
|
||||||
// Flex::row()
|
} else if busy {
|
||||||
// .with_children(contact.user.avatar.clone().map(|avatar| {
|
format!(" {} is on a call", &github_login)
|
||||||
// let status_badge = if contact.online {
|
} else {
|
||||||
// Some(
|
let room = ActiveCall::global(cx).read(cx).room();
|
||||||
// Empty::new()
|
if room.is_some() {
|
||||||
// .collapsed()
|
format!("Invite {} to join call", &github_login)
|
||||||
// .contained()
|
} else {
|
||||||
// .with_style(if busy {
|
format!("Call {}", &github_login)
|
||||||
// collab_theme.contact_status_busy
|
}
|
||||||
// } else {
|
};
|
||||||
// collab_theme.contact_status_free
|
Tooltip::text(text, cx)
|
||||||
// })
|
})
|
||||||
// .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()
|
|
||||||
// };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_contact_request(
|
fn render_contact_request(
|
||||||
|
@ -2831,8 +2758,7 @@ impl CollabPanel {
|
||||||
h_stack()
|
h_stack()
|
||||||
.id(channel_id as usize)
|
.id(channel_id as usize)
|
||||||
.child(Label::new(channel.name.clone()))
|
.child(Label::new(channel.name.clone()))
|
||||||
.children(face_pile.map(|face_pile| face_pile.render(cx)))
|
.children(face_pile.map(|face_pile| face_pile.render(cx))),
|
||||||
.tooltip(|cx| Tooltip::text("Join channel", cx)),
|
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
h_stack()
|
h_stack()
|
||||||
|
@ -2894,6 +2820,7 @@ impl CollabPanel {
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
|
.tooltip(|cx| Tooltip::text("Join channel", cx))
|
||||||
|
|
||||||
// let channel_id = channel.id;
|
// let channel_id = channel.id;
|
||||||
// let collab_theme = &theme.collab_panel;
|
// let collab_theme = &theme.collab_panel;
|
||||||
|
|
|
@ -1034,7 +1034,7 @@ impl sqlez::bindable::Bind for GlobalPixels {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default, Add, Sub, Mul, Div, Neg)]
|
#[derive(Clone, Copy, Default, Add, Sub, Mul, Div, Neg)]
|
||||||
pub struct Rems(f32);
|
pub struct Rems(pub f32);
|
||||||
|
|
||||||
impl Mul<Pixels> for Rems {
|
impl Mul<Pixels> for Rems {
|
||||||
type Output = Pixels;
|
type Output = Pixels;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use gpui::{img, ImageData, ImageSource, Img, IntoElement};
|
use gpui::{img, rems, Div, ImageData, ImageSource, IntoElement, Styled};
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq, Clone)]
|
#[derive(Debug, Default, PartialEq, Clone)]
|
||||||
pub enum Shape {
|
pub enum Shape {
|
||||||
|
@ -13,13 +13,14 @@ pub enum Shape {
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct Avatar {
|
pub struct Avatar {
|
||||||
src: ImageSource,
|
src: ImageSource,
|
||||||
|
is_available: Option<bool>,
|
||||||
shape: Shape,
|
shape: Shape,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderOnce for Avatar {
|
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();
|
let mut img = img();
|
||||||
|
|
||||||
if self.shape == Shape::Circle {
|
if self.shape == Shape::Circle {
|
||||||
|
@ -28,10 +29,29 @@ impl RenderOnce for Avatar {
|
||||||
img = img.rounded_md();
|
img = img.rounded_md();
|
||||||
}
|
}
|
||||||
|
|
||||||
img.source(self.src.clone())
|
let size = rems(1.0);
|
||||||
.size_4()
|
|
||||||
// todo!(Pull the avatar fallback background from the theme.)
|
div()
|
||||||
.bg(gpui::red())
|
.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 {
|
Self {
|
||||||
src: src.into().into(),
|
src: src.into().into(),
|
||||||
shape: Shape::Circle,
|
shape: Shape::Circle,
|
||||||
|
is_available: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn data(src: Arc<ImageData>) -> Self {
|
pub fn data(src: Arc<ImageData>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
src: src.into(),
|
src: src.into(),
|
||||||
shape: Shape::Circle,
|
shape: Shape::Circle,
|
||||||
|
is_available: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,10 +75,15 @@ impl Avatar {
|
||||||
Self {
|
Self {
|
||||||
src,
|
src,
|
||||||
shape: Shape::Circle,
|
shape: Shape::Circle,
|
||||||
|
is_available: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn shape(mut self, shape: Shape) -> Self {
|
pub fn shape(mut self, shape: Shape) -> Self {
|
||||||
self.shape = shape;
|
self.shape = shape;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
pub fn availability_indicator(mut self, is_available: impl Into<Option<bool>>) -> Self {
|
||||||
|
self.is_available = is_available.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ impl ListItem {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn left_content(mut self, left_content: impl IntoElement) -> Self {
|
pub fn left_child(mut self, left_content: impl IntoElement) -> Self {
|
||||||
self.left_slot = Some(left_content.into_any_element());
|
self.left_slot = Some(left_content.into_any_element());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,5 +19,13 @@ impl Render for AvatarStory {
|
||||||
.child(Avatar::uri(
|
.child(Avatar::uri(
|
||||||
"https://avatars.githubusercontent.com/u/326587?v=4",
|
"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),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue