Cargo.lock 🔗
@@ -11661,6 +11661,7 @@ dependencies = [
"auto_update2",
"backtrace",
"call2",
+ "channel2",
"chrono",
"cli",
"client2",
Conrad Irwin created
Release Notes:
- N/A
Cargo.lock | 1
crates/call2/src/call2.rs | 7
crates/call2/src/room.rs | 13
crates/collab_ui2/src/collab_panel.rs | 1114 +++++++------
crates/collab_ui2/src/collab_panel/contact_finder.rs | 266 +-
crates/collab_ui2/src/collab_titlebar_item.rs | 12
crates/ui2/src/components/avatar.rs | 6
crates/ui2/src/components/list.rs | 33
crates/ui2/src/components/slot.rs | 4
crates/zed2/Cargo.toml | 2
crates/zed2/src/main.rs | 4
11 files changed, 718 insertions(+), 744 deletions(-)
@@ -11661,6 +11661,7 @@ dependencies = [
"auto_update2",
"backtrace",
"call2",
+ "channel2",
"chrono",
"cli",
"client2",
@@ -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);
})
})
});
@@ -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"))?
@@ -1,5 +1,6 @@
+#![allow(unused)]
// mod channel_modal;
-// mod contact_finder;
+mod contact_finder;
// use crate::{
// channel_view::{self, ChannelView},
@@ -15,7 +16,7 @@
// proto::{self, PeerId},
// Client, Contact, User, UserStore,
// };
-// use contact_finder::ContactFinder;
+use contact_finder::ContactFinder;
// use context_menu::{ContextMenu, ContextMenuItem};
// use db::kvp::KEY_VALUE_STORE;
// use drag_and_drop::{DragAndDrop, Draggable};
@@ -155,22 +156,30 @@ actions!(
const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
-use std::sync::Arc;
+use std::{iter::once, mem, sync::Arc};
-use client::{Client, Contact, UserStore};
+use call::ActiveCall;
+use channel::{Channel, ChannelId, ChannelStore};
+use client::{Client, Contact, User, UserStore};
use db::kvp::KEY_VALUE_STORE;
+use editor::Editor;
+use feature_flags::{ChannelsAlpha, FeatureFlagAppExt};
+use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
- actions, div, serde_json, AppContext, AsyncWindowContext, Div, EventEmitter, FocusHandle,
- Focusable, FocusableView, InteractiveElement, Model, ParentElement, Render, Styled, View,
- ViewContext, VisualContext, WeakView,
+ actions, div, img, serde_json, AppContext, AsyncWindowContext, Div, EventEmitter, FocusHandle,
+ Focusable, FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render,
+ RenderOnce, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView,
};
use project::Fs;
use serde_derive::{Deserialize, Serialize};
use settings::Settings;
-use ui::{h_stack, Avatar, Label};
-use util::ResultExt;
+use ui::{
+ h_stack, v_stack, Avatar, Button, Icon, IconButton, Label, List, ListHeader, ListItem, Tooltip,
+};
+use util::{maybe, ResultExt};
use workspace::{
dock::{DockPosition, Panel, PanelEvent},
+ notifications::NotifyResultExt,
Workspace,
};
@@ -268,26 +277,26 @@ pub fn init(cx: &mut AppContext) {
// );
}
-// #[derive(Debug)]
-// pub enum ChannelEditingState {
-// Create {
-// location: Option<ChannelId>,
-// pending_name: Option<String>,
-// },
-// Rename {
-// location: ChannelId,
-// pending_name: Option<String>,
-// },
-// }
+#[derive(Debug)]
+pub enum ChannelEditingState {
+ Create {
+ location: Option<ChannelId>,
+ pending_name: Option<String>,
+ },
+ Rename {
+ location: ChannelId,
+ pending_name: Option<String>,
+ },
+}
-// impl ChannelEditingState {
-// fn pending_name(&self) -> Option<&str> {
-// match self {
-// ChannelEditingState::Create { pending_name, .. } => pending_name.as_deref(),
-// ChannelEditingState::Rename { pending_name, .. } => pending_name.as_deref(),
-// }
-// }
-// }
+impl ChannelEditingState {
+ fn pending_name(&self) -> Option<&str> {
+ match self {
+ ChannelEditingState::Create { pending_name, .. } => pending_name.as_deref(),
+ ChannelEditingState::Rename { pending_name, .. } => pending_name.as_deref(),
+ }
+ }
+}
pub struct CollabPanel {
width: Option<f32>,
@@ -296,22 +305,22 @@ pub struct CollabPanel {
// channel_clipboard: Option<ChannelMoveClipboard>,
// pending_serialization: Task<Option<()>>,
// context_menu: ViewHandle<ContextMenu>,
- // filter_editor: ViewHandle<Editor>,
+ filter_editor: View<Editor>,
// channel_name_editor: ViewHandle<Editor>,
- // channel_editing_state: Option<ChannelEditingState>,
- // entries: Vec<ListEntry>,
+ channel_editing_state: Option<ChannelEditingState>,
+ entries: Vec<ListEntry>,
// selection: Option<usize>,
+ channel_store: Model<ChannelStore>,
user_store: Model<UserStore>,
- _client: Arc<Client>,
- // channel_store: ModelHandle<ChannelStore>,
+ client: Arc<Client>,
// project: ModelHandle<Project>,
- // match_candidates: Vec<StringMatchCandidate>,
+ match_candidates: Vec<StringMatchCandidate>,
// list_state: ListState<Self>,
- // subscriptions: Vec<Subscription>,
- // collapsed_sections: Vec<Section>,
- // collapsed_channels: Vec<ChannelId>,
+ subscriptions: Vec<Subscription>,
+ collapsed_sections: Vec<Section>,
+ collapsed_channels: Vec<ChannelId>,
// drag_target_channel: ChannelDragTarget,
- _workspace: WeakView<Workspace>,
+ workspace: WeakView<Workspace>,
// context_menu_on_selected: bool,
}
@@ -335,58 +344,58 @@ struct SerializedCollabPanel {
// Dismissed,
// }
-// #[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)]
-// enum Section {
-// ActiveCall,
-// Channels,
-// ChannelInvites,
-// ContactRequests,
-// Contacts,
-// Online,
-// Offline,
-// }
+#[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)]
+enum Section {
+ ActiveCall,
+ Channels,
+ ChannelInvites,
+ ContactRequests,
+ Contacts,
+ Online,
+ Offline,
+}
-// #[derive(Clone, Debug)]
-// enum ListEntry {
-// Header(Section),
-// CallParticipant {
-// user: Arc<User>,
-// peer_id: Option<PeerId>,
-// is_pending: bool,
-// },
-// ParticipantProject {
-// project_id: u64,
-// worktree_root_names: Vec<String>,
-// host_user_id: u64,
-// is_last: bool,
-// },
-// ParticipantScreen {
-// peer_id: Option<PeerId>,
-// is_last: bool,
-// },
-// IncomingRequest(Arc<User>),
-// OutgoingRequest(Arc<User>),
-// ChannelInvite(Arc<Channel>),
-// Channel {
-// channel: Arc<Channel>,
-// depth: usize,
-// has_children: bool,
-// },
-// ChannelNotes {
-// channel_id: ChannelId,
-// },
-// ChannelChat {
-// channel_id: ChannelId,
-// },
-// ChannelEditor {
-// depth: usize,
-// },
-// Contact {
-// contact: Arc<Contact>,
-// calling: bool,
-// },
-// ContactPlaceholder,
-// }
+#[derive(Clone, Debug)]
+enum ListEntry {
+ Header(Section),
+ // CallParticipant {
+ // user: Arc<User>,
+ // peer_id: Option<PeerId>,
+ // is_pending: bool,
+ // },
+ // ParticipantProject {
+ // project_id: u64,
+ // worktree_root_names: Vec<String>,
+ // host_user_id: u64,
+ // is_last: bool,
+ // },
+ // ParticipantScreen {
+ // peer_id: Option<PeerId>,
+ // is_last: bool,
+ // },
+ IncomingRequest(Arc<User>),
+ OutgoingRequest(Arc<User>),
+ // ChannelInvite(Arc<Channel>),
+ Channel {
+ channel: Arc<Channel>,
+ depth: usize,
+ has_children: bool,
+ },
+ // ChannelNotes {
+ // channel_id: ChannelId,
+ // },
+ // ChannelChat {
+ // channel_id: ChannelId,
+ // },
+ ChannelEditor {
+ depth: usize,
+ },
+ Contact {
+ contact: Arc<Contact>,
+ calling: bool,
+ },
+ ContactPlaceholder,
+}
// impl Entity for CollabPanel {
// type Event = Event;
@@ -397,16 +406,11 @@ impl CollabPanel {
cx.build_view(|cx| {
// let view_id = cx.view_id();
- // let filter_editor = cx.add_view(|cx| {
- // let mut editor = Editor::single_line(
- // Some(Arc::new(|theme| {
- // theme.collab_panel.user_query_editor.clone()
- // })),
- // cx,
- // );
- // editor.set_placeholder_text("Filter channels, contacts", cx);
- // editor
- // });
+ let filter_editor = cx.build_view(|cx| {
+ let mut editor = Editor::single_line(cx);
+ editor.set_placeholder_text("Filter channels, contacts", cx);
+ editor
+ });
// cx.subscribe(&filter_editor, |this, _, event, cx| {
// if let editor::Event::BufferEdited = event {
@@ -585,7 +589,7 @@ impl CollabPanel {
// }
// });
- let this = Self {
+ let mut this = Self {
width: None,
focus_handle: cx.focus_handle(),
// channel_clipboard: None,
@@ -593,25 +597,25 @@ impl CollabPanel {
// pending_serialization: Task::ready(None),
// context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
// channel_name_editor,
- // filter_editor,
- // entries: Vec::default(),
- // channel_editing_state: None,
+ filter_editor,
+ entries: Vec::default(),
+ channel_editing_state: None,
// selection: None,
+ channel_store: ChannelStore::global(cx),
user_store: workspace.user_store().clone(),
- // channel_store: ChannelStore::global(cx),
// project: workspace.project().clone(),
- // subscriptions: Vec::default(),
- // match_candidates: Vec::default(),
- // collapsed_sections: vec![Section::Offline],
- // collapsed_channels: Vec::default(),
- _workspace: workspace.weak_handle(),
- _client: workspace.app_state().client.clone(),
+ subscriptions: Vec::default(),
+ match_candidates: Vec::default(),
+ collapsed_sections: vec![Section::Offline],
+ collapsed_channels: Vec::default(),
+ workspace: workspace.weak_handle(),
+ client: workspace.app_state().client.clone(),
// context_menu_on_selected: true,
// drag_target_channel: ChannelDragTarget::None,
// list_state,
};
- // this.update_entries(false, cx);
+ this.update_entries(false, cx);
// // Update the dock position when the setting changes.
// let mut old_dock_position = this.position(cx);
@@ -628,10 +632,10 @@ impl CollabPanel {
// );
// let active_call = ActiveCall::global(cx);
- // this.subscriptions
- // .push(cx.observe(&this.user_store, |this, _, cx| {
- // this.update_entries(true, cx)
- // }));
+ this.subscriptions
+ .push(cx.observe(&this.user_store, |this, _, cx| {
+ this.update_entries(true, cx)
+ }));
// this.subscriptions
// .push(cx.observe(&this.channel_store, |this, _, cx| {
// this.update_entries(true, cx)
@@ -720,449 +724,449 @@ impl CollabPanel {
// );
// }
- // fn update_entries(&mut self, select_same_item: bool, cx: &mut ViewContext<Self>) {
- // let channel_store = self.channel_store.read(cx);
- // let user_store = self.user_store.read(cx);
- // let query = self.filter_editor.read(cx).text(cx);
- // let executor = cx.background().clone();
-
- // let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned());
- // let old_entries = mem::take(&mut self.entries);
- // let mut scroll_to_top = false;
-
- // if let Some(room) = ActiveCall::global(cx).read(cx).room() {
- // self.entries.push(ListEntry::Header(Section::ActiveCall));
- // if !old_entries
- // .iter()
- // .any(|entry| matches!(entry, ListEntry::Header(Section::ActiveCall)))
- // {
- // scroll_to_top = true;
- // }
-
- // if !self.collapsed_sections.contains(&Section::ActiveCall) {
- // let room = room.read(cx);
-
- // if let Some(channel_id) = room.channel_id() {
- // self.entries.push(ListEntry::ChannelNotes { channel_id });
- // self.entries.push(ListEntry::ChannelChat { channel_id })
- // }
-
- // // Populate the active user.
- // if let Some(user) = user_store.current_user() {
- // self.match_candidates.clear();
- // self.match_candidates.push(StringMatchCandidate {
- // id: 0,
- // string: user.github_login.clone(),
- // char_bag: user.github_login.chars().collect(),
- // });
- // let matches = executor.block(match_strings(
- // &self.match_candidates,
- // &query,
- // true,
- // usize::MAX,
- // &Default::default(),
- // executor.clone(),
- // ));
- // if !matches.is_empty() {
- // let user_id = user.id;
- // self.entries.push(ListEntry::CallParticipant {
- // user,
- // peer_id: None,
- // is_pending: false,
- // });
- // let mut projects = room.local_participant().projects.iter().peekable();
- // while let Some(project) = projects.next() {
- // self.entries.push(ListEntry::ParticipantProject {
- // project_id: project.id,
- // worktree_root_names: project.worktree_root_names.clone(),
- // host_user_id: user_id,
- // is_last: projects.peek().is_none() && !room.is_screen_sharing(),
- // });
- // }
- // if room.is_screen_sharing() {
- // self.entries.push(ListEntry::ParticipantScreen {
- // peer_id: None,
- // is_last: true,
- // });
- // }
- // }
- // }
-
- // // Populate remote participants.
- // self.match_candidates.clear();
- // self.match_candidates
- // .extend(room.remote_participants().iter().map(|(_, participant)| {
- // StringMatchCandidate {
- // id: participant.user.id as usize,
- // string: participant.user.github_login.clone(),
- // char_bag: participant.user.github_login.chars().collect(),
- // }
- // }));
- // let matches = executor.block(match_strings(
- // &self.match_candidates,
- // &query,
- // true,
- // usize::MAX,
- // &Default::default(),
- // executor.clone(),
- // ));
- // for mat in matches {
- // let user_id = mat.candidate_id as u64;
- // let participant = &room.remote_participants()[&user_id];
- // self.entries.push(ListEntry::CallParticipant {
- // user: participant.user.clone(),
- // peer_id: Some(participant.peer_id),
- // is_pending: false,
- // });
- // let mut projects = participant.projects.iter().peekable();
- // while let Some(project) = projects.next() {
- // self.entries.push(ListEntry::ParticipantProject {
- // project_id: project.id,
- // worktree_root_names: project.worktree_root_names.clone(),
- // host_user_id: participant.user.id,
- // is_last: projects.peek().is_none()
- // && participant.video_tracks.is_empty(),
- // });
- // }
- // if !participant.video_tracks.is_empty() {
- // self.entries.push(ListEntry::ParticipantScreen {
- // peer_id: Some(participant.peer_id),
- // is_last: true,
- // });
- // }
- // }
-
- // // Populate pending participants.
- // self.match_candidates.clear();
- // self.match_candidates
- // .extend(room.pending_participants().iter().enumerate().map(
- // |(id, participant)| StringMatchCandidate {
- // id,
- // string: participant.github_login.clone(),
- // char_bag: participant.github_login.chars().collect(),
- // },
- // ));
- // let matches = executor.block(match_strings(
- // &self.match_candidates,
- // &query,
- // true,
- // usize::MAX,
- // &Default::default(),
- // executor.clone(),
- // ));
- // self.entries
- // .extend(matches.iter().map(|mat| ListEntry::CallParticipant {
- // user: room.pending_participants()[mat.candidate_id].clone(),
- // peer_id: None,
- // is_pending: true,
- // }));
- // }
- // }
-
- // let mut request_entries = Vec::new();
-
- // if cx.has_flag::<ChannelsAlpha>() {
- // self.entries.push(ListEntry::Header(Section::Channels));
-
- // if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() {
- // self.match_candidates.clear();
- // self.match_candidates
- // .extend(channel_store.ordered_channels().enumerate().map(
- // |(ix, (_, channel))| StringMatchCandidate {
- // id: ix,
- // string: channel.name.clone(),
- // char_bag: channel.name.chars().collect(),
- // },
- // ));
- // let matches = executor.block(match_strings(
- // &self.match_candidates,
- // &query,
- // true,
- // usize::MAX,
- // &Default::default(),
- // executor.clone(),
- // ));
- // if let Some(state) = &self.channel_editing_state {
- // if matches!(state, ChannelEditingState::Create { location: None, .. }) {
- // self.entries.push(ListEntry::ChannelEditor { depth: 0 });
- // }
- // }
- // let mut collapse_depth = None;
- // for mat in matches {
- // let channel = channel_store.channel_at_index(mat.candidate_id).unwrap();
- // let depth = channel.parent_path.len();
-
- // if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) {
- // collapse_depth = Some(depth);
- // } else if let Some(collapsed_depth) = collapse_depth {
- // if depth > collapsed_depth {
- // continue;
- // }
- // if self.is_channel_collapsed(channel.id) {
- // collapse_depth = Some(depth);
- // } else {
- // collapse_depth = None;
- // }
- // }
-
- // let has_children = channel_store
- // .channel_at_index(mat.candidate_id + 1)
- // .map_or(false, |next_channel| {
- // next_channel.parent_path.ends_with(&[channel.id])
- // });
-
- // match &self.channel_editing_state {
- // Some(ChannelEditingState::Create {
- // location: parent_id,
- // ..
- // }) if *parent_id == Some(channel.id) => {
- // self.entries.push(ListEntry::Channel {
- // channel: channel.clone(),
- // depth,
- // has_children: false,
- // });
- // self.entries
- // .push(ListEntry::ChannelEditor { depth: depth + 1 });
- // }
- // Some(ChannelEditingState::Rename {
- // location: parent_id,
- // ..
- // }) if parent_id == &channel.id => {
- // self.entries.push(ListEntry::ChannelEditor { depth });
- // }
- // _ => {
- // self.entries.push(ListEntry::Channel {
- // channel: channel.clone(),
- // depth,
- // has_children,
- // });
- // }
- // }
- // }
- // }
-
- // let channel_invites = channel_store.channel_invitations();
- // if !channel_invites.is_empty() {
- // self.match_candidates.clear();
- // self.match_candidates
- // .extend(channel_invites.iter().enumerate().map(|(ix, channel)| {
- // StringMatchCandidate {
- // id: ix,
- // string: channel.name.clone(),
- // char_bag: channel.name.chars().collect(),
- // }
- // }));
- // let matches = executor.block(match_strings(
- // &self.match_candidates,
- // &query,
- // true,
- // usize::MAX,
- // &Default::default(),
- // executor.clone(),
- // ));
- // request_entries.extend(matches.iter().map(|mat| {
- // ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone())
- // }));
-
- // if !request_entries.is_empty() {
- // self.entries
- // .push(ListEntry::Header(Section::ChannelInvites));
- // if !self.collapsed_sections.contains(&Section::ChannelInvites) {
- // self.entries.append(&mut request_entries);
- // }
- // }
- // }
- // }
-
- // self.entries.push(ListEntry::Header(Section::Contacts));
-
- // request_entries.clear();
- // let incoming = user_store.incoming_contact_requests();
- // if !incoming.is_empty() {
- // self.match_candidates.clear();
- // self.match_candidates
- // .extend(
- // incoming
- // .iter()
- // .enumerate()
- // .map(|(ix, user)| StringMatchCandidate {
- // id: ix,
- // string: user.github_login.clone(),
- // char_bag: user.github_login.chars().collect(),
- // }),
- // );
- // let matches = executor.block(match_strings(
- // &self.match_candidates,
- // &query,
- // true,
- // usize::MAX,
- // &Default::default(),
- // executor.clone(),
- // ));
- // request_entries.extend(
- // matches
- // .iter()
- // .map(|mat| ListEntry::IncomingRequest(incoming[mat.candidate_id].clone())),
- // );
- // }
-
- // let outgoing = user_store.outgoing_contact_requests();
- // if !outgoing.is_empty() {
- // self.match_candidates.clear();
- // self.match_candidates
- // .extend(
- // outgoing
- // .iter()
- // .enumerate()
- // .map(|(ix, user)| StringMatchCandidate {
- // id: ix,
- // string: user.github_login.clone(),
- // char_bag: user.github_login.chars().collect(),
- // }),
- // );
- // let matches = executor.block(match_strings(
- // &self.match_candidates,
- // &query,
- // true,
- // usize::MAX,
- // &Default::default(),
- // executor.clone(),
- // ));
- // request_entries.extend(
- // matches
- // .iter()
- // .map(|mat| ListEntry::OutgoingRequest(outgoing[mat.candidate_id].clone())),
- // );
- // }
-
- // if !request_entries.is_empty() {
- // self.entries
- // .push(ListEntry::Header(Section::ContactRequests));
- // if !self.collapsed_sections.contains(&Section::ContactRequests) {
- // self.entries.append(&mut request_entries);
- // }
- // }
-
- // let contacts = user_store.contacts();
- // if !contacts.is_empty() {
- // self.match_candidates.clear();
- // self.match_candidates
- // .extend(
- // contacts
- // .iter()
- // .enumerate()
- // .map(|(ix, contact)| StringMatchCandidate {
- // id: ix,
- // string: contact.user.github_login.clone(),
- // char_bag: contact.user.github_login.chars().collect(),
- // }),
- // );
-
- // let matches = executor.block(match_strings(
- // &self.match_candidates,
- // &query,
- // true,
- // usize::MAX,
- // &Default::default(),
- // executor.clone(),
- // ));
-
- // let (online_contacts, offline_contacts) = matches
- // .iter()
- // .partition::<Vec<_>, _>(|mat| contacts[mat.candidate_id].online);
-
- // for (matches, section) in [
- // (online_contacts, Section::Online),
- // (offline_contacts, Section::Offline),
- // ] {
- // if !matches.is_empty() {
- // self.entries.push(ListEntry::Header(section));
- // if !self.collapsed_sections.contains(§ion) {
- // let active_call = &ActiveCall::global(cx).read(cx);
- // for mat in matches {
- // let contact = &contacts[mat.candidate_id];
- // self.entries.push(ListEntry::Contact {
- // contact: contact.clone(),
- // calling: active_call.pending_invites().contains(&contact.user.id),
- // });
- // }
- // }
- // }
- // }
- // }
-
- // if incoming.is_empty() && outgoing.is_empty() && contacts.is_empty() {
- // self.entries.push(ListEntry::ContactPlaceholder);
- // }
-
- // if select_same_item {
- // if let Some(prev_selected_entry) = prev_selected_entry {
- // self.selection.take();
- // for (ix, entry) in self.entries.iter().enumerate() {
- // if *entry == prev_selected_entry {
- // self.selection = Some(ix);
- // break;
- // }
- // }
- // }
- // } else {
- // self.selection = self.selection.and_then(|prev_selection| {
- // if self.entries.is_empty() {
- // None
- // } else {
- // Some(prev_selection.min(self.entries.len() - 1))
- // }
- // });
- // }
-
- // let old_scroll_top = self.list_state.logical_scroll_top();
-
- // self.list_state.reset(self.entries.len());
+ fn update_entries(&mut self, select_same_item: bool, cx: &mut ViewContext<Self>) {
+ let channel_store = self.channel_store.read(cx);
+ let user_store = self.user_store.read(cx);
+ let query = self.filter_editor.read(cx).text(cx);
+ let executor = cx.background_executor().clone();
+
+ // let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned());
+ let _old_entries = mem::take(&mut self.entries);
+ // let mut scroll_to_top = false;
+
+ // if let Some(room) = ActiveCall::global(cx).read(cx).room() {
+ // self.entries.push(ListEntry::Header(Section::ActiveCall));
+ // if !old_entries
+ // .iter()
+ // .any(|entry| matches!(entry, ListEntry::Header(Section::ActiveCall)))
+ // {
+ // scroll_to_top = true;
+ // }
+
+ // if !self.collapsed_sections.contains(&Section::ActiveCall) {
+ // let room = room.read(cx);
+
+ // if let Some(channel_id) = room.channel_id() {
+ // self.entries.push(ListEntry::ChannelNotes { channel_id });
+ // self.entries.push(ListEntry::ChannelChat { channel_id })
+ // }
+
+ // // Populate the active user.
+ // if let Some(user) = user_store.current_user() {
+ // self.match_candidates.clear();
+ // self.match_candidates.push(StringMatchCandidate {
+ // id: 0,
+ // string: user.github_login.clone(),
+ // char_bag: user.github_login.chars().collect(),
+ // });
+ // let matches = executor.block(match_strings(
+ // &self.match_candidates,
+ // &query,
+ // true,
+ // usize::MAX,
+ // &Default::default(),
+ // executor.clone(),
+ // ));
+ // if !matches.is_empty() {
+ // let user_id = user.id;
+ // self.entries.push(ListEntry::CallParticipant {
+ // user,
+ // peer_id: None,
+ // is_pending: false,
+ // });
+ // let mut projects = room.local_participant().projects.iter().peekable();
+ // while let Some(project) = projects.next() {
+ // self.entries.push(ListEntry::ParticipantProject {
+ // project_id: project.id,
+ // worktree_root_names: project.worktree_root_names.clone(),
+ // host_user_id: user_id,
+ // is_last: projects.peek().is_none() && !room.is_screen_sharing(),
+ // });
+ // }
+ // if room.is_screen_sharing() {
+ // self.entries.push(ListEntry::ParticipantScreen {
+ // peer_id: None,
+ // is_last: true,
+ // });
+ // }
+ // }
+ // }
+
+ // // Populate remote participants.
+ // self.match_candidates.clear();
+ // self.match_candidates
+ // .extend(room.remote_participants().iter().map(|(_, participant)| {
+ // StringMatchCandidate {
+ // id: participant.user.id as usize,
+ // string: participant.user.github_login.clone(),
+ // char_bag: participant.user.github_login.chars().collect(),
+ // }
+ // }));
+ // let matches = executor.block(match_strings(
+ // &self.match_candidates,
+ // &query,
+ // true,
+ // usize::MAX,
+ // &Default::default(),
+ // executor.clone(),
+ // ));
+ // for mat in matches {
+ // let user_id = mat.candidate_id as u64;
+ // let participant = &room.remote_participants()[&user_id];
+ // self.entries.push(ListEntry::CallParticipant {
+ // user: participant.user.clone(),
+ // peer_id: Some(participant.peer_id),
+ // is_pending: false,
+ // });
+ // let mut projects = participant.projects.iter().peekable();
+ // while let Some(project) = projects.next() {
+ // self.entries.push(ListEntry::ParticipantProject {
+ // project_id: project.id,
+ // worktree_root_names: project.worktree_root_names.clone(),
+ // host_user_id: participant.user.id,
+ // is_last: projects.peek().is_none()
+ // && participant.video_tracks.is_empty(),
+ // });
+ // }
+ // if !participant.video_tracks.is_empty() {
+ // self.entries.push(ListEntry::ParticipantScreen {
+ // peer_id: Some(participant.peer_id),
+ // is_last: true,
+ // });
+ // }
+ // }
+
+ // // Populate pending participants.
+ // self.match_candidates.clear();
+ // self.match_candidates
+ // .extend(room.pending_participants().iter().enumerate().map(
+ // |(id, participant)| StringMatchCandidate {
+ // id,
+ // string: participant.github_login.clone(),
+ // char_bag: participant.github_login.chars().collect(),
+ // },
+ // ));
+ // let matches = executor.block(match_strings(
+ // &self.match_candidates,
+ // &query,
+ // true,
+ // usize::MAX,
+ // &Default::default(),
+ // executor.clone(),
+ // ));
+ // self.entries
+ // .extend(matches.iter().map(|mat| ListEntry::CallParticipant {
+ // user: room.pending_participants()[mat.candidate_id].clone(),
+ // peer_id: None,
+ // is_pending: true,
+ // }));
+ // }
+ // }
+
+ let mut request_entries = Vec::new();
+
+ if cx.has_flag::<ChannelsAlpha>() {
+ self.entries.push(ListEntry::Header(Section::Channels));
+
+ if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() {
+ self.match_candidates.clear();
+ self.match_candidates
+ .extend(channel_store.ordered_channels().enumerate().map(
+ |(ix, (_, channel))| StringMatchCandidate {
+ id: ix,
+ string: channel.name.clone(),
+ char_bag: channel.name.chars().collect(),
+ },
+ ));
+ let matches = executor.block(match_strings(
+ &self.match_candidates,
+ &query,
+ true,
+ usize::MAX,
+ &Default::default(),
+ executor.clone(),
+ ));
+ if let Some(state) = &self.channel_editing_state {
+ if matches!(state, ChannelEditingState::Create { location: None, .. }) {
+ self.entries.push(ListEntry::ChannelEditor { depth: 0 });
+ }
+ }
+ let mut collapse_depth = None;
+ for mat in matches {
+ let channel = channel_store.channel_at_index(mat.candidate_id).unwrap();
+ let depth = channel.parent_path.len();
+
+ if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) {
+ collapse_depth = Some(depth);
+ } else if let Some(collapsed_depth) = collapse_depth {
+ if depth > collapsed_depth {
+ continue;
+ }
+ if self.is_channel_collapsed(channel.id) {
+ collapse_depth = Some(depth);
+ } else {
+ collapse_depth = None;
+ }
+ }
+
+ let has_children = channel_store
+ .channel_at_index(mat.candidate_id + 1)
+ .map_or(false, |next_channel| {
+ next_channel.parent_path.ends_with(&[channel.id])
+ });
+
+ match &self.channel_editing_state {
+ Some(ChannelEditingState::Create {
+ location: parent_id,
+ ..
+ }) if *parent_id == Some(channel.id) => {
+ self.entries.push(ListEntry::Channel {
+ channel: channel.clone(),
+ depth,
+ has_children: false,
+ });
+ self.entries
+ .push(ListEntry::ChannelEditor { depth: depth + 1 });
+ }
+ Some(ChannelEditingState::Rename {
+ location: parent_id,
+ ..
+ }) if parent_id == &channel.id => {
+ self.entries.push(ListEntry::ChannelEditor { depth });
+ }
+ _ => {
+ self.entries.push(ListEntry::Channel {
+ channel: channel.clone(),
+ depth,
+ has_children,
+ });
+ }
+ }
+ }
+ }
- // if scroll_to_top {
- // self.list_state.scroll_to(ListOffset::default());
- // } else {
- // // Attempt to maintain the same scroll position.
- // if let Some(old_top_entry) = old_entries.get(old_scroll_top.item_ix) {
- // let new_scroll_top = self
- // .entries
- // .iter()
- // .position(|entry| entry == old_top_entry)
- // .map(|item_ix| ListOffset {
- // item_ix,
- // offset_in_item: old_scroll_top.offset_in_item,
- // })
- // .or_else(|| {
- // let entry_after_old_top = old_entries.get(old_scroll_top.item_ix + 1)?;
- // let item_ix = self
- // .entries
- // .iter()
- // .position(|entry| entry == entry_after_old_top)?;
- // Some(ListOffset {
- // item_ix,
- // offset_in_item: 0.,
- // })
- // })
- // .or_else(|| {
- // let entry_before_old_top =
- // old_entries.get(old_scroll_top.item_ix.saturating_sub(1))?;
- // let item_ix = self
- // .entries
- // .iter()
- // .position(|entry| entry == entry_before_old_top)?;
- // Some(ListOffset {
- // item_ix,
- // offset_in_item: 0.,
- // })
- // });
+ // let channel_invites = channel_store.channel_invitations();
+ // if !channel_invites.is_empty() {
+ // self.match_candidates.clear();
+ // self.match_candidates
+ // .extend(channel_invites.iter().enumerate().map(|(ix, channel)| {
+ // StringMatchCandidate {
+ // id: ix,
+ // string: channel.name.clone(),
+ // char_bag: channel.name.chars().collect(),
+ // }
+ // }));
+ // let matches = executor.block(match_strings(
+ // &self.match_candidates,
+ // &query,
+ // true,
+ // usize::MAX,
+ // &Default::default(),
+ // executor.clone(),
+ // ));
+ // request_entries.extend(matches.iter().map(|mat| {
+ // ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone())
+ // }));
- // self.list_state
- // .scroll_to(new_scroll_top.unwrap_or(old_scroll_top));
- // }
- // }
+ // if !request_entries.is_empty() {
+ // self.entries
+ // .push(ListEntry::Header(Section::ChannelInvites));
+ // if !self.collapsed_sections.contains(&Section::ChannelInvites) {
+ // self.entries.append(&mut request_entries);
+ // }
+ // }
+ // }
+ }
+
+ self.entries.push(ListEntry::Header(Section::Contacts));
+
+ request_entries.clear();
+ let incoming = user_store.incoming_contact_requests();
+ if !incoming.is_empty() {
+ self.match_candidates.clear();
+ self.match_candidates
+ .extend(
+ incoming
+ .iter()
+ .enumerate()
+ .map(|(ix, user)| StringMatchCandidate {
+ id: ix,
+ string: user.github_login.clone(),
+ char_bag: user.github_login.chars().collect(),
+ }),
+ );
+ let matches = executor.block(match_strings(
+ &self.match_candidates,
+ &query,
+ true,
+ usize::MAX,
+ &Default::default(),
+ executor.clone(),
+ ));
+ request_entries.extend(
+ matches
+ .iter()
+ .map(|mat| ListEntry::IncomingRequest(incoming[mat.candidate_id].clone())),
+ );
+ }
+
+ let outgoing = user_store.outgoing_contact_requests();
+ if !outgoing.is_empty() {
+ self.match_candidates.clear();
+ self.match_candidates
+ .extend(
+ outgoing
+ .iter()
+ .enumerate()
+ .map(|(ix, user)| StringMatchCandidate {
+ id: ix,
+ string: user.github_login.clone(),
+ char_bag: user.github_login.chars().collect(),
+ }),
+ );
+ let matches = executor.block(match_strings(
+ &self.match_candidates,
+ &query,
+ true,
+ usize::MAX,
+ &Default::default(),
+ executor.clone(),
+ ));
+ request_entries.extend(
+ matches
+ .iter()
+ .map(|mat| ListEntry::OutgoingRequest(outgoing[mat.candidate_id].clone())),
+ );
+ }
+
+ if !request_entries.is_empty() {
+ self.entries
+ .push(ListEntry::Header(Section::ContactRequests));
+ if !self.collapsed_sections.contains(&Section::ContactRequests) {
+ self.entries.append(&mut request_entries);
+ }
+ }
+
+ let contacts = user_store.contacts();
+ if !contacts.is_empty() {
+ self.match_candidates.clear();
+ self.match_candidates
+ .extend(
+ contacts
+ .iter()
+ .enumerate()
+ .map(|(ix, contact)| StringMatchCandidate {
+ id: ix,
+ string: contact.user.github_login.clone(),
+ char_bag: contact.user.github_login.chars().collect(),
+ }),
+ );
+
+ let matches = executor.block(match_strings(
+ &self.match_candidates,
+ &query,
+ true,
+ usize::MAX,
+ &Default::default(),
+ executor.clone(),
+ ));
+
+ let (online_contacts, offline_contacts) = matches
+ .iter()
+ .partition::<Vec<_>, _>(|mat| contacts[mat.candidate_id].online);
+
+ for (matches, section) in [
+ (online_contacts, Section::Online),
+ (offline_contacts, Section::Offline),
+ ] {
+ if !matches.is_empty() {
+ self.entries.push(ListEntry::Header(section));
+ if !self.collapsed_sections.contains(§ion) {
+ let active_call = &ActiveCall::global(cx).read(cx);
+ for mat in matches {
+ let contact = &contacts[mat.candidate_id];
+ self.entries.push(ListEntry::Contact {
+ contact: contact.clone(),
+ calling: active_call.pending_invites().contains(&contact.user.id),
+ });
+ }
+ }
+ }
+ }
+ }
+
+ if incoming.is_empty() && outgoing.is_empty() && contacts.is_empty() {
+ self.entries.push(ListEntry::ContactPlaceholder);
+ }
+
+ // if select_same_item {
+ // if let Some(prev_selected_entry) = prev_selected_entry {
+ // self.selection.take();
+ // for (ix, entry) in self.entries.iter().enumerate() {
+ // if *entry == prev_selected_entry {
+ // self.selection = Some(ix);
+ // break;
+ // }
+ // }
+ // }
+ // } else {
+ // self.selection = self.selection.and_then(|prev_selection| {
+ // if self.entries.is_empty() {
+ // None
+ // } else {
+ // Some(prev_selection.min(self.entries.len() - 1))
+ // }
+ // });
+ // }
+
+ // let old_scroll_top = self.list_state.logical_scroll_top();
+
+ // self.list_state.reset(self.entries.len());
+
+ // if scroll_to_top {
+ // self.list_state.scroll_to(ListOffset::default());
+ // } else {
+ // // Attempt to maintain the same scroll position.
+ // if let Some(old_top_entry) = old_entries.get(old_scroll_top.item_ix) {
+ // let new_scroll_top = self
+ // .entries
+ // .iter()
+ // .position(|entry| entry == old_top_entry)
+ // .map(|item_ix| ListOffset {
+ // item_ix,
+ // offset_in_item: old_scroll_top.offset_in_item,
+ // })
+ // .or_else(|| {
+ // let entry_after_old_top = old_entries.get(old_scroll_top.item_ix + 1)?;
+ // let item_ix = self
+ // .entries
+ // .iter()
+ // .position(|entry| entry == entry_after_old_top)?;
+ // Some(ListOffset {
+ // item_ix,
+ // offset_in_item: 0.,
+ // })
+ // })
+ // .or_else(|| {
+ // let entry_before_old_top =
+ // old_entries.get(old_scroll_top.item_ix.saturating_sub(1))?;
+ // let item_ix = self
+ // .entries
+ // .iter()
+ // .position(|entry| entry == entry_before_old_top)?;
+ // Some(ListOffset {
+ // item_ix,
+ // offset_in_item: 0.,
+ // })
+ // });
+
+ // self.list_state
+ // .scroll_to(new_scroll_top.unwrap_or(old_scroll_top));
+ // }
+ // }
- // cx.notify();
- // }
+ cx.notify();
+ }
// fn render_call_participant(
// user: &User,
@@ -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),
- )
- .with_child(
- ChildView::new(&self.picker, cx)
- .contained()
- .with_style(theme.body),
+ 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),
)
- .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();
}))
}
})
@@ -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
@@ -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)
@@ -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),
}
@@ -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" }
@@ -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);