Add logged out collab panel (#3412)

Release Notes:

- N/A
This commit is contained in:
Conrad Irwin
2023-11-28 11:49:09 -07:00
committed by GitHub
11 changed files with 1509 additions and 1474 deletions
Generated
+1
View File
@@ -11661,6 +11661,7 @@ dependencies = [
"auto_update2",
"backtrace",
"call2",
"channel2",
"chrono",
"cli",
"client2",
+5 -2
View File
@@ -660,9 +660,12 @@ impl CallHandler for Call {
self.active_call.as_ref().map(|call| {
call.0.update(cx, |this, cx| {
this.room().map(|room| {
room.update(cx, |this, cx| {
this.toggle_mute(cx).log_err();
let room = room.clone();
cx.spawn(|_, mut cx| async move {
room.update(&mut cx, |this, cx| this.toggle_mute(cx))??
.await
})
.detach_and_log_err(cx);
})
})
});
+7 -6
View File
@@ -1,4 +1,7 @@
use crate::participant::{LocalParticipant, ParticipantLocation, RemoteParticipant};
use crate::{
call_settings::CallSettings,
participant::{LocalParticipant, ParticipantLocation, RemoteParticipant},
};
use anyhow::{anyhow, Result};
use audio::{Audio, Sound};
use client::{
@@ -18,6 +21,7 @@ use live_kit_client::{
};
use postage::{sink::Sink, stream::Stream, watch};
use project::Project;
use settings::Settings as _;
use std::{future::Future, mem, sync::Arc, time::Duration};
use util::{post_inc, ResultExt, TryFutureExt};
@@ -328,10 +332,8 @@ impl Room {
}
}
pub fn mute_on_join(_cx: &AppContext) -> bool {
// todo!() po: This should be uncommented, though then unmuting does not work
false
//CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some()
pub fn mute_on_join(cx: &AppContext) -> bool {
CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some()
}
fn from_join_response(
@@ -1265,7 +1267,6 @@ impl Room {
.ok_or_else(|| anyhow!("live-kit was not initialized"))?
.await
};
let publication = publish_track.await;
this.upgrade()
.ok_or_else(|| anyhow!("room was dropped"))?
File diff suppressed because it is too large Load Diff
@@ -1,37 +1,34 @@
use client::{ContactRequestStatus, User, UserStore};
use gpui::{
elements::*, AppContext, Entity, ModelHandle, MouseState, Task, View, ViewContext, ViewHandle,
div, img, svg, AnyElement, AppContext, DismissEvent, Div, Entity, EventEmitter, FocusHandle,
FocusableView, Img, IntoElement, Model, ParentElement as _, Render, Styled, Task, View,
ViewContext, VisualContext, WeakView,
};
use picker::{Picker, PickerDelegate, PickerEvent};
use picker::{Picker, PickerDelegate};
use std::sync::Arc;
use util::TryFutureExt;
use workspace::Modal;
use theme::ActiveTheme as _;
use ui::{h_stack, v_stack, Label};
use util::{ResultExt as _, TryFutureExt};
pub fn init(cx: &mut AppContext) {
Picker::<ContactFinderDelegate>::init(cx);
cx.add_action(ContactFinder::dismiss)
//Picker::<ContactFinderDelegate>::init(cx);
//cx.add_action(ContactFinder::dismiss)
}
pub struct ContactFinder {
picker: ViewHandle<Picker<ContactFinderDelegate>>,
picker: View<Picker<ContactFinderDelegate>>,
has_focus: bool,
}
impl ContactFinder {
pub fn new(user_store: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) -> Self {
let picker = cx.add_view(|cx| {
Picker::new(
ContactFinderDelegate {
user_store,
potential_contacts: Arc::from([]),
selected_index: 0,
},
cx,
)
.with_theme(|theme| theme.collab_panel.tabbed_modal.picker.clone())
});
cx.subscribe(&picker, |_, _, e, cx| cx.emit(*e)).detach();
pub fn new(user_store: Model<UserStore>, cx: &mut ViewContext<Self>) -> Self {
let delegate = ContactFinderDelegate {
parent: cx.view().downgrade(),
user_store,
potential_contacts: Arc::from([]),
selected_index: 0,
};
let picker = cx.build_view(|cx| Picker::new(delegate, cx));
Self {
picker,
@@ -41,105 +38,72 @@ impl ContactFinder {
pub fn set_query(&mut self, query: String, cx: &mut ViewContext<Self>) {
self.picker.update(cx, |picker, cx| {
picker.set_query(query, cx);
// todo!()
// picker.set_query(query, cx);
});
}
fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
cx.emit(PickerEvent::Dismiss);
}
}
impl Entity for ContactFinder {
type Event = PickerEvent;
}
impl View for ContactFinder {
fn ui_name() -> &'static str {
"ContactFinder"
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let full_theme = &theme::current(cx);
let theme = &full_theme.collab_panel.tabbed_modal;
fn render_mode_button(
text: &'static str,
theme: &theme::TabbedModal,
_cx: &mut ViewContext<ContactFinder>,
) -> AnyElement<ContactFinder> {
let contained_text = &theme.tab_button.active_state().default;
Label::new(text, contained_text.text.clone())
.contained()
.with_style(contained_text.container.clone())
.into_any()
impl Render for ContactFinder {
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
fn render_mode_button(text: &'static str) -> AnyElement {
Label::new(text).into_any_element()
}
Flex::column()
.with_child(
Flex::column()
.with_child(
Label::new("Contacts", theme.title.text.clone())
.contained()
.with_style(theme.title.container.clone()),
)
.with_child(Flex::row().with_children([render_mode_button(
"Invite new contacts",
&theme,
cx,
)]))
.expanded()
.contained()
.with_style(theme.header),
v_stack()
.child(
v_stack()
.child(Label::new("Contacts"))
.child(h_stack().children([render_mode_button("Invite new contacts")]))
.bg(cx.theme().colors().element_background),
)
.with_child(
ChildView::new(&self.picker, cx)
.contained()
.with_style(theme.body),
)
.constrained()
.with_max_height(theme.max_height)
.with_max_width(theme.max_width)
.contained()
.with_style(theme.modal)
.into_any()
.child(self.picker.clone())
.w_96()
}
fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
self.has_focus = true;
if cx.is_self_focused() {
cx.focus(&self.picker)
}
}
// fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
// self.has_focus = true;
// if cx.is_self_focused() {
// cx.focus(&self.picker)
// }
// }
fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
self.has_focus = false;
}
// fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
// self.has_focus = false;
// }
type Element = Div;
}
impl Modal for ContactFinder {
fn has_focus(&self) -> bool {
self.has_focus
}
// impl Modal for ContactFinder {
// fn has_focus(&self) -> bool {
// self.has_focus
// }
fn dismiss_on_event(event: &Self::Event) -> bool {
match event {
PickerEvent::Dismiss => true,
}
}
}
// fn dismiss_on_event(event: &Self::Event) -> bool {
// match event {
// PickerEvent::Dismiss => true,
// }
// }
// }
pub struct ContactFinderDelegate {
parent: WeakView<ContactFinder>,
potential_contacts: Arc<[Arc<User>]>,
user_store: ModelHandle<UserStore>,
user_store: Model<UserStore>,
selected_index: usize,
}
impl PickerDelegate for ContactFinderDelegate {
fn placeholder_text(&self) -> Arc<str> {
"Search collaborator by username...".into()
}
impl EventEmitter<DismissEvent> for ContactFinder {}
impl FocusableView for ContactFinder {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl PickerDelegate for ContactFinderDelegate {
type ListItem = Div;
fn match_count(&self) -> usize {
self.potential_contacts.len()
}
@@ -152,6 +116,10 @@ impl PickerDelegate for ContactFinderDelegate {
self.selected_index = ix;
}
fn placeholder_text(&self) -> Arc<str> {
"Search collaborator by username...".into()
}
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
let search_users = self
.user_store
@@ -161,7 +129,7 @@ impl PickerDelegate for ContactFinderDelegate {
async {
let potential_contacts = search_users.await?;
picker.update(&mut cx, |picker, cx| {
picker.delegate_mut().potential_contacts = potential_contacts.into();
picker.delegate.potential_contacts = potential_contacts.into();
cx.notify();
})?;
anyhow::Ok(())
@@ -191,19 +159,18 @@ impl PickerDelegate for ContactFinderDelegate {
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
cx.emit(PickerEvent::Dismiss);
//cx.emit(PickerEvent::Dismiss);
self.parent
.update(cx, |_, cx| cx.emit(DismissEvent::Dismiss))
.log_err();
}
fn render_match(
&self,
ix: usize,
mouse_state: &mut MouseState,
selected: bool,
cx: &gpui::AppContext,
) -> AnyElement<Picker<Self>> {
let full_theme = &theme::current(cx);
let theme = &full_theme.collab_panel.contact_finder;
let tabbed_modal = &full_theme.collab_panel.tabbed_modal;
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let user = &self.potential_contacts[ix];
let request_status = self.user_store.read(cx).contact_request_status(user);
@@ -214,48 +181,47 @@ impl PickerDelegate for ContactFinderDelegate {
ContactRequestStatus::RequestSent => Some("icons/x.svg"),
ContactRequestStatus::RequestAccepted => None,
};
let button_style = if self.user_store.read(cx).is_contact_request_pending(user) {
&theme.disabled_contact_button
} else {
&theme.contact_button
};
let style = tabbed_modal
.picker
.item
.in_state(selected)
.style_for(mouse_state);
Flex::row()
.with_children(user.avatar.clone().map(|avatar| {
Image::from_data(avatar)
.with_style(theme.contact_avatar)
.aligned()
.left()
}))
.with_child(
Label::new(user.github_login.clone(), style.label.clone())
.contained()
.with_style(theme.contact_username)
.aligned()
.left(),
)
.with_children(icon_path.map(|icon_path| {
Svg::new(icon_path)
.with_color(button_style.color)
.constrained()
.with_width(button_style.icon_width)
.aligned()
.contained()
.with_style(button_style.container)
.constrained()
.with_width(button_style.button_width)
.with_height(button_style.button_width)
.aligned()
.flex_float()
}))
.contained()
.with_style(style.container)
.constrained()
.with_height(tabbed_modal.row_height)
.into_any()
dbg!(icon_path);
Some(
div()
.flex_1()
.justify_between()
.children(user.avatar.clone().map(|avatar| img().data(avatar)))
.child(Label::new(user.github_login.clone()))
.children(icon_path.map(|icon_path| svg().path(icon_path))),
)
// Flex::row()
// .with_children(user.avatar.clone().map(|avatar| {
// Image::from_data(avatar)
// .with_style(theme.contact_avatar)
// .aligned()
// .left()
// }))
// .with_child(
// Label::new(user.github_login.clone(), style.label.clone())
// .contained()
// .with_style(theme.contact_username)
// .aligned()
// .left(),
// )
// .with_children(icon_path.map(|icon_path| {
// Svg::new(icon_path)
// .with_color(button_style.color)
// .constrained()
// .with_width(button_style.icon_width)
// .aligned()
// .contained()
// .with_style(button_style.container)
// .constrained()
// .with_width(button_style.button_width)
// .with_height(button_style.button_width)
// .aligned()
// .flex_float()
// }))
// .contained()
// .with_style(style.container)
// .constrained()
// .with_height(tabbed_modal.row_height)
// .into_any()
}
}
@@ -39,7 +39,7 @@ use project::Project;
use theme::ActiveTheme;
use ui::{h_stack, Avatar, Button, ButtonVariant, Color, IconButton, KeyBinding, Tooltip};
use util::ResultExt;
use workspace::Workspace;
use workspace::{notifications::NotifyResultExt, Workspace};
use crate::face_pile::FacePile;
@@ -290,11 +290,13 @@ impl Render for CollabTitlebarItem {
} else {
this.child(Button::new("Sign in").on_click(move |_, cx| {
let client = client.clone();
cx.spawn(move |cx| async move {
client.authenticate_and_connect(true, &cx).await?;
Ok::<(), anyhow::Error>(())
cx.spawn(move |mut cx| async move {
client
.authenticate_and_connect(true, &cx)
.await
.notify_async_err(&mut cx);
})
.detach_and_log_err(cx);
.detach();
}))
}
})
+6
View File
@@ -49,6 +49,12 @@ impl Avatar {
}
}
pub fn source(src: ImageSource) -> Self {
Self {
src,
shape: Shape::Circle,
}
}
pub fn shape(mut self, shape: Shape) -> Self {
self.shape = shape;
self
+12 -21
View File
@@ -1,13 +1,14 @@
use std::rc::Rc;
use gpui::{
div, px, AnyElement, ClickEvent, Div, IntoElement, MouseButton, MouseDownEvent, Pixels,
Stateful, StatefulInteractiveElement,
div, px, AnyElement, ClickEvent, Div, ImageSource, IntoElement, MouseButton, MouseDownEvent,
Pixels, Stateful, StatefulInteractiveElement,
};
use smallvec::SmallVec;
use crate::{
disclosure_control, h_stack, v_stack, Avatar, Icon, IconElement, IconSize, Label, Toggle,
disclosure_control, h_stack, v_stack, Avatar, Icon, IconButton, IconElement, IconSize, Label,
Toggle,
};
use crate::{prelude::*, GraphicSlot};
@@ -20,8 +21,7 @@ pub enum ListItemVariant {
}
pub enum ListHeaderMeta {
// TODO: These should be IconButtons
Tools(Vec<Icon>),
Tools(Vec<IconButton>),
// TODO: This should be a button
Button(Label),
Text(Label),
@@ -47,11 +47,7 @@ impl RenderOnce for ListHeader {
h_stack()
.gap_2()
.items_center()
.children(icons.into_iter().map(|i| {
IconElement::new(i)
.color(Color::Muted)
.size(IconSize::Small)
})),
.children(icons.into_iter().map(|i| i.color(Color::Muted))),
),
Some(ListHeaderMeta::Button(label)) => div().child(label),
Some(ListHeaderMeta::Text(label)) => div().child(label),
@@ -115,6 +111,10 @@ impl ListHeader {
self
}
pub fn right_button(self, button: IconButton) -> Self {
self.meta(Some(ListHeaderMeta::Tools(vec![button])))
}
pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
self.meta = meta;
self
@@ -257,7 +257,7 @@ impl ListItem {
self
}
pub fn left_avatar(mut self, left_avatar: impl Into<SharedString>) -> Self {
pub fn left_avatar(mut self, left_avatar: impl Into<ImageSource>) -> Self {
self.left_slot = Some(GraphicSlot::Avatar(left_avatar.into()));
self
}
@@ -275,7 +275,7 @@ impl RenderOnce for ListItem {
.color(Color::Muted),
),
),
Some(GraphicSlot::Avatar(src)) => Some(h_stack().child(Avatar::uri(src))),
Some(GraphicSlot::Avatar(src)) => Some(h_stack().child(Avatar::source(src))),
Some(GraphicSlot::PublicActor(src)) => Some(h_stack().child(Avatar::uri(src))),
None => None,
};
@@ -297,15 +297,6 @@ impl RenderOnce for ListItem {
.when(self.selected, |this| {
this.bg(cx.theme().colors().ghost_element_selected)
})
.when_some(self.on_click.clone(), |this, on_click| {
this.on_click(move |event, cx| {
// HACK: GPUI currently fires `on_click` with any mouse button,
// but we only care about the left button.
if event.down.button == MouseButton::Left {
(on_click)(event, cx)
}
})
})
.when_some(self.on_secondary_mouse_down, |this, on_mouse_down| {
this.on_mouse_down(MouseButton::Right, move |event, cx| {
(on_mouse_down)(event, cx)
+2 -2
View File
@@ -1,4 +1,4 @@
use gpui::SharedString;
use gpui::{ImageSource, SharedString};
use crate::Icon;
@@ -9,6 +9,6 @@ use crate::Icon;
/// Can be filled with a []
pub enum GraphicSlot {
Icon(Icon),
Avatar(SharedString),
Avatar(ImageSource),
PublicActor(SharedString),
}
+1 -1
View File
@@ -21,7 +21,7 @@ audio = { package = "audio2", path = "../audio2" }
auto_update = { package = "auto_update2", path = "../auto_update2" }
# breadcrumbs = { path = "../breadcrumbs" }
call = { package = "call2", path = "../call2" }
# channel = { path = "../channel" }
channel = { package = "channel2", path = "../channel2" }
cli = { path = "../cli" }
collab_ui = { package = "collab_ui2", path = "../collab_ui2" }
collections = { path = "../collections" }
+2 -2
View File
@@ -189,7 +189,7 @@ fn main() {
let app_state = Arc::new(AppState {
languages,
client: client.clone(),
user_store,
user_store: user_store.clone(),
fs,
build_window_options,
call_factory: call::Call::new,
@@ -210,7 +210,7 @@ fn main() {
// outline::init(cx);
// project_symbols::init(cx);
project_panel::init(Assets, cx);
// channel::init(&client, user_store.clone(), cx);
channel::init(&client, user_store.clone(), cx);
// diagnostics::init(cx);
search::init(cx);
// semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx);