@@ -118,3123 +118,3142 @@
// to: ChannelId,
// }
-// actions!(
-// collab_panel,
-// [
-// ToggleFocus,
-// Remove,
-// Secondary,
-// CollapseSelectedChannel,
-// ExpandSelectedChannel,
-// StartMoveChannel,
-// MoveSelected,
-// InsertSpace,
-// ]
-// );
-
-// impl_actions!(
-// collab_panel,
-// [
-// RemoveChannel,
-// NewChannel,
-// InviteMembers,
-// ManageMembers,
-// RenameChannel,
-// ToggleCollapse,
-// OpenChannelNotes,
-// JoinChannelCall,
-// JoinChannelChat,
-// CopyChannelLink,
-// StartMoveChannelFor,
-// MoveChannel,
-// ToggleSelectedIx
-// ]
-// );
-
-// #[derive(Debug, Copy, Clone, PartialEq, Eq)]
-// struct ChannelMoveClipboard {
-// channel_id: ChannelId,
-// }
-
-// const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
-
-// pub fn init(cx: &mut AppContext) {
-// settings::register::<panel_settings::CollaborationPanelSettings>(cx);
-// contact_finder::init(cx);
-// channel_modal::init(cx);
-// channel_view::init(cx);
-
-// cx.add_action(CollabPanel::cancel);
-// cx.add_action(CollabPanel::select_next);
-// cx.add_action(CollabPanel::select_prev);
-// cx.add_action(CollabPanel::confirm);
-// cx.add_action(CollabPanel::insert_space);
-// cx.add_action(CollabPanel::remove);
-// cx.add_action(CollabPanel::remove_selected_channel);
-// cx.add_action(CollabPanel::show_inline_context_menu);
-// cx.add_action(CollabPanel::new_subchannel);
-// cx.add_action(CollabPanel::invite_members);
-// cx.add_action(CollabPanel::manage_members);
-// cx.add_action(CollabPanel::rename_selected_channel);
-// cx.add_action(CollabPanel::rename_channel);
-// cx.add_action(CollabPanel::toggle_channel_collapsed_action);
-// cx.add_action(CollabPanel::collapse_selected_channel);
-// cx.add_action(CollabPanel::expand_selected_channel);
-// cx.add_action(CollabPanel::open_channel_notes);
-// cx.add_action(CollabPanel::join_channel_chat);
-// cx.add_action(CollabPanel::copy_channel_link);
-
-// cx.add_action(
-// |panel: &mut CollabPanel, action: &ToggleSelectedIx, cx: &mut ViewContext<CollabPanel>| {
-// if panel.selection.take() != Some(action.ix) {
-// panel.selection = Some(action.ix)
-// }
-
-// cx.notify();
-// },
-// );
-
-// cx.add_action(
-// |panel: &mut CollabPanel,
-// action: &StartMoveChannelFor,
-// _: &mut ViewContext<CollabPanel>| {
-// panel.channel_clipboard = Some(ChannelMoveClipboard {
-// channel_id: action.channel_id,
-// });
-// },
-// );
-
-// cx.add_action(
-// |panel: &mut CollabPanel, _: &StartMoveChannel, _: &mut ViewContext<CollabPanel>| {
-// if let Some(channel) = panel.selected_channel() {
-// panel.channel_clipboard = Some(ChannelMoveClipboard {
-// channel_id: channel.id,
-// })
-// }
-// },
-// );
-
-// cx.add_action(
-// |panel: &mut CollabPanel, _: &MoveSelected, cx: &mut ViewContext<CollabPanel>| {
-// let Some(clipboard) = panel.channel_clipboard.take() else {
-// return;
-// };
-// let Some(selected_channel) = panel.selected_channel() else {
-// return;
-// };
-
-// panel
-// .channel_store
-// .update(cx, |channel_store, cx| {
-// channel_store.move_channel(clipboard.channel_id, Some(selected_channel.id), cx)
-// })
-// .detach_and_log_err(cx)
-// },
-// );
-
-// cx.add_action(
-// |panel: &mut CollabPanel, action: &MoveChannel, cx: &mut ViewContext<CollabPanel>| {
-// if let Some(clipboard) = panel.channel_clipboard.take() {
-// panel.channel_store.update(cx, |channel_store, cx| {
-// channel_store
-// .move_channel(clipboard.channel_id, Some(action.to), cx)
-// .detach_and_log_err(cx)
-// })
-// }
-// },
-// );
-// }
-
-// #[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(),
-// }
-// }
-// }
-
-// pub struct CollabPanel {
-// width: Option<f32>,
-// fs: Arc<dyn Fs>,
-// has_focus: bool,
-// channel_clipboard: Option<ChannelMoveClipboard>,
-// pending_serialization: Task<Option<()>>,
-// context_menu: ViewHandle<ContextMenu>,
-// filter_editor: ViewHandle<Editor>,
-// channel_name_editor: ViewHandle<Editor>,
-// channel_editing_state: Option<ChannelEditingState>,
-// entries: Vec<ListEntry>,
-// selection: Option<usize>,
-// user_store: ModelHandle<UserStore>,
-// client: Arc<Client>,
-// channel_store: ModelHandle<ChannelStore>,
-// project: ModelHandle<Project>,
-// match_candidates: Vec<StringMatchCandidate>,
-// list_state: ListState<Self>,
-// subscriptions: Vec<Subscription>,
-// collapsed_sections: Vec<Section>,
-// collapsed_channels: Vec<ChannelId>,
-// drag_target_channel: ChannelDragTarget,
-// workspace: WeakViewHandle<Workspace>,
-// context_menu_on_selected: bool,
-// }
-
-// #[derive(PartialEq, Eq)]
-// enum ChannelDragTarget {
-// None,
-// Root,
-// Channel(ChannelId),
-// }
-
-// #[derive(Serialize, Deserialize)]
-// struct SerializedCollabPanel {
-// width: Option<f32>,
-// collapsed_channels: Option<Vec<ChannelId>>,
-// }
-
-// #[derive(Debug)]
-// pub enum Event {
-// DockPositionChanged,
-// Focus,
-// Dismissed,
-// }
-
-// #[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,
-// }
-
-// impl Entity for CollabPanel {
-// type Event = Event;
-// }
-
-// impl CollabPanel {
-// pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> ViewHandle<Self> {
-// cx.add_view::<Self, _>(|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
-// });
-
-// cx.subscribe(&filter_editor, |this, _, event, cx| {
-// if let editor::Event::BufferEdited = event {
-// let query = this.filter_editor.read(cx).text(cx);
-// if !query.is_empty() {
-// this.selection.take();
-// }
-// this.update_entries(true, cx);
-// if !query.is_empty() {
-// this.selection = this
-// .entries
-// .iter()
-// .position(|entry| !matches!(entry, ListEntry::Header(_)));
-// }
-// } else if let editor::Event::Blurred = event {
-// let query = this.filter_editor.read(cx).text(cx);
-// if query.is_empty() {
-// this.selection.take();
-// this.update_entries(true, cx);
-// }
-// }
-// })
-// .detach();
-
-// let channel_name_editor = cx.add_view(|cx| {
-// Editor::single_line(
-// Some(Arc::new(|theme| {
-// theme.collab_panel.user_query_editor.clone()
-// })),
-// cx,
-// )
-// });
-
-// cx.subscribe(&channel_name_editor, |this, _, event, cx| {
-// if let editor::Event::Blurred = event {
-// if let Some(state) = &this.channel_editing_state {
-// if state.pending_name().is_some() {
-// return;
-// }
-// }
-// this.take_editing_state(cx);
-// this.update_entries(false, cx);
-// cx.notify();
-// }
-// })
-// .detach();
-
-// let list_state =
-// ListState::<Self>::new(0, Orientation::Top, 1000., move |this, ix, cx| {
-// let theme = theme::current(cx).clone();
-// let is_selected = this.selection == Some(ix);
-// let current_project_id = this.project.read(cx).remote_id();
-
-// match &this.entries[ix] {
-// ListEntry::Header(section) => {
-// let is_collapsed = this.collapsed_sections.contains(section);
-// this.render_header(*section, &theme, is_selected, is_collapsed, cx)
-// }
-// ListEntry::CallParticipant {
-// user,
-// peer_id,
-// is_pending,
-// } => Self::render_call_participant(
-// user,
-// *peer_id,
-// this.user_store.clone(),
-// *is_pending,
-// is_selected,
-// &theme,
-// cx,
-// ),
-// ListEntry::ParticipantProject {
-// project_id,
-// worktree_root_names,
-// host_user_id,
-// is_last,
-// } => Self::render_participant_project(
-// *project_id,
-// worktree_root_names,
-// *host_user_id,
-// Some(*project_id) == current_project_id,
-// *is_last,
-// is_selected,
-// &theme,
-// cx,
-// ),
-// ListEntry::ParticipantScreen { peer_id, is_last } => {
-// Self::render_participant_screen(
-// *peer_id,
-// *is_last,
-// is_selected,
-// &theme.collab_panel,
-// cx,
-// )
-// }
-// ListEntry::Channel {
-// channel,
-// depth,
-// has_children,
-// } => {
-// let channel_row = this.render_channel(
-// &*channel,
-// *depth,
-// &theme,
-// is_selected,
-// *has_children,
-// ix,
-// cx,
-// );
-
-// if is_selected && this.context_menu_on_selected {
-// Stack::new()
-// .with_child(channel_row)
-// .with_child(
-// ChildView::new(&this.context_menu, cx)
-// .aligned()
-// .bottom()
-// .right(),
-// )
-// .into_any()
-// } else {
-// return channel_row;
-// }
-// }
-// ListEntry::ChannelNotes { channel_id } => this.render_channel_notes(
-// *channel_id,
-// &theme.collab_panel,
-// is_selected,
-// ix,
-// cx,
-// ),
-// ListEntry::ChannelChat { channel_id } => this.render_channel_chat(
-// *channel_id,
-// &theme.collab_panel,
-// is_selected,
-// ix,
-// cx,
-// ),
-// ListEntry::ChannelInvite(channel) => Self::render_channel_invite(
-// channel.clone(),
-// this.channel_store.clone(),
-// &theme.collab_panel,
-// is_selected,
-// cx,
-// ),
-// ListEntry::IncomingRequest(user) => Self::render_contact_request(
-// user.clone(),
-// this.user_store.clone(),
-// &theme.collab_panel,
-// true,
-// is_selected,
-// cx,
-// ),
-// ListEntry::OutgoingRequest(user) => Self::render_contact_request(
-// user.clone(),
-// this.user_store.clone(),
-// &theme.collab_panel,
-// false,
-// is_selected,
-// cx,
-// ),
-// ListEntry::Contact { contact, calling } => Self::render_contact(
-// contact,
-// *calling,
-// &this.project,
-// &theme,
-// is_selected,
-// cx,
-// ),
-// ListEntry::ChannelEditor { depth } => {
-// this.render_channel_editor(&theme, *depth, cx)
-// }
-// ListEntry::ContactPlaceholder => {
-// this.render_contact_placeholder(&theme.collab_panel, is_selected, cx)
-// }
-// }
-// });
-
-// let mut this = Self {
-// width: None,
-// has_focus: false,
-// channel_clipboard: None,
-// fs: workspace.app_state().fs.clone(),
-// 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,
-// selection: None,
-// 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(),
-// context_menu_on_selected: true,
-// drag_target_channel: ChannelDragTarget::None,
-// list_state,
-// };
-
-// this.update_entries(false, cx);
-
-// // Update the dock position when the setting changes.
-// let mut old_dock_position = this.position(cx);
-// this.subscriptions
-// .push(
-// cx.observe_global::<SettingsStore, _>(move |this: &mut Self, cx| {
-// let new_dock_position = this.position(cx);
-// if new_dock_position != old_dock_position {
-// old_dock_position = new_dock_position;
-// cx.emit(Event::DockPositionChanged);
-// }
-// cx.notify();
-// }),
-// );
-
-// 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.channel_store, |this, _, cx| {
-// this.update_entries(true, cx)
-// }));
-// this.subscriptions
-// .push(cx.observe(&active_call, |this, _, cx| this.update_entries(true, cx)));
-// this.subscriptions
-// .push(cx.observe_flag::<ChannelsAlpha, _>(move |_, this, cx| {
-// this.update_entries(true, cx)
-// }));
-// this.subscriptions.push(cx.subscribe(
-// &this.channel_store,
-// |this, _channel_store, e, cx| match e {
-// ChannelEvent::ChannelCreated(channel_id)
-// | ChannelEvent::ChannelRenamed(channel_id) => {
-// if this.take_editing_state(cx) {
-// this.update_entries(false, cx);
-// this.selection = this.entries.iter().position(|entry| {
-// if let ListEntry::Channel { channel, .. } = entry {
-// channel.id == *channel_id
-// } else {
-// false
-// }
-// });
-// }
-// }
-// },
-// ));
-
-// this
-// })
-// }
-
-// pub fn load(
-// workspace: WeakViewHandle<Workspace>,
-// cx: AsyncAppContext,
-// ) -> Task<Result<ViewHandle<Self>>> {
-// cx.spawn(|mut cx| async move {
-// let serialized_panel = if let Some(panel) = cx
-// .background()
-// .spawn(async move { KEY_VALUE_STORE.read_kvp(COLLABORATION_PANEL_KEY) })
-// .await
-// .log_err()
-// .flatten()
-// {
-// match serde_json::from_str::<SerializedCollabPanel>(&panel) {
-// Ok(panel) => Some(panel),
-// Err(err) => {
-// log::error!("Failed to deserialize collaboration panel: {}", err);
-// None
-// }
-// }
-// } else {
-// None
-// };
-
-// workspace.update(&mut cx, |workspace, cx| {
-// let panel = CollabPanel::new(workspace, cx);
-// if let Some(serialized_panel) = serialized_panel {
-// panel.update(cx, |panel, cx| {
-// panel.width = serialized_panel.width;
-// panel.collapsed_channels = serialized_panel
-// .collapsed_channels
-// .unwrap_or_else(|| Vec::new());
-// cx.notify();
-// });
-// }
-// panel
-// })
-// })
-// }
-
-// fn serialize(&mut self, cx: &mut ViewContext<Self>) {
-// let width = self.width;
-// let collapsed_channels = self.collapsed_channels.clone();
-// self.pending_serialization = cx.background().spawn(
-// async move {
-// KEY_VALUE_STORE
-// .write_kvp(
-// COLLABORATION_PANEL_KEY.into(),
-// serde_json::to_string(&SerializedCollabPanel {
-// width,
-// collapsed_channels: Some(collapsed_channels),
-// })?,
-// )
-// .await?;
-// anyhow::Ok(())
-// }
-// .log_err(),
-// );
-// }
-
-// 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());
-
-// 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();
-// }
-
-// fn render_call_participant(
-// user: &User,
-// peer_id: Option<PeerId>,
-// user_store: ModelHandle<UserStore>,
-// is_pending: bool,
-// is_selected: bool,
-// theme: &theme::Theme,
-// cx: &mut ViewContext<Self>,
-// ) -> AnyElement<Self> {
-// enum CallParticipant {}
-// enum CallParticipantTooltip {}
-// enum LeaveCallButton {}
-// enum LeaveCallTooltip {}
-
-// let collab_theme = &theme.collab_panel;
-
-// let is_current_user =
-// user_store.read(cx).current_user().map(|user| user.id) == Some(user.id);
-
-// let content = MouseEventHandler::new::<CallParticipant, _>(
-// user.id as usize,
-// cx,
-// |mouse_state, cx| {
-// let style = if is_current_user {
-// *collab_theme
-// .contact_row
-// .in_state(is_selected)
-// .style_for(&mut Default::default())
-// } else {
-// *collab_theme
-// .contact_row
-// .in_state(is_selected)
-// .style_for(mouse_state)
-// };
-
-// Flex::row()
-// .with_children(user.avatar.clone().map(|avatar| {
-// Image::from_data(avatar)
-// .with_style(collab_theme.contact_avatar)
-// .aligned()
-// .left()
-// }))
-// .with_child(
-// Label::new(
-// user.github_login.clone(),
-// collab_theme.contact_username.text.clone(),
-// )
-// .contained()
-// .with_style(collab_theme.contact_username.container)
-// .aligned()
-// .left()
-// .flex(1., true),
-// )
-// .with_children(if is_pending {
-// Some(
-// Label::new("Calling", collab_theme.calling_indicator.text.clone())
-// .contained()
-// .with_style(collab_theme.calling_indicator.container)
-// .aligned()
-// .into_any(),
-// )
-// } else if is_current_user {
-// Some(
-// MouseEventHandler::new::<LeaveCallButton, _>(0, cx, |state, _| {
-// render_icon_button(
-// theme
-// .collab_panel
-// .leave_call_button
-// .style_for(is_selected, state),
-// "icons/exit.svg",
-// )
-// })
-// .with_cursor_style(CursorStyle::PointingHand)
-// .on_click(MouseButton::Left, |_, _, cx| {
-// Self::leave_call(cx);
-// })
-// .with_tooltip::<LeaveCallTooltip>(
-// 0,
-// "Leave call",
-// None,
-// theme.tooltip.clone(),
-// cx,
-// )
-// .into_any(),
-// )
-// } else {
-// None
-// })
-// .constrained()
-// .with_height(collab_theme.row_height)
-// .contained()
-// .with_style(style)
-// },
-// );
-
-// if is_current_user || is_pending || peer_id.is_none() {
-// return content.into_any();
-// }
-
-// let tooltip = format!("Follow {}", user.github_login);
-
-// content
-// .on_click(MouseButton::Left, move |_, this, cx| {
-// if let Some(workspace) = this.workspace.upgrade(cx) {
-// workspace
-// .update(cx, |workspace, cx| workspace.follow(peer_id.unwrap(), cx))
-// .map(|task| task.detach_and_log_err(cx));
-// }
-// })
-// .with_cursor_style(CursorStyle::PointingHand)
-// .with_tooltip::<CallParticipantTooltip>(
-// user.id as usize,
-// tooltip,
-// Some(Box::new(FollowNextCollaborator)),
-// theme.tooltip.clone(),
-// cx,
-// )
-// .into_any()
-// }
-
-// fn render_participant_project(
-// project_id: u64,
-// worktree_root_names: &[String],
-// host_user_id: u64,
-// is_current: bool,
-// is_last: bool,
-// is_selected: bool,
-// theme: &theme::Theme,
-// cx: &mut ViewContext<Self>,
-// ) -> AnyElement<Self> {
-// enum JoinProject {}
-// enum JoinProjectTooltip {}
-
-// let collab_theme = &theme.collab_panel;
-// let host_avatar_width = collab_theme
-// .contact_avatar
-// .width
-// .or(collab_theme.contact_avatar.height)
-// .unwrap_or(0.);
-// let tree_branch = collab_theme.tree_branch;
-// let project_name = if worktree_root_names.is_empty() {
-// "untitled".to_string()
-// } else {
-// worktree_root_names.join(", ")
-// };
-
-// let content =
-// MouseEventHandler::new::<JoinProject, _>(project_id as usize, cx, |mouse_state, cx| {
-// let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
-// let row = if is_current {
-// collab_theme
-// .project_row
-// .in_state(true)
-// .style_for(&mut Default::default())
-// } else {
-// collab_theme
-// .project_row
-// .in_state(is_selected)
-// .style_for(mouse_state)
-// };
-
-// Flex::row()
-// .with_child(render_tree_branch(
-// tree_branch,
-// &row.name.text,
-// is_last,
-// vec2f(host_avatar_width, collab_theme.row_height),
-// cx.font_cache(),
-// ))
-// .with_child(
-// Svg::new("icons/file_icons/folder.svg")
-// .with_color(collab_theme.channel_hash.color)
-// .constrained()
-// .with_width(collab_theme.channel_hash.width)
-// .aligned()
-// .left(),
-// )
-// .with_child(
-// Label::new(project_name.clone(), row.name.text.clone())
-// .aligned()
-// .left()
-// .contained()
-// .with_style(row.name.container)
-// .flex(1., false),
-// )
-// .constrained()
-// .with_height(collab_theme.row_height)
-// .contained()
-// .with_style(row.container)
-// });
-
-// if is_current {
-// return content.into_any();
-// }
-
-// content
-// .with_cursor_style(CursorStyle::PointingHand)
-// .on_click(MouseButton::Left, move |_, this, cx| {
-// if let Some(workspace) = this.workspace.upgrade(cx) {
-// let app_state = workspace.read(cx).app_state().clone();
-// workspace::join_remote_project(project_id, host_user_id, app_state, cx)
-// .detach_and_log_err(cx);
-// }
-// })
-// .with_tooltip::<JoinProjectTooltip>(
-// project_id as usize,
-// format!("Open {}", project_name),
-// None,
-// theme.tooltip.clone(),
-// cx,
-// )
-// .into_any()
-// }
-
-// fn render_participant_screen(
-// peer_id: Option<PeerId>,
-// is_last: bool,
-// is_selected: bool,
-// theme: &theme::CollabPanel,
-// cx: &mut ViewContext<Self>,
-// ) -> AnyElement<Self> {
-// enum OpenSharedScreen {}
-
-// let host_avatar_width = theme
-// .contact_avatar
-// .width
-// .or(theme.contact_avatar.height)
-// .unwrap_or(0.);
-// let tree_branch = theme.tree_branch;
-
-// let handler = MouseEventHandler::new::<OpenSharedScreen, _>(
-// peer_id.map(|id| id.as_u64()).unwrap_or(0) as usize,
-// cx,
-// |mouse_state, cx| {
-// let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
-// let row = theme
-// .project_row
-// .in_state(is_selected)
-// .style_for(mouse_state);
-
-// Flex::row()
-// .with_child(render_tree_branch(
-// tree_branch,
-// &row.name.text,
-// is_last,
-// vec2f(host_avatar_width, theme.row_height),
-// cx.font_cache(),
-// ))
-// .with_child(
-// Svg::new("icons/desktop.svg")
-// .with_color(theme.channel_hash.color)
-// .constrained()
-// .with_width(theme.channel_hash.width)
-// .aligned()
-// .left(),
-// )
-// .with_child(
-// Label::new("Screen", row.name.text.clone())
-// .aligned()
-// .left()
-// .contained()
-// .with_style(row.name.container)
-// .flex(1., false),
-// )
-// .constrained()
-// .with_height(theme.row_height)
-// .contained()
-// .with_style(row.container)
-// },
-// );
-// if peer_id.is_none() {
-// return handler.into_any();
-// }
-// handler
-// .with_cursor_style(CursorStyle::PointingHand)
-// .on_click(MouseButton::Left, move |_, this, cx| {
-// if let Some(workspace) = this.workspace.upgrade(cx) {
-// workspace.update(cx, |workspace, cx| {
-// workspace.open_shared_screen(peer_id.unwrap(), cx)
-// });
-// }
-// })
-// .into_any()
-// }
-
-// fn take_editing_state(&mut self, cx: &mut ViewContext<Self>) -> bool {
-// if let Some(_) = self.channel_editing_state.take() {
-// self.channel_name_editor.update(cx, |editor, cx| {
-// editor.set_text("", cx);
-// });
-// true
-// } else {
-// false
-// }
-// }
-
-// fn render_header(
-// &self,
-// section: Section,
-// theme: &theme::Theme,
-// is_selected: bool,
-// is_collapsed: bool,
-// cx: &mut ViewContext<Self>,
-// ) -> AnyElement<Self> {
-// enum Header {}
-// enum LeaveCallContactList {}
-// enum AddChannel {}
-
-// let tooltip_style = &theme.tooltip;
-// let mut channel_link = None;
-// let mut channel_tooltip_text = None;
-// let mut channel_icon = None;
-// let mut is_dragged_over = false;
-
-// let text = match section {
-// Section::ActiveCall => {
-// let channel_name = maybe!({
-// let channel_id = ActiveCall::global(cx).read(cx).channel_id(cx)?;
-
-// let channel = self.channel_store.read(cx).channel_for_id(channel_id)?;
-
-// channel_link = Some(channel.link());
-// (channel_icon, channel_tooltip_text) = match channel.visibility {
-// proto::ChannelVisibility::Public => {
-// (Some("icons/public.svg"), Some("Copy public channel link."))
-// }
-// proto::ChannelVisibility::Members => {
-// (Some("icons/hash.svg"), Some("Copy private channel link."))
-// }
-// };
-
-// Some(channel.name.as_str())
-// });
-
-// if let Some(name) = channel_name {
-// Cow::Owned(format!("{}", name))
-// } else {
-// Cow::Borrowed("Current Call")
-// }
-// }
-// Section::ContactRequests => Cow::Borrowed("Requests"),
-// Section::Contacts => Cow::Borrowed("Contacts"),
-// Section::Channels => Cow::Borrowed("Channels"),
-// Section::ChannelInvites => Cow::Borrowed("Invites"),
-// Section::Online => Cow::Borrowed("Online"),
-// Section::Offline => Cow::Borrowed("Offline"),
-// };
-
-// enum AddContact {}
-// let button = match section {
-// Section::ActiveCall => channel_link.map(|channel_link| {
-// let channel_link_copy = channel_link.clone();
-// MouseEventHandler::new::<AddContact, _>(0, cx, |state, _| {
-// render_icon_button(
-// theme
-// .collab_panel
-// .leave_call_button
-// .style_for(is_selected, state),
-// "icons/link.svg",
-// )
-// })
-// .with_cursor_style(CursorStyle::PointingHand)
-// .on_click(MouseButton::Left, move |_, _, cx| {
-// let item = ClipboardItem::new(channel_link_copy.clone());
-// cx.write_to_clipboard(item)
-// })
-// .with_tooltip::<AddContact>(
-// 0,
-// channel_tooltip_text.unwrap(),
-// None,
-// tooltip_style.clone(),
-// cx,
-// )
-// }),
-// Section::Contacts => Some(
-// MouseEventHandler::new::<LeaveCallContactList, _>(0, cx, |state, _| {
-// render_icon_button(
-// theme
-// .collab_panel
-// .add_contact_button
-// .style_for(is_selected, state),
-// "icons/plus.svg",
-// )
-// })
-// .with_cursor_style(CursorStyle::PointingHand)
-// .on_click(MouseButton::Left, |_, this, cx| {
-// this.toggle_contact_finder(cx);
-// })
-// .with_tooltip::<LeaveCallContactList>(
-// 0,
-// "Search for new contact",
-// None,
-// tooltip_style.clone(),
-// cx,
-// ),
-// ),
-// Section::Channels => {
-// if cx
-// .global::<DragAndDrop<Workspace>>()
-// .currently_dragged::<Channel>(cx.window())
-// .is_some()
-// && self.drag_target_channel == ChannelDragTarget::Root
-// {
-// is_dragged_over = true;
-// }
-
-// Some(
-// MouseEventHandler::new::<AddChannel, _>(0, cx, |state, _| {
-// render_icon_button(
-// theme
-// .collab_panel
-// .add_contact_button
-// .style_for(is_selected, state),
-// "icons/plus.svg",
-// )
-// })
-// .with_cursor_style(CursorStyle::PointingHand)
-// .on_click(MouseButton::Left, |_, this, cx| this.new_root_channel(cx))
-// .with_tooltip::<AddChannel>(
-// 0,
-// "Create a channel",
-// None,
-// tooltip_style.clone(),
-// cx,
-// ),
-// )
-// }
-// _ => None,
-// };
-
-// let can_collapse = match section {
-// Section::ActiveCall | Section::Channels | Section::Contacts => false,
-// Section::ChannelInvites
-// | Section::ContactRequests
-// | Section::Online
-// | Section::Offline => true,
-// };
-// let icon_size = (&theme.collab_panel).section_icon_size;
-// let mut result = MouseEventHandler::new::<Header, _>(section as usize, cx, |state, _| {
-// let header_style = if can_collapse {
-// theme
-// .collab_panel
-// .subheader_row
-// .in_state(is_selected)
-// .style_for(state)
-// } else {
-// &theme.collab_panel.header_row
-// };
-
-// Flex::row()
-// .with_children(if can_collapse {
-// Some(
-// Svg::new(if is_collapsed {
-// "icons/chevron_right.svg"
-// } else {
-// "icons/chevron_down.svg"
-// })
-// .with_color(header_style.text.color)
-// .constrained()
-// .with_max_width(icon_size)
-// .with_max_height(icon_size)
-// .aligned()
-// .constrained()
-// .with_width(icon_size)
-// .contained()
-// .with_margin_right(
-// theme.collab_panel.contact_username.container.margin.left,
-// ),
-// )
-// } else if let Some(channel_icon) = channel_icon {
-// Some(
-// Svg::new(channel_icon)
-// .with_color(header_style.text.color)
-// .constrained()
-// .with_max_width(icon_size)
-// .with_max_height(icon_size)
-// .aligned()
-// .constrained()
-// .with_width(icon_size)
-// .contained()
-// .with_margin_right(
-// theme.collab_panel.contact_username.container.margin.left,
-// ),
-// )
-// } else {
-// None
-// })
-// .with_child(
-// Label::new(text, header_style.text.clone())
-// .aligned()
-// .left()
-// .flex(1., true),
-// )
-// .with_children(button.map(|button| button.aligned().right()))
-// .constrained()
-// .with_height(theme.collab_panel.row_height)
-// .contained()
-// .with_style(if is_dragged_over {
-// theme.collab_panel.dragged_over_header
-// } else {
-// header_style.container
-// })
-// });
-
-// result = result
-// .on_move(move |_, this, cx| {
-// if cx
-// .global::<DragAndDrop<Workspace>>()
-// .currently_dragged::<Channel>(cx.window())
-// .is_some()
-// {
-// this.drag_target_channel = ChannelDragTarget::Root;
-// cx.notify()
-// }
-// })
-// .on_up(MouseButton::Left, move |_, this, cx| {
-// if let Some((_, dragged_channel)) = cx
-// .global::<DragAndDrop<Workspace>>()
-// .currently_dragged::<Channel>(cx.window())
-// {
-// this.channel_store
-// .update(cx, |channel_store, cx| {
-// channel_store.move_channel(dragged_channel.id, None, cx)
-// })
-// .detach_and_log_err(cx)
-// }
-// });
-
-// if can_collapse {
-// result = result
-// .with_cursor_style(CursorStyle::PointingHand)
-// .on_click(MouseButton::Left, move |_, this, cx| {
-// if can_collapse {
-// this.toggle_section_expanded(section, cx);
-// }
-// })
-// }
-
-// result.into_any()
-// }
-
-// fn render_contact(
-// contact: &Contact,
-// calling: bool,
-// project: &ModelHandle<Project>,
-// theme: &theme::Theme,
-// is_selected: bool,
-// cx: &mut ViewContext<Self>,
-// ) -> AnyElement<Self> {
-// enum ContactTooltip {}
-
-// let collab_theme = &theme.collab_panel;
-// let online = contact.online;
-// let busy = contact.busy || calling;
-// let user_id = contact.user.id;
-// let github_login = contact.user.github_login.clone();
-// let initial_project = project.clone();
-
-// 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_child(
-// Label::new(
-// contact.user.github_login.clone(),
-// collab_theme.contact_username.text.clone(),
-// )
-// .contained()
-// .with_style(collab_theme.contact_username.container)
-// .aligned()
-// .left()
-// .flex(1., true),
-// )
-// .with_children(if state.hovered() {
-// Some(
-// MouseEventHandler::new::<Cancel, _>(
-// contact.user.id as usize,
-// cx,
-// |mouse_state, _| {
-// let button_style =
-// collab_theme.contact_button.style_for(mouse_state);
-// render_icon_button(button_style, "icons/x.svg")
-// .aligned()
-// .flex_float()
-// },
-// )
-// .with_padding(Padding::uniform(2.))
-// .with_cursor_style(CursorStyle::PointingHand)
-// .on_click(MouseButton::Left, move |_, this, cx| {
-// this.remove_contact(user_id, &github_login, cx);
-// })
-// .flex_float(),
-// )
-// } else {
-// None
-// })
-// .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_placeholder(
-// &self,
-// theme: &theme::CollabPanel,
-// is_selected: bool,
-// cx: &mut ViewContext<Self>,
-// ) -> AnyElement<Self> {
-// enum AddContacts {}
-// MouseEventHandler::new::<AddContacts, _>(0, cx, |state, _| {
-// let style = theme.list_empty_state.style_for(is_selected, state);
-// Flex::row()
-// .with_child(
-// Svg::new("icons/plus.svg")
-// .with_color(theme.list_empty_icon.color)
-// .constrained()
-// .with_width(theme.list_empty_icon.width)
-// .aligned()
-// .left(),
-// )
-// .with_child(
-// Label::new("Add a contact", style.text.clone())
-// .contained()
-// .with_style(theme.list_empty_label_container),
-// )
-// .align_children_center()
-// .contained()
-// .with_style(style.container)
-// .into_any()
-// })
-// .on_click(MouseButton::Left, |_, this, cx| {
-// this.toggle_contact_finder(cx);
-// })
-// .into_any()
-// }
-
-// fn render_channel_editor(
-// &self,
-// theme: &theme::Theme,
-// depth: usize,
-// cx: &AppContext,
-// ) -> AnyElement<Self> {
-// Flex::row()
-// .with_child(
-// Empty::new()
-// .constrained()
-// .with_width(theme.collab_panel.disclosure.button_space()),
-// )
-// .with_child(
-// Svg::new("icons/hash.svg")
-// .with_color(theme.collab_panel.channel_hash.color)
-// .constrained()
-// .with_width(theme.collab_panel.channel_hash.width)
-// .aligned()
-// .left(),
-// )
-// .with_child(
-// if let Some(pending_name) = self
-// .channel_editing_state
-// .as_ref()
-// .and_then(|state| state.pending_name())
-// {
-// Label::new(
-// pending_name.to_string(),
-// theme.collab_panel.contact_username.text.clone(),
-// )
-// .contained()
-// .with_style(theme.collab_panel.contact_username.container)
-// .aligned()
-// .left()
-// .flex(1., true)
-// .into_any()
-// } else {
-// ChildView::new(&self.channel_name_editor, cx)
-// .aligned()
-// .left()
-// .contained()
-// .with_style(theme.collab_panel.channel_editor)
-// .flex(1.0, true)
-// .into_any()
-// },
-// )
-// .align_children_center()
-// .constrained()
-// .with_height(theme.collab_panel.row_height)
-// .contained()
-// .with_style(ContainerStyle {
-// background_color: Some(theme.editor.background),
-// ..*theme.collab_panel.contact_row.default_style()
-// })
-// .with_padding_left(
-// theme.collab_panel.contact_row.default_style().padding.left
-// + theme.collab_panel.channel_indent * depth as f32,
-// )
-// .into_any()
-// }
-
-// fn render_channel(
-// &self,
-// channel: &Channel,
-// depth: usize,
-// theme: &theme::Theme,
-// is_selected: bool,
-// has_children: bool,
-// ix: usize,
-// cx: &mut ViewContext<Self>,
-// ) -> AnyElement<Self> {
-// let channel_id = channel.id;
-// let collab_theme = &theme.collab_panel;
-// let is_public = self
-// .channel_store
-// .read(cx)
-// .channel_for_id(channel_id)
-// .map(|channel| channel.visibility)
-// == Some(proto::ChannelVisibility::Public);
-// let other_selected = self.selected_channel().map(|channel| channel.id) == Some(channel.id);
-// let disclosed =
-// has_children.then(|| !self.collapsed_channels.binary_search(&channel.id).is_ok());
-
-// let is_active = maybe!({
-// let call_channel = ActiveCall::global(cx)
-// .read(cx)
-// .room()?
-// .read(cx)
-// .channel_id()?;
-// Some(call_channel == channel_id)
-// })
-// .unwrap_or(false);
-
-// const FACEPILE_LIMIT: usize = 3;
-
-// enum ChannelCall {}
-// enum ChannelNote {}
-// enum NotesTooltip {}
-// enum ChatTooltip {}
-// enum ChannelTooltip {}
-
-// let mut is_dragged_over = false;
-// if cx
-// .global::<DragAndDrop<Workspace>>()
-// .currently_dragged::<Channel>(cx.window())
-// .is_some()
-// && self.drag_target_channel == ChannelDragTarget::Channel(channel_id)
-// {
-// is_dragged_over = true;
-// }
-
-// let has_messages_notification = channel.unseen_message_id.is_some();
-
-// MouseEventHandler::new::<Channel, _>(ix, cx, |state, cx| {
-// let row_hovered = state.hovered();
-
-// let mut select_state = |interactive: &Interactive<ContainerStyle>| {
-// if state.clicked() == Some(MouseButton::Left) && interactive.clicked.is_some() {
-// interactive.clicked.as_ref().unwrap().clone()
-// } else if state.hovered() || other_selected {
-// interactive
-// .hovered
-// .as_ref()
-// .unwrap_or(&interactive.default)
-// .clone()
-// } else {
-// interactive.default.clone()
-// }
-// };
-
-// Flex::<Self>::row()
-// .with_child(
-// Svg::new(if is_public {
-// "icons/public.svg"
-// } else {
-// "icons/hash.svg"
-// })
-// .with_color(collab_theme.channel_hash.color)
-// .constrained()
-// .with_width(collab_theme.channel_hash.width)
-// .aligned()
-// .left(),
-// )
-// .with_child({
-// let style = collab_theme.channel_name.inactive_state();
-// Flex::row()
-// .with_child(
-// Label::new(channel.name.clone(), style.text.clone())
-// .contained()
-// .with_style(style.container)
-// .aligned()
-// .left()
-// .with_tooltip::<ChannelTooltip>(
-// ix,
-// "Join channel",
-// None,
-// theme.tooltip.clone(),
-// cx,
-// ),
-// )
-// .with_children({
-// let participants =
-// self.channel_store.read(cx).channel_participants(channel_id);
-
-// if !participants.is_empty() {
-// let extra_count = participants.len().saturating_sub(FACEPILE_LIMIT);
-
-// let result = FacePile::new(collab_theme.face_overlap)
-// .with_children(
-// participants
-// .iter()
-// .filter_map(|user| {
-// Some(
-// Image::from_data(user.avatar.clone()?)
-// .with_style(collab_theme.channel_avatar),
-// )
-// })
-// .take(FACEPILE_LIMIT),
-// )
-// .with_children((extra_count > 0).then(|| {
-// Label::new(
-// format!("+{}", extra_count),
-// collab_theme.extra_participant_label.text.clone(),
-// )
-// .contained()
-// .with_style(collab_theme.extra_participant_label.container)
-// }));
-
-// Some(result)
-// } else {
-// None
-// }
-// })
-// .with_spacing(8.)
-// .align_children_center()
-// .flex(1., true)
-// })
-// .with_child(
-// MouseEventHandler::new::<ChannelNote, _>(ix, cx, move |mouse_state, _| {
-// let container_style = collab_theme
-// .disclosure
-// .button
-// .style_for(mouse_state)
-// .container;
-
-// if channel.unseen_message_id.is_some() {
-// Svg::new("icons/conversations.svg")
-// .with_color(collab_theme.channel_note_active_color)
-// .constrained()
-// .with_width(collab_theme.channel_hash.width)
-// .contained()
-// .with_style(container_style)
-// .with_uniform_padding(4.)
-// .into_any()
-// } else if row_hovered {
-// Svg::new("icons/conversations.svg")
-// .with_color(collab_theme.channel_hash.color)
-// .constrained()
-// .with_width(collab_theme.channel_hash.width)
-// .contained()
-// .with_style(container_style)
-// .with_uniform_padding(4.)
-// .into_any()
-// } else {
-// Empty::new().into_any()
-// }
-// })
-// .on_click(MouseButton::Left, move |_, this, cx| {
-// this.join_channel_chat(&JoinChannelChat { channel_id }, cx);
-// })
-// .with_tooltip::<ChatTooltip>(
-// ix,
-// "Open channel chat",
-// None,
-// theme.tooltip.clone(),
-// cx,
-// )
-// .contained()
-// .with_margin_right(4.),
-// )
-// .with_child(
-// MouseEventHandler::new::<ChannelCall, _>(ix, cx, move |mouse_state, cx| {
-// let container_style = collab_theme
-// .disclosure
-// .button
-// .style_for(mouse_state)
-// .container;
-// if row_hovered || channel.unseen_note_version.is_some() {
-// Svg::new("icons/file.svg")
-// .with_color(if channel.unseen_note_version.is_some() {
-// collab_theme.channel_note_active_color
-// } else {
-// collab_theme.channel_hash.color
-// })
-// .constrained()
-// .with_width(collab_theme.channel_hash.width)
-// .contained()
-// .with_style(container_style)
-// .with_uniform_padding(4.)
-// .with_margin_right(collab_theme.channel_hash.container.margin.left)
-// .with_tooltip::<NotesTooltip>(
-// ix as usize,
-// "Open channel notes",
-// None,
-// theme.tooltip.clone(),
-// cx,
-// )
-// .into_any()
-// } else if has_messages_notification {
-// Empty::new()
-// .constrained()
-// .with_width(collab_theme.channel_hash.width)
-// .contained()
-// .with_uniform_padding(4.)
-// .with_margin_right(collab_theme.channel_hash.container.margin.left)
-// .into_any()
-// } else {
-// Empty::new().into_any()
-// }
-// })
-// .on_click(MouseButton::Left, move |_, this, cx| {
-// this.open_channel_notes(&OpenChannelNotes { channel_id }, cx);
-// }),
-// )
-// .align_children_center()
-// .styleable_component()
-// .disclosable(
-// disclosed,
-// Box::new(ToggleCollapse {
-// location: channel.id.clone(),
-// }),
-// )
-// .with_id(ix)
-// .with_style(collab_theme.disclosure.clone())
-// .element()
-// .constrained()
-// .with_height(collab_theme.row_height)
-// .contained()
-// .with_style(select_state(
-// collab_theme
-// .channel_row
-// .in_state(is_selected || is_active || is_dragged_over),
-// ))
-// .with_padding_left(
-// collab_theme.channel_row.default_style().padding.left
-// + collab_theme.channel_indent * depth as f32,
-// )
-// })
-// .on_click(MouseButton::Left, move |_, this, cx| {
-// if this.drag_target_channel == ChannelDragTarget::None {
-// if is_active {
-// this.open_channel_notes(&OpenChannelNotes { channel_id }, cx)
-// } else {
-// this.join_channel(channel_id, cx)
-// }
-// }
-// })
-// .on_click(MouseButton::Right, {
-// let channel = channel.clone();
-// move |e, this, cx| {
-// this.deploy_channel_context_menu(Some(e.position), &channel, ix, cx);
-// }
-// })
-// .on_up(MouseButton::Left, move |_, this, cx| {
-// if let Some((_, dragged_channel)) = cx
-// .global::<DragAndDrop<Workspace>>()
-// .currently_dragged::<Channel>(cx.window())
-// {
-// this.channel_store
-// .update(cx, |channel_store, cx| {
-// channel_store.move_channel(dragged_channel.id, Some(channel_id), cx)
-// })
-// .detach_and_log_err(cx)
-// }
-// })
-// .on_move({
-// let channel = channel.clone();
-// move |_, this, cx| {
-// if let Some((_, dragged_channel)) = cx
-// .global::<DragAndDrop<Workspace>>()
-// .currently_dragged::<Channel>(cx.window())
-// {
-// if channel.id != dragged_channel.id {
-// this.drag_target_channel = ChannelDragTarget::Channel(channel.id);
-// }
-// cx.notify()
-// }
-// }
-// })
-// .as_draggable::<_, Channel>(
-// channel.clone(),
-// move |_, channel, cx: &mut ViewContext<Workspace>| {
-// let theme = &theme::current(cx).collab_panel;
-
-// Flex::<Workspace>::row()
-// .with_child(
-// Svg::new("icons/hash.svg")
-// .with_color(theme.channel_hash.color)
-// .constrained()
-// .with_width(theme.channel_hash.width)
-// .aligned()
-// .left(),
-// )
-// .with_child(
-// Label::new(channel.name.clone(), theme.channel_name.text.clone())
-// .contained()
-// .with_style(theme.channel_name.container)
-// .aligned()
-// .left(),
-// )
-// .align_children_center()
-// .contained()
-// .with_background_color(
-// theme
-// .container
-// .background_color
-// .unwrap_or(gpui::color::Color::transparent_black()),
-// )
-// .contained()
-// .with_padding_left(
-// theme.channel_row.default_style().padding.left
-// + theme.channel_indent * depth as f32,
-// )
-// .into_any()
-// },
-// )
-// .with_cursor_style(CursorStyle::PointingHand)
-// .into_any()
-// }
-
-// fn render_channel_notes(
-// &self,
-// channel_id: ChannelId,
-// theme: &theme::CollabPanel,
-// is_selected: bool,
-// ix: usize,
-// cx: &mut ViewContext<Self>,
-// ) -> AnyElement<Self> {
-// enum ChannelNotes {}
-// let host_avatar_width = theme
-// .contact_avatar
-// .width
-// .or(theme.contact_avatar.height)
-// .unwrap_or(0.);
-
-// MouseEventHandler::new::<ChannelNotes, _>(ix as usize, cx, |state, cx| {
-// let tree_branch = *theme.tree_branch.in_state(is_selected).style_for(state);
-// let row = theme.project_row.in_state(is_selected).style_for(state);
-
-// Flex::<Self>::row()
-// .with_child(render_tree_branch(
-// tree_branch,
-// &row.name.text,
-// false,
-// vec2f(host_avatar_width, theme.row_height),
-// cx.font_cache(),
-// ))
-// .with_child(
-// Svg::new("icons/file.svg")
-// .with_color(theme.channel_hash.color)
-// .constrained()
-// .with_width(theme.channel_hash.width)
-// .aligned()
-// .left(),
-// )
-// .with_child(
-// Label::new("notes", theme.channel_name.text.clone())
-// .contained()
-// .with_style(theme.channel_name.container)
-// .aligned()
-// .left()
-// .flex(1., true),
-// )
-// .constrained()
-// .with_height(theme.row_height)
-// .contained()
-// .with_style(*theme.channel_row.style_for(is_selected, state))
-// .with_padding_left(theme.channel_row.default_style().padding.left)
-// })
-// .on_click(MouseButton::Left, move |_, this, cx| {
-// this.open_channel_notes(&OpenChannelNotes { channel_id }, cx);
-// })
-// .with_cursor_style(CursorStyle::PointingHand)
-// .into_any()
-// }
-
-// fn render_channel_chat(
-// &self,
-// channel_id: ChannelId,
-// theme: &theme::CollabPanel,
-// is_selected: bool,
-// ix: usize,
-// cx: &mut ViewContext<Self>,
-// ) -> AnyElement<Self> {
-// enum ChannelChat {}
-// let host_avatar_width = theme
-// .contact_avatar
-// .width
-// .or(theme.contact_avatar.height)
-// .unwrap_or(0.);
-
-// MouseEventHandler::new::<ChannelChat, _>(ix as usize, cx, |state, cx| {
-// let tree_branch = *theme.tree_branch.in_state(is_selected).style_for(state);
-// let row = theme.project_row.in_state(is_selected).style_for(state);
-
-// Flex::<Self>::row()
-// .with_child(render_tree_branch(
-// tree_branch,
-// &row.name.text,
-// true,
-// vec2f(host_avatar_width, theme.row_height),
-// cx.font_cache(),
-// ))
-// .with_child(
-// Svg::new("icons/conversations.svg")
-// .with_color(theme.channel_hash.color)
-// .constrained()
-// .with_width(theme.channel_hash.width)
-// .aligned()
-// .left(),
-// )
-// .with_child(
-// Label::new("chat", theme.channel_name.text.clone())
-// .contained()
-// .with_style(theme.channel_name.container)
-// .aligned()
-// .left()
-// .flex(1., true),
-// )
-// .constrained()
-// .with_height(theme.row_height)
-// .contained()
-// .with_style(*theme.channel_row.style_for(is_selected, state))
-// .with_padding_left(theme.channel_row.default_style().padding.left)
-// })
-// .on_click(MouseButton::Left, move |_, this, cx| {
-// this.join_channel_chat(&JoinChannelChat { channel_id }, cx);
-// })
-// .with_cursor_style(CursorStyle::PointingHand)
-// .into_any()
-// }
-
-// fn render_channel_invite(
-// channel: Arc<Channel>,
-// channel_store: ModelHandle<ChannelStore>,
-// theme: &theme::CollabPanel,
-// is_selected: bool,
-// cx: &mut ViewContext<Self>,
-// ) -> AnyElement<Self> {
-// enum Decline {}
-// enum Accept {}
-
-// let channel_id = channel.id;
-// let is_invite_pending = channel_store
-// .read(cx)
-// .has_pending_channel_invite_response(&channel);
-// let button_spacing = theme.contact_button_spacing;
-
-// Flex::row()
-// .with_child(
-// Svg::new("icons/hash.svg")
-// .with_color(theme.channel_hash.color)
-// .constrained()
-// .with_width(theme.channel_hash.width)
-// .aligned()
-// .left(),
-// )
-// .with_child(
-// Label::new(channel.name.clone(), theme.contact_username.text.clone())
-// .contained()
-// .with_style(theme.contact_username.container)
-// .aligned()
-// .left()
-// .flex(1., true),
-// )
-// .with_child(
-// MouseEventHandler::new::<Decline, _>(channel.id as usize, cx, |mouse_state, _| {
-// let button_style = if is_invite_pending {
-// &theme.disabled_button
-// } else {
-// theme.contact_button.style_for(mouse_state)
-// };
-// render_icon_button(button_style, "icons/x.svg").aligned()
-// })
-// .with_cursor_style(CursorStyle::PointingHand)
-// .on_click(MouseButton::Left, move |_, this, cx| {
-// this.respond_to_channel_invite(channel_id, false, cx);
-// })
-// .contained()
-// .with_margin_right(button_spacing),
-// )
-// .with_child(
-// MouseEventHandler::new::<Accept, _>(channel.id as usize, cx, |mouse_state, _| {
-// let button_style = if is_invite_pending {
-// &theme.disabled_button
-// } else {
-// theme.contact_button.style_for(mouse_state)
-// };
-// render_icon_button(button_style, "icons/check.svg")
-// .aligned()
-// .flex_float()
-// })
-// .with_cursor_style(CursorStyle::PointingHand)
-// .on_click(MouseButton::Left, move |_, this, cx| {
-// this.respond_to_channel_invite(channel_id, true, cx);
-// }),
-// )
-// .constrained()
-// .with_height(theme.row_height)
-// .contained()
-// .with_style(
-// *theme
-// .contact_row
-// .in_state(is_selected)
-// .style_for(&mut Default::default()),
-// )
-// .with_padding_left(
-// theme.contact_row.default_style().padding.left + theme.channel_indent,
-// )
-// .into_any()
-// }
-
-// fn render_contact_request(
-// user: Arc<User>,
-// user_store: ModelHandle<UserStore>,
-// theme: &theme::CollabPanel,
-// is_incoming: bool,
-// is_selected: bool,
-// cx: &mut ViewContext<Self>,
-// ) -> AnyElement<Self> {
-// enum Decline {}
-// enum Accept {}
-// enum Cancel {}
-
-// let mut row = 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(),
-// theme.contact_username.text.clone(),
-// )
-// .contained()
-// .with_style(theme.contact_username.container)
-// .aligned()
-// .left()
-// .flex(1., true),
-// );
-
-// let user_id = user.id;
-// let github_login = user.github_login.clone();
-// let is_contact_request_pending = user_store.read(cx).is_contact_request_pending(&user);
-// let button_spacing = theme.contact_button_spacing;
-
-// if is_incoming {
-// row.add_child(
-// MouseEventHandler::new::<Decline, _>(user.id as usize, cx, |mouse_state, _| {
-// let button_style = if is_contact_request_pending {
-// &theme.disabled_button
-// } else {
-// theme.contact_button.style_for(mouse_state)
-// };
-// render_icon_button(button_style, "icons/x.svg").aligned()
-// })
-// .with_cursor_style(CursorStyle::PointingHand)
-// .on_click(MouseButton::Left, move |_, this, cx| {
-// this.respond_to_contact_request(user_id, false, cx);
-// })
-// .contained()
-// .with_margin_right(button_spacing),
-// );
-
-// row.add_child(
-// MouseEventHandler::new::<Accept, _>(user.id as usize, cx, |mouse_state, _| {
-// let button_style = if is_contact_request_pending {
-// &theme.disabled_button
-// } else {
-// theme.contact_button.style_for(mouse_state)
-// };
-// render_icon_button(button_style, "icons/check.svg")
-// .aligned()
-// .flex_float()
-// })
-// .with_cursor_style(CursorStyle::PointingHand)
-// .on_click(MouseButton::Left, move |_, this, cx| {
-// this.respond_to_contact_request(user_id, true, cx);
-// }),
-// );
-// } else {
-// row.add_child(
-// MouseEventHandler::new::<Cancel, _>(user.id as usize, cx, |mouse_state, _| {
-// let button_style = if is_contact_request_pending {
-// &theme.disabled_button
-// } else {
-// theme.contact_button.style_for(mouse_state)
-// };
-// render_icon_button(button_style, "icons/x.svg")
-// .aligned()
-// .flex_float()
-// })
-// .with_padding(Padding::uniform(2.))
-// .with_cursor_style(CursorStyle::PointingHand)
-// .on_click(MouseButton::Left, move |_, this, cx| {
-// this.remove_contact(user_id, &github_login, cx);
-// })
-// .flex_float(),
-// );
-// }
-
-// row.constrained()
-// .with_height(theme.row_height)
-// .contained()
-// .with_style(
-// *theme
-// .contact_row
-// .in_state(is_selected)
-// .style_for(&mut Default::default()),
-// )
-// .into_any()
-// }
-
-// fn has_subchannels(&self, ix: usize) -> bool {
-// self.entries.get(ix).map_or(false, |entry| {
-// if let ListEntry::Channel { has_children, .. } = entry {
-// *has_children
-// } else {
-// false
-// }
-// })
-// }
-
-// fn deploy_channel_context_menu(
-// &mut self,
-// position: Option<Vector2F>,
-// channel: &Channel,
-// ix: usize,
-// cx: &mut ViewContext<Self>,
-// ) {
-// self.context_menu_on_selected = position.is_none();
-
-// let clipboard_channel_name = self.channel_clipboard.as_ref().and_then(|clipboard| {
-// self.channel_store
-// .read(cx)
-// .channel_for_id(clipboard.channel_id)
-// .map(|channel| channel.name.clone())
-// });
-
-// self.context_menu.update(cx, |context_menu, cx| {
-// context_menu.set_position_mode(if self.context_menu_on_selected {
-// OverlayPositionMode::Local
-// } else {
-// OverlayPositionMode::Window
-// });
-
-// let mut items = Vec::new();
-
-// let select_action_name = if self.selection == Some(ix) {
-// "Unselect"
-// } else {
-// "Select"
-// };
-
-// items.push(ContextMenuItem::action(
-// select_action_name,
-// ToggleSelectedIx { ix },
-// ));
-
-// if self.has_subchannels(ix) {
-// let expand_action_name = if self.is_channel_collapsed(channel.id) {
-// "Expand Subchannels"
-// } else {
-// "Collapse Subchannels"
-// };
-// items.push(ContextMenuItem::action(
-// expand_action_name,
-// ToggleCollapse {
-// location: channel.id,
-// },
-// ));
-// }
-
-// items.push(ContextMenuItem::action(
-// "Open Notes",
-// OpenChannelNotes {
-// channel_id: channel.id,
-// },
-// ));
-
-// items.push(ContextMenuItem::action(
-// "Open Chat",
-// JoinChannelChat {
-// channel_id: channel.id,
-// },
-// ));
-
-// items.push(ContextMenuItem::action(
-// "Copy Channel Link",
-// CopyChannelLink {
-// channel_id: channel.id,
-// },
-// ));
-
-// if self.channel_store.read(cx).is_channel_admin(channel.id) {
-// items.extend([
-// ContextMenuItem::Separator,
-// ContextMenuItem::action(
-// "New Subchannel",
-// NewChannel {
-// location: channel.id,
-// },
-// ),
-// ContextMenuItem::action(
-// "Rename",
-// RenameChannel {
-// channel_id: channel.id,
-// },
-// ),
-// ContextMenuItem::action(
-// "Move this channel",
-// StartMoveChannelFor {
-// channel_id: channel.id,
-// },
-// ),
-// ]);
-
-// if let Some(channel_name) = clipboard_channel_name {
-// items.push(ContextMenuItem::Separator);
-// items.push(ContextMenuItem::action(
-// format!("Move '#{}' here", channel_name),
-// MoveChannel { to: channel.id },
-// ));
-// }
-
-// items.extend([
-// ContextMenuItem::Separator,
-// ContextMenuItem::action(
-// "Invite Members",
-// InviteMembers {
-// channel_id: channel.id,
-// },
-// ),
-// ContextMenuItem::action(
-// "Manage Members",
-// ManageMembers {
-// channel_id: channel.id,
-// },
-// ),
-// ContextMenuItem::Separator,
-// ContextMenuItem::action(
-// "Delete",
-// RemoveChannel {
-// channel_id: channel.id,
-// },
-// ),
-// ]);
-// }
-
-// context_menu.show(
-// position.unwrap_or_default(),
-// if self.context_menu_on_selected {
-// gpui::elements::AnchorCorner::TopRight
-// } else {
-// gpui::elements::AnchorCorner::BottomLeft
-// },
-// items,
-// cx,
-// );
-// });
-
-// cx.notify();
-// }
-
-// fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
-// if self.take_editing_state(cx) {
-// cx.focus(&self.filter_editor);
-// } else {
-// self.filter_editor.update(cx, |editor, cx| {
-// if editor.buffer().read(cx).len(cx) > 0 {
-// editor.set_text("", cx);
-// }
-// });
-// }
-
-// self.update_entries(false, cx);
-// }
-
-// fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
-// let ix = self.selection.map_or(0, |ix| ix + 1);
-// if ix < self.entries.len() {
-// self.selection = Some(ix);
-// }
-
-// self.list_state.reset(self.entries.len());
-// if let Some(ix) = self.selection {
-// self.list_state.scroll_to(ListOffset {
-// item_ix: ix,
-// offset_in_item: 0.,
-// });
-// }
-// cx.notify();
-// }
-
-// fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
-// let ix = self.selection.take().unwrap_or(0);
-// if ix > 0 {
-// self.selection = Some(ix - 1);
-// }
-
-// self.list_state.reset(self.entries.len());
-// if let Some(ix) = self.selection {
-// self.list_state.scroll_to(ListOffset {
-// item_ix: ix,
-// offset_in_item: 0.,
-// });
-// }
-// cx.notify();
-// }
-
-// fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
-// if self.confirm_channel_edit(cx) {
-// return;
-// }
-
-// if let Some(selection) = self.selection {
-// if let Some(entry) = self.entries.get(selection) {
-// match entry {
-// ListEntry::Header(section) => match section {
-// Section::ActiveCall => Self::leave_call(cx),
-// Section::Channels => self.new_root_channel(cx),
-// Section::Contacts => self.toggle_contact_finder(cx),
-// Section::ContactRequests
-// | Section::Online
-// | Section::Offline
-// | Section::ChannelInvites => {
-// self.toggle_section_expanded(*section, cx);
-// }
-// },
-// ListEntry::Contact { contact, calling } => {
-// if contact.online && !contact.busy && !calling {
-// self.call(contact.user.id, Some(self.project.clone()), cx);
-// }
-// }
-// ListEntry::ParticipantProject {
-// project_id,
-// host_user_id,
-// ..
-// } => {
-// if let Some(workspace) = self.workspace.upgrade(cx) {
-// let app_state = workspace.read(cx).app_state().clone();
-// workspace::join_remote_project(
-// *project_id,
-// *host_user_id,
-// app_state,
-// cx,
-// )
-// .detach_and_log_err(cx);
-// }
-// }
-// ListEntry::ParticipantScreen { peer_id, .. } => {
-// let Some(peer_id) = peer_id else {
-// return;
-// };
-// if let Some(workspace) = self.workspace.upgrade(cx) {
-// workspace.update(cx, |workspace, cx| {
-// workspace.open_shared_screen(*peer_id, cx)
-// });
-// }
-// }
-// ListEntry::Channel { channel, .. } => {
-// let is_active = maybe!({
-// let call_channel = ActiveCall::global(cx)
-// .read(cx)
-// .room()?
-// .read(cx)
-// .channel_id()?;
-
-// Some(call_channel == channel.id)
-// })
-// .unwrap_or(false);
-// if is_active {
-// self.open_channel_notes(
-// &OpenChannelNotes {
-// channel_id: channel.id,
-// },
-// cx,
-// )
-// } else {
-// self.join_channel(channel.id, cx)
-// }
-// }
-// ListEntry::ContactPlaceholder => self.toggle_contact_finder(cx),
-// _ => {}
-// }
-// }
-// }
-// }
-
-// fn insert_space(&mut self, _: &InsertSpace, cx: &mut ViewContext<Self>) {
-// if self.channel_editing_state.is_some() {
-// self.channel_name_editor.update(cx, |editor, cx| {
-// editor.insert(" ", cx);
-// });
-// }
-// }
-
-// fn confirm_channel_edit(&mut self, cx: &mut ViewContext<CollabPanel>) -> bool {
-// if let Some(editing_state) = &mut self.channel_editing_state {
-// match editing_state {
-// ChannelEditingState::Create {
-// location,
-// pending_name,
-// ..
-// } => {
-// if pending_name.is_some() {
-// return false;
-// }
-// let channel_name = self.channel_name_editor.read(cx).text(cx);
-
-// *pending_name = Some(channel_name.clone());
-
-// self.channel_store
-// .update(cx, |channel_store, cx| {
-// channel_store.create_channel(&channel_name, *location, cx)
-// })
-// .detach();
-// cx.notify();
-// }
-// ChannelEditingState::Rename {
-// location,
-// pending_name,
-// } => {
-// if pending_name.is_some() {
-// return false;
-// }
-// let channel_name = self.channel_name_editor.read(cx).text(cx);
-// *pending_name = Some(channel_name.clone());
-
-// self.channel_store
-// .update(cx, |channel_store, cx| {
-// channel_store.rename(*location, &channel_name, cx)
-// })
-// .detach();
-// cx.notify();
-// }
-// }
-// cx.focus_self();
-// true
-// } else {
-// false
-// }
-// }
-
-// fn toggle_section_expanded(&mut self, section: Section, cx: &mut ViewContext<Self>) {
-// if let Some(ix) = self.collapsed_sections.iter().position(|s| *s == section) {
-// self.collapsed_sections.remove(ix);
-// } else {
-// self.collapsed_sections.push(section);
-// }
-// self.update_entries(false, cx);
-// }
-
-// fn collapse_selected_channel(
-// &mut self,
-// _: &CollapseSelectedChannel,
-// cx: &mut ViewContext<Self>,
-// ) {
-// let Some(channel_id) = self.selected_channel().map(|channel| channel.id) else {
-// return;
-// };
-
-// if self.is_channel_collapsed(channel_id) {
-// return;
-// }
-
-// self.toggle_channel_collapsed(channel_id, cx);
-// }
-
-// fn expand_selected_channel(&mut self, _: &ExpandSelectedChannel, cx: &mut ViewContext<Self>) {
-// let Some(id) = self.selected_channel().map(|channel| channel.id) else {
-// return;
-// };
-
-// if !self.is_channel_collapsed(id) {
-// return;
-// }
-
-// self.toggle_channel_collapsed(id, cx)
-// }
-
-// fn toggle_channel_collapsed_action(
-// &mut self,
-// action: &ToggleCollapse,
-// cx: &mut ViewContext<Self>,
-// ) {
-// self.toggle_channel_collapsed(action.location, cx);
-// }
-
-// fn toggle_channel_collapsed<'a>(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
-// match self.collapsed_channels.binary_search(&channel_id) {
-// Ok(ix) => {
-// self.collapsed_channels.remove(ix);
-// }
-// Err(ix) => {
-// self.collapsed_channels.insert(ix, channel_id);
-// }
-// };
-// self.serialize(cx);
-// self.update_entries(true, cx);
-// cx.notify();
-// cx.focus_self();
-// }
-
-// fn is_channel_collapsed(&self, channel_id: ChannelId) -> bool {
-// self.collapsed_channels.binary_search(&channel_id).is_ok()
-// }
-
-// fn leave_call(cx: &mut ViewContext<Self>) {
-// ActiveCall::global(cx)
-// .update(cx, |call, cx| call.hang_up(cx))
-// .detach_and_log_err(cx);
-// }
-
-// fn toggle_contact_finder(&mut self, cx: &mut ViewContext<Self>) {
-// if let Some(workspace) = self.workspace.upgrade(cx) {
-// workspace.update(cx, |workspace, cx| {
-// workspace.toggle_modal(cx, |_, cx| {
-// cx.add_view(|cx| {
-// let mut finder = ContactFinder::new(self.user_store.clone(), cx);
-// finder.set_query(self.filter_editor.read(cx).text(cx), cx);
-// finder
-// })
-// });
-// });
-// }
-// }
+actions!(
+ ToggleFocus,
+ Remove,
+ Secondary,
+ CollapseSelectedChannel,
+ ExpandSelectedChannel,
+ StartMoveChannel,
+ MoveSelected,
+ InsertSpace,
+);
-// fn new_root_channel(&mut self, cx: &mut ViewContext<Self>) {
-// self.channel_editing_state = Some(ChannelEditingState::Create {
-// location: None,
-// pending_name: None,
-// });
-// self.update_entries(false, cx);
-// self.select_channel_editor();
-// cx.focus(self.channel_name_editor.as_any());
-// cx.notify();
-// }
-
-// fn select_channel_editor(&mut self) {
-// self.selection = self.entries.iter().position(|entry| match entry {
-// ListEntry::ChannelEditor { .. } => true,
-// _ => false,
-// });
-// }
+// impl_actions!(
+// collab_panel,
+// [
+// RemoveChannel,
+// NewChannel,
+// InviteMembers,
+// ManageMembers,
+// RenameChannel,
+// ToggleCollapse,
+// OpenChannelNotes,
+// JoinChannelCall,
+// JoinChannelChat,
+// CopyChannelLink,
+// StartMoveChannelFor,
+// MoveChannel,
+// ToggleSelectedIx
+// ]
+// );
-// fn new_subchannel(&mut self, action: &NewChannel, cx: &mut ViewContext<Self>) {
-// self.collapsed_channels
-// .retain(|channel| *channel != action.location);
-// self.channel_editing_state = Some(ChannelEditingState::Create {
-// location: Some(action.location.to_owned()),
-// pending_name: None,
-// });
-// self.update_entries(false, cx);
-// self.select_channel_editor();
-// cx.focus(self.channel_name_editor.as_any());
-// cx.notify();
-// }
+// #[derive(Debug, Copy, Clone, PartialEq, Eq)]
+// struct ChannelMoveClipboard {
+// channel_id: ChannelId,
+// }
-// fn invite_members(&mut self, action: &InviteMembers, cx: &mut ViewContext<Self>) {
-// self.show_channel_modal(action.channel_id, channel_modal::Mode::InviteMembers, cx);
-// }
+// const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
-// fn manage_members(&mut self, action: &ManageMembers, cx: &mut ViewContext<Self>) {
-// self.show_channel_modal(action.channel_id, channel_modal::Mode::ManageMembers, cx);
-// }
+use std::sync::Arc;
+
+use gpui::{
+ actions, div, AppContext, AsyncWindowContext, Div, EventEmitter, FocusHandle, Focusable,
+ InteractiveComponent, ParentComponent, Render, Task, View, ViewContext, VisualContext,
+ WeakView,
+};
+use project::Fs;
+use settings::Settings;
+use workspace::{
+ dock::{DockPosition, Panel, PanelEvent},
+ Workspace,
+};
+
+use crate::CollaborationPanelSettings;
+
+pub fn init(cx: &mut AppContext) {
+ cx.observe_new_views(|workspace: &mut Workspace, _| {
+ workspace.register_action(|workspace, _: &ToggleFocus, cx| {
+ workspace.toggle_panel_focus::<CollabPanel>(cx);
+ });
+ })
+ .detach();
+ // contact_finder::init(cx);
+ // channel_modal::init(cx);
+ // channel_view::init(cx);
+
+ // cx.add_action(CollabPanel::cancel);
+ // cx.add_action(CollabPanel::select_next);
+ // cx.add_action(CollabPanel::select_prev);
+ // cx.add_action(CollabPanel::confirm);
+ // cx.add_action(CollabPanel::insert_space);
+ // cx.add_action(CollabPanel::remove);
+ // cx.add_action(CollabPanel::remove_selected_channel);
+ // cx.add_action(CollabPanel::show_inline_context_menu);
+ // cx.add_action(CollabPanel::new_subchannel);
+ // cx.add_action(CollabPanel::invite_members);
+ // cx.add_action(CollabPanel::manage_members);
+ // cx.add_action(CollabPanel::rename_selected_channel);
+ // cx.add_action(CollabPanel::rename_channel);
+ // cx.add_action(CollabPanel::toggle_channel_collapsed_action);
+ // cx.add_action(CollabPanel::collapse_selected_channel);
+ // cx.add_action(CollabPanel::expand_selected_channel);
+ // cx.add_action(CollabPanel::open_channel_notes);
+ // cx.add_action(CollabPanel::join_channel_chat);
+ // cx.add_action(CollabPanel::copy_channel_link);
+
+ // cx.add_action(
+ // |panel: &mut CollabPanel, action: &ToggleSelectedIx, cx: &mut ViewContext<CollabPanel>| {
+ // if panel.selection.take() != Some(action.ix) {
+ // panel.selection = Some(action.ix)
+ // }
+
+ // cx.notify();
+ // },
+ // );
+
+ // cx.add_action(
+ // |panel: &mut CollabPanel,
+ // action: &StartMoveChannelFor,
+ // _: &mut ViewContext<CollabPanel>| {
+ // panel.channel_clipboard = Some(ChannelMoveClipboard {
+ // channel_id: action.channel_id,
+ // });
+ // },
+ // );
+
+ // cx.add_action(
+ // |panel: &mut CollabPanel, _: &StartMoveChannel, _: &mut ViewContext<CollabPanel>| {
+ // if let Some(channel) = panel.selected_channel() {
+ // panel.channel_clipboard = Some(ChannelMoveClipboard {
+ // channel_id: channel.id,
+ // })
+ // }
+ // },
+ // );
+
+ // cx.add_action(
+ // |panel: &mut CollabPanel, _: &MoveSelected, cx: &mut ViewContext<CollabPanel>| {
+ // let Some(clipboard) = panel.channel_clipboard.take() else {
+ // return;
+ // };
+ // let Some(selected_channel) = panel.selected_channel() else {
+ // return;
+ // };
+
+ // panel
+ // .channel_store
+ // .update(cx, |channel_store, cx| {
+ // channel_store.move_channel(clipboard.channel_id, Some(selected_channel.id), cx)
+ // })
+ // .detach_and_log_err(cx)
+ // },
+ // );
+
+ // cx.add_action(
+ // |panel: &mut CollabPanel, action: &MoveChannel, cx: &mut ViewContext<CollabPanel>| {
+ // if let Some(clipboard) = panel.channel_clipboard.take() {
+ // panel.channel_store.update(cx, |channel_store, cx| {
+ // channel_store
+ // .move_channel(clipboard.channel_id, Some(action.to), cx)
+ // .detach_and_log_err(cx)
+ // })
+ // }
+ // },
+ // );
+}
-// fn remove(&mut self, _: &Remove, cx: &mut ViewContext<Self>) {
-// if let Some(channel) = self.selected_channel() {
-// self.remove_channel(channel.id, cx)
-// }
-// }
+// #[derive(Debug)]
+// pub enum ChannelEditingState {
+// Create {
+// location: Option<ChannelId>,
+// pending_name: Option<String>,
+// },
+// Rename {
+// location: ChannelId,
+// pending_name: Option<String>,
+// },
+// }
-// fn rename_selected_channel(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
-// if let Some(channel) = self.selected_channel() {
-// self.rename_channel(
-// &RenameChannel {
-// channel_id: channel.id,
-// },
-// cx,
-// );
+// 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(),
// }
// }
+// }
-// fn rename_channel(&mut self, action: &RenameChannel, cx: &mut ViewContext<Self>) {
-// let channel_store = self.channel_store.read(cx);
-// if !channel_store.is_channel_admin(action.channel_id) {
-// return;
-// }
-// if let Some(channel) = channel_store.channel_for_id(action.channel_id).cloned() {
-// self.channel_editing_state = Some(ChannelEditingState::Rename {
-// location: action.channel_id.to_owned(),
-// pending_name: None,
-// });
-// self.channel_name_editor.update(cx, |editor, cx| {
-// editor.set_text(channel.name.clone(), cx);
-// editor.select_all(&Default::default(), cx);
-// });
-// cx.focus(self.channel_name_editor.as_any());
-// self.update_entries(false, cx);
-// self.select_channel_editor();
-// }
-// }
+pub struct CollabPanel {
+ width: Option<f32>,
+ fs: Arc<dyn Fs>,
+ focus_handle: FocusHandle,
+ // channel_clipboard: Option<ChannelMoveClipboard>,
+ // pending_serialization: Task<Option<()>>,
+ // context_menu: ViewHandle<ContextMenu>,
+ // filter_editor: ViewHandle<Editor>,
+ // channel_name_editor: ViewHandle<Editor>,
+ // channel_editing_state: Option<ChannelEditingState>,
+ // entries: Vec<ListEntry>,
+ // selection: Option<usize>,
+ // user_store: ModelHandle<UserStore>,
+ // client: Arc<Client>,
+ // channel_store: ModelHandle<ChannelStore>,
+ // project: ModelHandle<Project>,
+ // match_candidates: Vec<StringMatchCandidate>,
+ // list_state: ListState<Self>,
+ // subscriptions: Vec<Subscription>,
+ // collapsed_sections: Vec<Section>,
+ // collapsed_channels: Vec<ChannelId>,
+ // drag_target_channel: ChannelDragTarget,
+ _workspace: WeakView<Workspace>,
+ // context_menu_on_selected: bool,
+}
-// fn open_channel_notes(&mut self, action: &OpenChannelNotes, cx: &mut ViewContext<Self>) {
-// if let Some(workspace) = self.workspace.upgrade(cx) {
-// ChannelView::open(action.channel_id, workspace, cx).detach();
-// }
-// }
+// #[derive(PartialEq, Eq)]
+// enum ChannelDragTarget {
+// None,
+// Root,
+// Channel(ChannelId),
+// }
-// fn show_inline_context_menu(&mut self, _: &menu::ShowContextMenu, cx: &mut ViewContext<Self>) {
-// let Some(channel) = self.selected_channel() else {
-// return;
-// };
+// #[derive(Serialize, Deserialize)]
+// struct SerializedCollabPanel {
+// width: Option<f32>,
+// collapsed_channels: Option<Vec<ChannelId>>,
+// }
-// self.deploy_channel_context_menu(None, &channel.clone(), self.selection.unwrap(), cx);
-// }
+// #[derive(Debug)]
+// pub enum Event {
+// DockPositionChanged,
+// Focus,
+// Dismissed,
+// }
-// fn selected_channel(&self) -> Option<&Arc<Channel>> {
-// self.selection
-// .and_then(|ix| self.entries.get(ix))
-// .and_then(|entry| match entry {
-// ListEntry::Channel { channel, .. } => Some(channel),
-// _ => None,
-// })
-// }
+// #[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)]
+// enum Section {
+// ActiveCall,
+// Channels,
+// ChannelInvites,
+// ContactRequests,
+// Contacts,
+// Online,
+// Offline,
+// }
-// fn show_channel_modal(
-// &mut self,
+// #[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,
-// mode: channel_modal::Mode,
-// cx: &mut ViewContext<Self>,
-// ) {
-// let workspace = self.workspace.clone();
-// let user_store = self.user_store.clone();
-// let channel_store = self.channel_store.clone();
-// let members = self.channel_store.update(cx, |channel_store, cx| {
-// channel_store.get_channel_member_details(channel_id, cx)
-// });
-
-// cx.spawn(|_, mut cx| async move {
-// let members = members.await?;
-// workspace.update(&mut cx, |workspace, cx| {
-// workspace.toggle_modal(cx, |_, cx| {
-// cx.add_view(|cx| {
-// ChannelModal::new(
-// user_store.clone(),
-// channel_store.clone(),
-// channel_id,
-// mode,
-// members,
-// cx,
-// )
-// })
-// });
-// })
-// })
-// .detach();
-// }
-
-// fn remove_selected_channel(&mut self, action: &RemoveChannel, cx: &mut ViewContext<Self>) {
-// self.remove_channel(action.channel_id, cx)
-// }
-
-// fn remove_channel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
-// let channel_store = self.channel_store.clone();
-// if let Some(channel) = channel_store.read(cx).channel_for_id(channel_id) {
-// let prompt_message = format!(
-// "Are you sure you want to remove the channel \"{}\"?",
-// channel.name
-// );
-// let mut answer =
-// cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
-// let window = cx.window();
-// cx.spawn(|this, mut cx| async move {
-// if answer.next().await == Some(0) {
-// if let Err(e) = channel_store
-// .update(&mut cx, |channels, _| channels.remove_channel(channel_id))
-// .await
-// {
-// window.prompt(
-// PromptLevel::Info,
-// &format!("Failed to remove channel: {}", e),
-// &["Ok"],
-// &mut cx,
-// );
-// }
-// this.update(&mut cx, |_, cx| cx.focus_self()).ok();
-// }
-// })
-// .detach();
-// }
-// }
-
-// // Should move to the filter editor if clicking on it
-// // Should move selection to the channel editor if activating it
-
-// fn remove_contact(&mut self, user_id: u64, github_login: &str, cx: &mut ViewContext<Self>) {
-// let user_store = self.user_store.clone();
-// let prompt_message = format!(
-// "Are you sure you want to remove \"{}\" from your contacts?",
-// github_login
-// );
-// let mut answer = cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
-// let window = cx.window();
-// cx.spawn(|_, mut cx| async move {
-// if answer.next().await == Some(0) {
-// if let Err(e) = user_store
-// .update(&mut cx, |store, cx| store.remove_contact(user_id, cx))
-// .await
-// {
-// window.prompt(
-// PromptLevel::Info,
-// &format!("Failed to remove contact: {}", e),
-// &["Ok"],
-// &mut cx,
-// );
-// }
-// }
-// })
-// .detach();
-// }
-
-// fn respond_to_contact_request(
-// &mut self,
-// user_id: u64,
-// accept: bool,
-// cx: &mut ViewContext<Self>,
-// ) {
-// self.user_store
-// .update(cx, |store, cx| {
-// store.respond_to_contact_request(user_id, accept, cx)
-// })
-// .detach();
-// }
-
-// fn respond_to_channel_invite(
-// &mut self,
-// channel_id: u64,
-// accept: bool,
-// cx: &mut ViewContext<Self>,
-// ) {
-// self.channel_store
-// .update(cx, |store, cx| {
-// store.respond_to_channel_invite(channel_id, accept, cx)
-// })
-// .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 join_channel(&self, channel_id: u64, cx: &mut ViewContext<Self>) {
-// let Some(workspace) = self.workspace.upgrade(cx) else {
-// return;
-// };
-// let Some(handle) = cx.window().downcast::<Workspace>() else {
-// return;
-// };
-// workspace::join_channel(
-// channel_id,
-// workspace.read(cx).app_state().clone(),
-// Some(handle),
-// cx,
-// )
-// .detach_and_log_err(cx)
-// }
-
-// fn join_channel_chat(&mut self, action: &JoinChannelChat, cx: &mut ViewContext<Self>) {
-// let channel_id = action.channel_id;
-// if let Some(workspace) = self.workspace.upgrade(cx) {
-// cx.app_context().defer(move |cx| {
-// workspace.update(cx, |workspace, cx| {
-// if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
-// panel.update(cx, |panel, cx| {
-// panel
-// .select_channel(channel_id, None, cx)
-// .detach_and_log_err(cx);
-// });
-// }
-// });
-// });
-// }
-// }
+// },
+// ChannelChat {
+// channel_id: ChannelId,
+// },
+// ChannelEditor {
+// depth: usize,
+// },
+// Contact {
+// contact: Arc<Contact>,
+// calling: bool,
+// },
+// ContactPlaceholder,
+// }
-// fn copy_channel_link(&mut self, action: &CopyChannelLink, cx: &mut ViewContext<Self>) {
-// let channel_store = self.channel_store.read(cx);
-// let Some(channel) = channel_store.channel_for_id(action.channel_id) else {
-// return;
-// };
-// let item = ClipboardItem::new(channel.link());
-// cx.write_to_clipboard(item)
-// }
+// impl Entity for CollabPanel {
+// type Event = Event;
// }
+impl CollabPanel {
+ pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
+ 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
+ // });
+
+ // cx.subscribe(&filter_editor, |this, _, event, cx| {
+ // if let editor::Event::BufferEdited = event {
+ // let query = this.filter_editor.read(cx).text(cx);
+ // if !query.is_empty() {
+ // this.selection.take();
+ // }
+ // this.update_entries(true, cx);
+ // if !query.is_empty() {
+ // this.selection = this
+ // .entries
+ // .iter()
+ // .position(|entry| !matches!(entry, ListEntry::Header(_)));
+ // }
+ // } else if let editor::Event::Blurred = event {
+ // let query = this.filter_editor.read(cx).text(cx);
+ // if query.is_empty() {
+ // this.selection.take();
+ // this.update_entries(true, cx);
+ // }
+ // }
+ // })
+ // .detach();
+
+ // let channel_name_editor = cx.add_view(|cx| {
+ // Editor::single_line(
+ // Some(Arc::new(|theme| {
+ // theme.collab_panel.user_query_editor.clone()
+ // })),
+ // cx,
+ // )
+ // });
+
+ // cx.subscribe(&channel_name_editor, |this, _, event, cx| {
+ // if let editor::Event::Blurred = event {
+ // if let Some(state) = &this.channel_editing_state {
+ // if state.pending_name().is_some() {
+ // return;
+ // }
+ // }
+ // this.take_editing_state(cx);
+ // this.update_entries(false, cx);
+ // cx.notify();
+ // }
+ // })
+ // .detach();
+
+ // let list_state =
+ // ListState::<Self>::new(0, Orientation::Top, 1000., move |this, ix, cx| {
+ // let theme = theme::current(cx).clone();
+ // let is_selected = this.selection == Some(ix);
+ // let current_project_id = this.project.read(cx).remote_id();
+
+ // match &this.entries[ix] {
+ // ListEntry::Header(section) => {
+ // let is_collapsed = this.collapsed_sections.contains(section);
+ // this.render_header(*section, &theme, is_selected, is_collapsed, cx)
+ // }
+ // ListEntry::CallParticipant {
+ // user,
+ // peer_id,
+ // is_pending,
+ // } => Self::render_call_participant(
+ // user,
+ // *peer_id,
+ // this.user_store.clone(),
+ // *is_pending,
+ // is_selected,
+ // &theme,
+ // cx,
+ // ),
+ // ListEntry::ParticipantProject {
+ // project_id,
+ // worktree_root_names,
+ // host_user_id,
+ // is_last,
+ // } => Self::render_participant_project(
+ // *project_id,
+ // worktree_root_names,
+ // *host_user_id,
+ // Some(*project_id) == current_project_id,
+ // *is_last,
+ // is_selected,
+ // &theme,
+ // cx,
+ // ),
+ // ListEntry::ParticipantScreen { peer_id, is_last } => {
+ // Self::render_participant_screen(
+ // *peer_id,
+ // *is_last,
+ // is_selected,
+ // &theme.collab_panel,
+ // cx,
+ // )
+ // }
+ // ListEntry::Channel {
+ // channel,
+ // depth,
+ // has_children,
+ // } => {
+ // let channel_row = this.render_channel(
+ // &*channel,
+ // *depth,
+ // &theme,
+ // is_selected,
+ // *has_children,
+ // ix,
+ // cx,
+ // );
+
+ // if is_selected && this.context_menu_on_selected {
+ // Stack::new()
+ // .with_child(channel_row)
+ // .with_child(
+ // ChildView::new(&this.context_menu, cx)
+ // .aligned()
+ // .bottom()
+ // .right(),
+ // )
+ // .into_any()
+ // } else {
+ // return channel_row;
+ // }
+ // }
+ // ListEntry::ChannelNotes { channel_id } => this.render_channel_notes(
+ // *channel_id,
+ // &theme.collab_panel,
+ // is_selected,
+ // ix,
+ // cx,
+ // ),
+ // ListEntry::ChannelChat { channel_id } => this.render_channel_chat(
+ // *channel_id,
+ // &theme.collab_panel,
+ // is_selected,
+ // ix,
+ // cx,
+ // ),
+ // ListEntry::ChannelInvite(channel) => Self::render_channel_invite(
+ // channel.clone(),
+ // this.channel_store.clone(),
+ // &theme.collab_panel,
+ // is_selected,
+ // cx,
+ // ),
+ // ListEntry::IncomingRequest(user) => Self::render_contact_request(
+ // user.clone(),
+ // this.user_store.clone(),
+ // &theme.collab_panel,
+ // true,
+ // is_selected,
+ // cx,
+ // ),
+ // ListEntry::OutgoingRequest(user) => Self::render_contact_request(
+ // user.clone(),
+ // this.user_store.clone(),
+ // &theme.collab_panel,
+ // false,
+ // is_selected,
+ // cx,
+ // ),
+ // ListEntry::Contact { contact, calling } => Self::render_contact(
+ // contact,
+ // *calling,
+ // &this.project,
+ // &theme,
+ // is_selected,
+ // cx,
+ // ),
+ // ListEntry::ChannelEditor { depth } => {
+ // this.render_channel_editor(&theme, *depth, cx)
+ // }
+ // ListEntry::ContactPlaceholder => {
+ // this.render_contact_placeholder(&theme.collab_panel, is_selected, cx)
+ // }
+ // }
+ // });
+
+ let this = Self {
+ width: None,
+ focus_handle: cx.focus_handle(),
+ // channel_clipboard: None,
+ fs: workspace.app_state().fs.clone(),
+ // 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,
+ // selection: None,
+ // 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(),
+ // context_menu_on_selected: true,
+ // drag_target_channel: ChannelDragTarget::None,
+ // list_state,
+ };
+
+ // this.update_entries(false, cx);
+
+ // // Update the dock position when the setting changes.
+ // let mut old_dock_position = this.position(cx);
+ // this.subscriptions
+ // .push(
+ // cx.observe_global::<SettingsStore, _>(move |this: &mut Self, cx| {
+ // let new_dock_position = this.position(cx);
+ // if new_dock_position != old_dock_position {
+ // old_dock_position = new_dock_position;
+ // cx.emit(Event::DockPositionChanged);
+ // }
+ // cx.notify();
+ // }),
+ // );
+
+ // 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.channel_store, |this, _, cx| {
+ // this.update_entries(true, cx)
+ // }));
+ // this.subscriptions
+ // .push(cx.observe(&active_call, |this, _, cx| this.update_entries(true, cx)));
+ // this.subscriptions
+ // .push(cx.observe_flag::<ChannelsAlpha, _>(move |_, this, cx| {
+ // this.update_entries(true, cx)
+ // }));
+ // this.subscriptions.push(cx.subscribe(
+ // &this.channel_store,
+ // |this, _channel_store, e, cx| match e {
+ // ChannelEvent::ChannelCreated(channel_id)
+ // | ChannelEvent::ChannelRenamed(channel_id) => {
+ // if this.take_editing_state(cx) {
+ // this.update_entries(false, cx);
+ // this.selection = this.entries.iter().position(|entry| {
+ // if let ListEntry::Channel { channel, .. } = entry {
+ // channel.id == *channel_id
+ // } else {
+ // false
+ // }
+ // });
+ // }
+ // }
+ // },
+ // ));
+
+ this
+ })
+ }
+
+ pub fn load(
+ workspace: WeakView<Workspace>,
+ cx: AsyncWindowContext,
+ ) -> Task<anyhow::Result<View<Self>>> {
+ cx.spawn(|mut cx| async move {
+ // todo!()
+ // let serialized_panel = if let Some(panel) = cx
+ // .background()
+ // .spawn(async move { KEY_VALUE_STORE.read_kvp(COLLABORATION_PANEL_KEY) })
+ // .await
+ // .log_err()
+ // .flatten()
+ // {
+ // match serde_json::from_str::<SerializedCollabPanel>(&panel) {
+ // Ok(panel) => Some(panel),
+ // Err(err) => {
+ // log::error!("Failed to deserialize collaboration panel: {}", err);
+ // None
+ // }
+ // }
+ // } else {
+ // None
+ // };
+
+ workspace.update(&mut cx, |workspace, cx| {
+ let panel = CollabPanel::new(workspace, cx);
+ // if let Some(serialized_panel) = serialized_panel {
+ // panel.update(cx, |panel, cx| {
+ // panel.width = serialized_panel.width;
+ // panel.collapsed_channels = serialized_panel
+ // .collapsed_channels
+ // .unwrap_or_else(|| Vec::new());
+ // cx.notify();
+ // });
+ // }
+ panel
+ })
+ })
+ }
+
+ // fn serialize(&mut self, cx: &mut ViewContext<Self>) {
+ // let width = self.width;
+ // let collapsed_channels = self.collapsed_channels.clone();
+ // self.pending_serialization = cx.background().spawn(
+ // async move {
+ // KEY_VALUE_STORE
+ // .write_kvp(
+ // COLLABORATION_PANEL_KEY.into(),
+ // serde_json::to_string(&SerializedCollabPanel {
+ // width,
+ // collapsed_channels: Some(collapsed_channels),
+ // })?,
+ // )
+ // .await?;
+ // anyhow::Ok(())
+ // }
+ // .log_err(),
+ // );
+ // }
+
+ // 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());
+
+ // 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();
+ // }
+
+ // fn render_call_participant(
+ // user: &User,
+ // peer_id: Option<PeerId>,
+ // user_store: ModelHandle<UserStore>,
+ // is_pending: bool,
+ // is_selected: bool,
+ // theme: &theme::Theme,
+ // cx: &mut ViewContext<Self>,
+ // ) -> AnyElement<Self> {
+ // enum CallParticipant {}
+ // enum CallParticipantTooltip {}
+ // enum LeaveCallButton {}
+ // enum LeaveCallTooltip {}
+
+ // let collab_theme = &theme.collab_panel;
+
+ // let is_current_user =
+ // user_store.read(cx).current_user().map(|user| user.id) == Some(user.id);
+
+ // let content = MouseEventHandler::new::<CallParticipant, _>(
+ // user.id as usize,
+ // cx,
+ // |mouse_state, cx| {
+ // let style = if is_current_user {
+ // *collab_theme
+ // .contact_row
+ // .in_state(is_selected)
+ // .style_for(&mut Default::default())
+ // } else {
+ // *collab_theme
+ // .contact_row
+ // .in_state(is_selected)
+ // .style_for(mouse_state)
+ // };
+
+ // Flex::row()
+ // .with_children(user.avatar.clone().map(|avatar| {
+ // Image::from_data(avatar)
+ // .with_style(collab_theme.contact_avatar)
+ // .aligned()
+ // .left()
+ // }))
+ // .with_child(
+ // Label::new(
+ // user.github_login.clone(),
+ // collab_theme.contact_username.text.clone(),
+ // )
+ // .contained()
+ // .with_style(collab_theme.contact_username.container)
+ // .aligned()
+ // .left()
+ // .flex(1., true),
+ // )
+ // .with_children(if is_pending {
+ // Some(
+ // Label::new("Calling", collab_theme.calling_indicator.text.clone())
+ // .contained()
+ // .with_style(collab_theme.calling_indicator.container)
+ // .aligned()
+ // .into_any(),
+ // )
+ // } else if is_current_user {
+ // Some(
+ // MouseEventHandler::new::<LeaveCallButton, _>(0, cx, |state, _| {
+ // render_icon_button(
+ // theme
+ // .collab_panel
+ // .leave_call_button
+ // .style_for(is_selected, state),
+ // "icons/exit.svg",
+ // )
+ // })
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .on_click(MouseButton::Left, |_, _, cx| {
+ // Self::leave_call(cx);
+ // })
+ // .with_tooltip::<LeaveCallTooltip>(
+ // 0,
+ // "Leave call",
+ // None,
+ // theme.tooltip.clone(),
+ // cx,
+ // )
+ // .into_any(),
+ // )
+ // } else {
+ // None
+ // })
+ // .constrained()
+ // .with_height(collab_theme.row_height)
+ // .contained()
+ // .with_style(style)
+ // },
+ // );
+
+ // if is_current_user || is_pending || peer_id.is_none() {
+ // return content.into_any();
+ // }
+
+ // let tooltip = format!("Follow {}", user.github_login);
+
+ // content
+ // .on_click(MouseButton::Left, move |_, this, cx| {
+ // if let Some(workspace) = this.workspace.upgrade(cx) {
+ // workspace
+ // .update(cx, |workspace, cx| workspace.follow(peer_id.unwrap(), cx))
+ // .map(|task| task.detach_and_log_err(cx));
+ // }
+ // })
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .with_tooltip::<CallParticipantTooltip>(
+ // user.id as usize,
+ // tooltip,
+ // Some(Box::new(FollowNextCollaborator)),
+ // theme.tooltip.clone(),
+ // cx,
+ // )
+ // .into_any()
+ // }
+
+ // fn render_participant_project(
+ // project_id: u64,
+ // worktree_root_names: &[String],
+ // host_user_id: u64,
+ // is_current: bool,
+ // is_last: bool,
+ // is_selected: bool,
+ // theme: &theme::Theme,
+ // cx: &mut ViewContext<Self>,
+ // ) -> AnyElement<Self> {
+ // enum JoinProject {}
+ // enum JoinProjectTooltip {}
+
+ // let collab_theme = &theme.collab_panel;
+ // let host_avatar_width = collab_theme
+ // .contact_avatar
+ // .width
+ // .or(collab_theme.contact_avatar.height)
+ // .unwrap_or(0.);
+ // let tree_branch = collab_theme.tree_branch;
+ // let project_name = if worktree_root_names.is_empty() {
+ // "untitled".to_string()
+ // } else {
+ // worktree_root_names.join(", ")
+ // };
+
+ // let content =
+ // MouseEventHandler::new::<JoinProject, _>(project_id as usize, cx, |mouse_state, cx| {
+ // let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
+ // let row = if is_current {
+ // collab_theme
+ // .project_row
+ // .in_state(true)
+ // .style_for(&mut Default::default())
+ // } else {
+ // collab_theme
+ // .project_row
+ // .in_state(is_selected)
+ // .style_for(mouse_state)
+ // };
+
+ // Flex::row()
+ // .with_child(render_tree_branch(
+ // tree_branch,
+ // &row.name.text,
+ // is_last,
+ // vec2f(host_avatar_width, collab_theme.row_height),
+ // cx.font_cache(),
+ // ))
+ // .with_child(
+ // Svg::new("icons/file_icons/folder.svg")
+ // .with_color(collab_theme.channel_hash.color)
+ // .constrained()
+ // .with_width(collab_theme.channel_hash.width)
+ // .aligned()
+ // .left(),
+ // )
+ // .with_child(
+ // Label::new(project_name.clone(), row.name.text.clone())
+ // .aligned()
+ // .left()
+ // .contained()
+ // .with_style(row.name.container)
+ // .flex(1., false),
+ // )
+ // .constrained()
+ // .with_height(collab_theme.row_height)
+ // .contained()
+ // .with_style(row.container)
+ // });
+
+ // if is_current {
+ // return content.into_any();
+ // }
+
+ // content
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .on_click(MouseButton::Left, move |_, this, cx| {
+ // if let Some(workspace) = this.workspace.upgrade(cx) {
+ // let app_state = workspace.read(cx).app_state().clone();
+ // workspace::join_remote_project(project_id, host_user_id, app_state, cx)
+ // .detach_and_log_err(cx);
+ // }
+ // })
+ // .with_tooltip::<JoinProjectTooltip>(
+ // project_id as usize,
+ // format!("Open {}", project_name),
+ // None,
+ // theme.tooltip.clone(),
+ // cx,
+ // )
+ // .into_any()
+ // }
+
+ // fn render_participant_screen(
+ // peer_id: Option<PeerId>,
+ // is_last: bool,
+ // is_selected: bool,
+ // theme: &theme::CollabPanel,
+ // cx: &mut ViewContext<Self>,
+ // ) -> AnyElement<Self> {
+ // enum OpenSharedScreen {}
+
+ // let host_avatar_width = theme
+ // .contact_avatar
+ // .width
+ // .or(theme.contact_avatar.height)
+ // .unwrap_or(0.);
+ // let tree_branch = theme.tree_branch;
+
+ // let handler = MouseEventHandler::new::<OpenSharedScreen, _>(
+ // peer_id.map(|id| id.as_u64()).unwrap_or(0) as usize,
+ // cx,
+ // |mouse_state, cx| {
+ // let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
+ // let row = theme
+ // .project_row
+ // .in_state(is_selected)
+ // .style_for(mouse_state);
+
+ // Flex::row()
+ // .with_child(render_tree_branch(
+ // tree_branch,
+ // &row.name.text,
+ // is_last,
+ // vec2f(host_avatar_width, theme.row_height),
+ // cx.font_cache(),
+ // ))
+ // .with_child(
+ // Svg::new("icons/desktop.svg")
+ // .with_color(theme.channel_hash.color)
+ // .constrained()
+ // .with_width(theme.channel_hash.width)
+ // .aligned()
+ // .left(),
+ // )
+ // .with_child(
+ // Label::new("Screen", row.name.text.clone())
+ // .aligned()
+ // .left()
+ // .contained()
+ // .with_style(row.name.container)
+ // .flex(1., false),
+ // )
+ // .constrained()
+ // .with_height(theme.row_height)
+ // .contained()
+ // .with_style(row.container)
+ // },
+ // );
+ // if peer_id.is_none() {
+ // return handler.into_any();
+ // }
+ // handler
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .on_click(MouseButton::Left, move |_, this, cx| {
+ // if let Some(workspace) = this.workspace.upgrade(cx) {
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.open_shared_screen(peer_id.unwrap(), cx)
+ // });
+ // }
+ // })
+ // .into_any()
+ // }
+
+ // fn take_editing_state(&mut self, cx: &mut ViewContext<Self>) -> bool {
+ // if let Some(_) = self.channel_editing_state.take() {
+ // self.channel_name_editor.update(cx, |editor, cx| {
+ // editor.set_text("", cx);
+ // });
+ // true
+ // } else {
+ // false
+ // }
+ // }
+
+ // fn render_header(
+ // &self,
+ // section: Section,
+ // theme: &theme::Theme,
+ // is_selected: bool,
+ // is_collapsed: bool,
+ // cx: &mut ViewContext<Self>,
+ // ) -> AnyElement<Self> {
+ // enum Header {}
+ // enum LeaveCallContactList {}
+ // enum AddChannel {}
+
+ // let tooltip_style = &theme.tooltip;
+ // let mut channel_link = None;
+ // let mut channel_tooltip_text = None;
+ // let mut channel_icon = None;
+ // let mut is_dragged_over = false;
+
+ // let text = match section {
+ // Section::ActiveCall => {
+ // let channel_name = maybe!({
+ // let channel_id = ActiveCall::global(cx).read(cx).channel_id(cx)?;
+
+ // let channel = self.channel_store.read(cx).channel_for_id(channel_id)?;
+
+ // channel_link = Some(channel.link());
+ // (channel_icon, channel_tooltip_text) = match channel.visibility {
+ // proto::ChannelVisibility::Public => {
+ // (Some("icons/public.svg"), Some("Copy public channel link."))
+ // }
+ // proto::ChannelVisibility::Members => {
+ // (Some("icons/hash.svg"), Some("Copy private channel link."))
+ // }
+ // };
+
+ // Some(channel.name.as_str())
+ // });
+
+ // if let Some(name) = channel_name {
+ // Cow::Owned(format!("{}", name))
+ // } else {
+ // Cow::Borrowed("Current Call")
+ // }
+ // }
+ // Section::ContactRequests => Cow::Borrowed("Requests"),
+ // Section::Contacts => Cow::Borrowed("Contacts"),
+ // Section::Channels => Cow::Borrowed("Channels"),
+ // Section::ChannelInvites => Cow::Borrowed("Invites"),
+ // Section::Online => Cow::Borrowed("Online"),
+ // Section::Offline => Cow::Borrowed("Offline"),
+ // };
+
+ // enum AddContact {}
+ // let button = match section {
+ // Section::ActiveCall => channel_link.map(|channel_link| {
+ // let channel_link_copy = channel_link.clone();
+ // MouseEventHandler::new::<AddContact, _>(0, cx, |state, _| {
+ // render_icon_button(
+ // theme
+ // .collab_panel
+ // .leave_call_button
+ // .style_for(is_selected, state),
+ // "icons/link.svg",
+ // )
+ // })
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .on_click(MouseButton::Left, move |_, _, cx| {
+ // let item = ClipboardItem::new(channel_link_copy.clone());
+ // cx.write_to_clipboard(item)
+ // })
+ // .with_tooltip::<AddContact>(
+ // 0,
+ // channel_tooltip_text.unwrap(),
+ // None,
+ // tooltip_style.clone(),
+ // cx,
+ // )
+ // }),
+ // Section::Contacts => Some(
+ // MouseEventHandler::new::<LeaveCallContactList, _>(0, cx, |state, _| {
+ // render_icon_button(
+ // theme
+ // .collab_panel
+ // .add_contact_button
+ // .style_for(is_selected, state),
+ // "icons/plus.svg",
+ // )
+ // })
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .on_click(MouseButton::Left, |_, this, cx| {
+ // this.toggle_contact_finder(cx);
+ // })
+ // .with_tooltip::<LeaveCallContactList>(
+ // 0,
+ // "Search for new contact",
+ // None,
+ // tooltip_style.clone(),
+ // cx,
+ // ),
+ // ),
+ // Section::Channels => {
+ // if cx
+ // .global::<DragAndDrop<Workspace>>()
+ // .currently_dragged::<Channel>(cx.window())
+ // .is_some()
+ // && self.drag_target_channel == ChannelDragTarget::Root
+ // {
+ // is_dragged_over = true;
+ // }
+
+ // Some(
+ // MouseEventHandler::new::<AddChannel, _>(0, cx, |state, _| {
+ // render_icon_button(
+ // theme
+ // .collab_panel
+ // .add_contact_button
+ // .style_for(is_selected, state),
+ // "icons/plus.svg",
+ // )
+ // })
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .on_click(MouseButton::Left, |_, this, cx| this.new_root_channel(cx))
+ // .with_tooltip::<AddChannel>(
+ // 0,
+ // "Create a channel",
+ // None,
+ // tooltip_style.clone(),
+ // cx,
+ // ),
+ // )
+ // }
+ // _ => None,
+ // };
+
+ // let can_collapse = match section {
+ // Section::ActiveCall | Section::Channels | Section::Contacts => false,
+ // Section::ChannelInvites
+ // | Section::ContactRequests
+ // | Section::Online
+ // | Section::Offline => true,
+ // };
+ // let icon_size = (&theme.collab_panel).section_icon_size;
+ // let mut result = MouseEventHandler::new::<Header, _>(section as usize, cx, |state, _| {
+ // let header_style = if can_collapse {
+ // theme
+ // .collab_panel
+ // .subheader_row
+ // .in_state(is_selected)
+ // .style_for(state)
+ // } else {
+ // &theme.collab_panel.header_row
+ // };
+
+ // Flex::row()
+ // .with_children(if can_collapse {
+ // Some(
+ // Svg::new(if is_collapsed {
+ // "icons/chevron_right.svg"
+ // } else {
+ // "icons/chevron_down.svg"
+ // })
+ // .with_color(header_style.text.color)
+ // .constrained()
+ // .with_max_width(icon_size)
+ // .with_max_height(icon_size)
+ // .aligned()
+ // .constrained()
+ // .with_width(icon_size)
+ // .contained()
+ // .with_margin_right(
+ // theme.collab_panel.contact_username.container.margin.left,
+ // ),
+ // )
+ // } else if let Some(channel_icon) = channel_icon {
+ // Some(
+ // Svg::new(channel_icon)
+ // .with_color(header_style.text.color)
+ // .constrained()
+ // .with_max_width(icon_size)
+ // .with_max_height(icon_size)
+ // .aligned()
+ // .constrained()
+ // .with_width(icon_size)
+ // .contained()
+ // .with_margin_right(
+ // theme.collab_panel.contact_username.container.margin.left,
+ // ),
+ // )
+ // } else {
+ // None
+ // })
+ // .with_child(
+ // Label::new(text, header_style.text.clone())
+ // .aligned()
+ // .left()
+ // .flex(1., true),
+ // )
+ // .with_children(button.map(|button| button.aligned().right()))
+ // .constrained()
+ // .with_height(theme.collab_panel.row_height)
+ // .contained()
+ // .with_style(if is_dragged_over {
+ // theme.collab_panel.dragged_over_header
+ // } else {
+ // header_style.container
+ // })
+ // });
+
+ // result = result
+ // .on_move(move |_, this, cx| {
+ // if cx
+ // .global::<DragAndDrop<Workspace>>()
+ // .currently_dragged::<Channel>(cx.window())
+ // .is_some()
+ // {
+ // this.drag_target_channel = ChannelDragTarget::Root;
+ // cx.notify()
+ // }
+ // })
+ // .on_up(MouseButton::Left, move |_, this, cx| {
+ // if let Some((_, dragged_channel)) = cx
+ // .global::<DragAndDrop<Workspace>>()
+ // .currently_dragged::<Channel>(cx.window())
+ // {
+ // this.channel_store
+ // .update(cx, |channel_store, cx| {
+ // channel_store.move_channel(dragged_channel.id, None, cx)
+ // })
+ // .detach_and_log_err(cx)
+ // }
+ // });
+
+ // if can_collapse {
+ // result = result
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .on_click(MouseButton::Left, move |_, this, cx| {
+ // if can_collapse {
+ // this.toggle_section_expanded(section, cx);
+ // }
+ // })
+ // }
+
+ // result.into_any()
+ // }
+
+ // fn render_contact(
+ // contact: &Contact,
+ // calling: bool,
+ // project: &ModelHandle<Project>,
+ // theme: &theme::Theme,
+ // is_selected: bool,
+ // cx: &mut ViewContext<Self>,
+ // ) -> AnyElement<Self> {
+ // enum ContactTooltip {}
+
+ // let collab_theme = &theme.collab_panel;
+ // let online = contact.online;
+ // let busy = contact.busy || calling;
+ // let user_id = contact.user.id;
+ // let github_login = contact.user.github_login.clone();
+ // let initial_project = project.clone();
+
+ // 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_child(
+ // Label::new(
+ // contact.user.github_login.clone(),
+ // collab_theme.contact_username.text.clone(),
+ // )
+ // .contained()
+ // .with_style(collab_theme.contact_username.container)
+ // .aligned()
+ // .left()
+ // .flex(1., true),
+ // )
+ // .with_children(if state.hovered() {
+ // Some(
+ // MouseEventHandler::new::<Cancel, _>(
+ // contact.user.id as usize,
+ // cx,
+ // |mouse_state, _| {
+ // let button_style =
+ // collab_theme.contact_button.style_for(mouse_state);
+ // render_icon_button(button_style, "icons/x.svg")
+ // .aligned()
+ // .flex_float()
+ // },
+ // )
+ // .with_padding(Padding::uniform(2.))
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .on_click(MouseButton::Left, move |_, this, cx| {
+ // this.remove_contact(user_id, &github_login, cx);
+ // })
+ // .flex_float(),
+ // )
+ // } else {
+ // None
+ // })
+ // .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_placeholder(
+ // &self,
+ // theme: &theme::CollabPanel,
+ // is_selected: bool,
+ // cx: &mut ViewContext<Self>,
+ // ) -> AnyElement<Self> {
+ // enum AddContacts {}
+ // MouseEventHandler::new::<AddContacts, _>(0, cx, |state, _| {
+ // let style = theme.list_empty_state.style_for(is_selected, state);
+ // Flex::row()
+ // .with_child(
+ // Svg::new("icons/plus.svg")
+ // .with_color(theme.list_empty_icon.color)
+ // .constrained()
+ // .with_width(theme.list_empty_icon.width)
+ // .aligned()
+ // .left(),
+ // )
+ // .with_child(
+ // Label::new("Add a contact", style.text.clone())
+ // .contained()
+ // .with_style(theme.list_empty_label_container),
+ // )
+ // .align_children_center()
+ // .contained()
+ // .with_style(style.container)
+ // .into_any()
+ // })
+ // .on_click(MouseButton::Left, |_, this, cx| {
+ // this.toggle_contact_finder(cx);
+ // })
+ // .into_any()
+ // }
+
+ // fn render_channel_editor(
+ // &self,
+ // theme: &theme::Theme,
+ // depth: usize,
+ // cx: &AppContext,
+ // ) -> AnyElement<Self> {
+ // Flex::row()
+ // .with_child(
+ // Empty::new()
+ // .constrained()
+ // .with_width(theme.collab_panel.disclosure.button_space()),
+ // )
+ // .with_child(
+ // Svg::new("icons/hash.svg")
+ // .with_color(theme.collab_panel.channel_hash.color)
+ // .constrained()
+ // .with_width(theme.collab_panel.channel_hash.width)
+ // .aligned()
+ // .left(),
+ // )
+ // .with_child(
+ // if let Some(pending_name) = self
+ // .channel_editing_state
+ // .as_ref()
+ // .and_then(|state| state.pending_name())
+ // {
+ // Label::new(
+ // pending_name.to_string(),
+ // theme.collab_panel.contact_username.text.clone(),
+ // )
+ // .contained()
+ // .with_style(theme.collab_panel.contact_username.container)
+ // .aligned()
+ // .left()
+ // .flex(1., true)
+ // .into_any()
+ // } else {
+ // ChildView::new(&self.channel_name_editor, cx)
+ // .aligned()
+ // .left()
+ // .contained()
+ // .with_style(theme.collab_panel.channel_editor)
+ // .flex(1.0, true)
+ // .into_any()
+ // },
+ // )
+ // .align_children_center()
+ // .constrained()
+ // .with_height(theme.collab_panel.row_height)
+ // .contained()
+ // .with_style(ContainerStyle {
+ // background_color: Some(theme.editor.background),
+ // ..*theme.collab_panel.contact_row.default_style()
+ // })
+ // .with_padding_left(
+ // theme.collab_panel.contact_row.default_style().padding.left
+ // + theme.collab_panel.channel_indent * depth as f32,
+ // )
+ // .into_any()
+ // }
+
+ // fn render_channel(
+ // &self,
+ // channel: &Channel,
+ // depth: usize,
+ // theme: &theme::Theme,
+ // is_selected: bool,
+ // has_children: bool,
+ // ix: usize,
+ // cx: &mut ViewContext<Self>,
+ // ) -> AnyElement<Self> {
+ // let channel_id = channel.id;
+ // let collab_theme = &theme.collab_panel;
+ // let is_public = self
+ // .channel_store
+ // .read(cx)
+ // .channel_for_id(channel_id)
+ // .map(|channel| channel.visibility)
+ // == Some(proto::ChannelVisibility::Public);
+ // let other_selected = self.selected_channel().map(|channel| channel.id) == Some(channel.id);
+ // let disclosed =
+ // has_children.then(|| !self.collapsed_channels.binary_search(&channel.id).is_ok());
+
+ // let is_active = maybe!({
+ // let call_channel = ActiveCall::global(cx)
+ // .read(cx)
+ // .room()?
+ // .read(cx)
+ // .channel_id()?;
+ // Some(call_channel == channel_id)
+ // })
+ // .unwrap_or(false);
+
+ // const FACEPILE_LIMIT: usize = 3;
+
+ // enum ChannelCall {}
+ // enum ChannelNote {}
+ // enum NotesTooltip {}
+ // enum ChatTooltip {}
+ // enum ChannelTooltip {}
+
+ // let mut is_dragged_over = false;
+ // if cx
+ // .global::<DragAndDrop<Workspace>>()
+ // .currently_dragged::<Channel>(cx.window())
+ // .is_some()
+ // && self.drag_target_channel == ChannelDragTarget::Channel(channel_id)
+ // {
+ // is_dragged_over = true;
+ // }
+
+ // let has_messages_notification = channel.unseen_message_id.is_some();
+
+ // MouseEventHandler::new::<Channel, _>(ix, cx, |state, cx| {
+ // let row_hovered = state.hovered();
+
+ // let mut select_state = |interactive: &Interactive<ContainerStyle>| {
+ // if state.clicked() == Some(MouseButton::Left) && interactive.clicked.is_some() {
+ // interactive.clicked.as_ref().unwrap().clone()
+ // } else if state.hovered() || other_selected {
+ // interactive
+ // .hovered
+ // .as_ref()
+ // .unwrap_or(&interactive.default)
+ // .clone()
+ // } else {
+ // interactive.default.clone()
+ // }
+ // };
+
+ // Flex::<Self>::row()
+ // .with_child(
+ // Svg::new(if is_public {
+ // "icons/public.svg"
+ // } else {
+ // "icons/hash.svg"
+ // })
+ // .with_color(collab_theme.channel_hash.color)
+ // .constrained()
+ // .with_width(collab_theme.channel_hash.width)
+ // .aligned()
+ // .left(),
+ // )
+ // .with_child({
+ // let style = collab_theme.channel_name.inactive_state();
+ // Flex::row()
+ // .with_child(
+ // Label::new(channel.name.clone(), style.text.clone())
+ // .contained()
+ // .with_style(style.container)
+ // .aligned()
+ // .left()
+ // .with_tooltip::<ChannelTooltip>(
+ // ix,
+ // "Join channel",
+ // None,
+ // theme.tooltip.clone(),
+ // cx,
+ // ),
+ // )
+ // .with_children({
+ // let participants =
+ // self.channel_store.read(cx).channel_participants(channel_id);
+
+ // if !participants.is_empty() {
+ // let extra_count = participants.len().saturating_sub(FACEPILE_LIMIT);
+
+ // let result = FacePile::new(collab_theme.face_overlap)
+ // .with_children(
+ // participants
+ // .iter()
+ // .filter_map(|user| {
+ // Some(
+ // Image::from_data(user.avatar.clone()?)
+ // .with_style(collab_theme.channel_avatar),
+ // )
+ // })
+ // .take(FACEPILE_LIMIT),
+ // )
+ // .with_children((extra_count > 0).then(|| {
+ // Label::new(
+ // format!("+{}", extra_count),
+ // collab_theme.extra_participant_label.text.clone(),
+ // )
+ // .contained()
+ // .with_style(collab_theme.extra_participant_label.container)
+ // }));
+
+ // Some(result)
+ // } else {
+ // None
+ // }
+ // })
+ // .with_spacing(8.)
+ // .align_children_center()
+ // .flex(1., true)
+ // })
+ // .with_child(
+ // MouseEventHandler::new::<ChannelNote, _>(ix, cx, move |mouse_state, _| {
+ // let container_style = collab_theme
+ // .disclosure
+ // .button
+ // .style_for(mouse_state)
+ // .container;
+
+ // if channel.unseen_message_id.is_some() {
+ // Svg::new("icons/conversations.svg")
+ // .with_color(collab_theme.channel_note_active_color)
+ // .constrained()
+ // .with_width(collab_theme.channel_hash.width)
+ // .contained()
+ // .with_style(container_style)
+ // .with_uniform_padding(4.)
+ // .into_any()
+ // } else if row_hovered {
+ // Svg::new("icons/conversations.svg")
+ // .with_color(collab_theme.channel_hash.color)
+ // .constrained()
+ // .with_width(collab_theme.channel_hash.width)
+ // .contained()
+ // .with_style(container_style)
+ // .with_uniform_padding(4.)
+ // .into_any()
+ // } else {
+ // Empty::new().into_any()
+ // }
+ // })
+ // .on_click(MouseButton::Left, move |_, this, cx| {
+ // this.join_channel_chat(&JoinChannelChat { channel_id }, cx);
+ // })
+ // .with_tooltip::<ChatTooltip>(
+ // ix,
+ // "Open channel chat",
+ // None,
+ // theme.tooltip.clone(),
+ // cx,
+ // )
+ // .contained()
+ // .with_margin_right(4.),
+ // )
+ // .with_child(
+ // MouseEventHandler::new::<ChannelCall, _>(ix, cx, move |mouse_state, cx| {
+ // let container_style = collab_theme
+ // .disclosure
+ // .button
+ // .style_for(mouse_state)
+ // .container;
+ // if row_hovered || channel.unseen_note_version.is_some() {
+ // Svg::new("icons/file.svg")
+ // .with_color(if channel.unseen_note_version.is_some() {
+ // collab_theme.channel_note_active_color
+ // } else {
+ // collab_theme.channel_hash.color
+ // })
+ // .constrained()
+ // .with_width(collab_theme.channel_hash.width)
+ // .contained()
+ // .with_style(container_style)
+ // .with_uniform_padding(4.)
+ // .with_margin_right(collab_theme.channel_hash.container.margin.left)
+ // .with_tooltip::<NotesTooltip>(
+ // ix as usize,
+ // "Open channel notes",
+ // None,
+ // theme.tooltip.clone(),
+ // cx,
+ // )
+ // .into_any()
+ // } else if has_messages_notification {
+ // Empty::new()
+ // .constrained()
+ // .with_width(collab_theme.channel_hash.width)
+ // .contained()
+ // .with_uniform_padding(4.)
+ // .with_margin_right(collab_theme.channel_hash.container.margin.left)
+ // .into_any()
+ // } else {
+ // Empty::new().into_any()
+ // }
+ // })
+ // .on_click(MouseButton::Left, move |_, this, cx| {
+ // this.open_channel_notes(&OpenChannelNotes { channel_id }, cx);
+ // }),
+ // )
+ // .align_children_center()
+ // .styleable_component()
+ // .disclosable(
+ // disclosed,
+ // Box::new(ToggleCollapse {
+ // location: channel.id.clone(),
+ // }),
+ // )
+ // .with_id(ix)
+ // .with_style(collab_theme.disclosure.clone())
+ // .element()
+ // .constrained()
+ // .with_height(collab_theme.row_height)
+ // .contained()
+ // .with_style(select_state(
+ // collab_theme
+ // .channel_row
+ // .in_state(is_selected || is_active || is_dragged_over),
+ // ))
+ // .with_padding_left(
+ // collab_theme.channel_row.default_style().padding.left
+ // + collab_theme.channel_indent * depth as f32,
+ // )
+ // })
+ // .on_click(MouseButton::Left, move |_, this, cx| {
+ // if this.drag_target_channel == ChannelDragTarget::None {
+ // if is_active {
+ // this.open_channel_notes(&OpenChannelNotes { channel_id }, cx)
+ // } else {
+ // this.join_channel(channel_id, cx)
+ // }
+ // }
+ // })
+ // .on_click(MouseButton::Right, {
+ // let channel = channel.clone();
+ // move |e, this, cx| {
+ // this.deploy_channel_context_menu(Some(e.position), &channel, ix, cx);
+ // }
+ // })
+ // .on_up(MouseButton::Left, move |_, this, cx| {
+ // if let Some((_, dragged_channel)) = cx
+ // .global::<DragAndDrop<Workspace>>()
+ // .currently_dragged::<Channel>(cx.window())
+ // {
+ // this.channel_store
+ // .update(cx, |channel_store, cx| {
+ // channel_store.move_channel(dragged_channel.id, Some(channel_id), cx)
+ // })
+ // .detach_and_log_err(cx)
+ // }
+ // })
+ // .on_move({
+ // let channel = channel.clone();
+ // move |_, this, cx| {
+ // if let Some((_, dragged_channel)) = cx
+ // .global::<DragAndDrop<Workspace>>()
+ // .currently_dragged::<Channel>(cx.window())
+ // {
+ // if channel.id != dragged_channel.id {
+ // this.drag_target_channel = ChannelDragTarget::Channel(channel.id);
+ // }
+ // cx.notify()
+ // }
+ // }
+ // })
+ // .as_draggable::<_, Channel>(
+ // channel.clone(),
+ // move |_, channel, cx: &mut ViewContext<Workspace>| {
+ // let theme = &theme::current(cx).collab_panel;
+
+ // Flex::<Workspace>::row()
+ // .with_child(
+ // Svg::new("icons/hash.svg")
+ // .with_color(theme.channel_hash.color)
+ // .constrained()
+ // .with_width(theme.channel_hash.width)
+ // .aligned()
+ // .left(),
+ // )
+ // .with_child(
+ // Label::new(channel.name.clone(), theme.channel_name.text.clone())
+ // .contained()
+ // .with_style(theme.channel_name.container)
+ // .aligned()
+ // .left(),
+ // )
+ // .align_children_center()
+ // .contained()
+ // .with_background_color(
+ // theme
+ // .container
+ // .background_color
+ // .unwrap_or(gpui::color::Color::transparent_black()),
+ // )
+ // .contained()
+ // .with_padding_left(
+ // theme.channel_row.default_style().padding.left
+ // + theme.channel_indent * depth as f32,
+ // )
+ // .into_any()
+ // },
+ // )
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .into_any()
+ // }
+
+ // fn render_channel_notes(
+ // &self,
+ // channel_id: ChannelId,
+ // theme: &theme::CollabPanel,
+ // is_selected: bool,
+ // ix: usize,
+ // cx: &mut ViewContext<Self>,
+ // ) -> AnyElement<Self> {
+ // enum ChannelNotes {}
+ // let host_avatar_width = theme
+ // .contact_avatar
+ // .width
+ // .or(theme.contact_avatar.height)
+ // .unwrap_or(0.);
+
+ // MouseEventHandler::new::<ChannelNotes, _>(ix as usize, cx, |state, cx| {
+ // let tree_branch = *theme.tree_branch.in_state(is_selected).style_for(state);
+ // let row = theme.project_row.in_state(is_selected).style_for(state);
+
+ // Flex::<Self>::row()
+ // .with_child(render_tree_branch(
+ // tree_branch,
+ // &row.name.text,
+ // false,
+ // vec2f(host_avatar_width, theme.row_height),
+ // cx.font_cache(),
+ // ))
+ // .with_child(
+ // Svg::new("icons/file.svg")
+ // .with_color(theme.channel_hash.color)
+ // .constrained()
+ // .with_width(theme.channel_hash.width)
+ // .aligned()
+ // .left(),
+ // )
+ // .with_child(
+ // Label::new("notes", theme.channel_name.text.clone())
+ // .contained()
+ // .with_style(theme.channel_name.container)
+ // .aligned()
+ // .left()
+ // .flex(1., true),
+ // )
+ // .constrained()
+ // .with_height(theme.row_height)
+ // .contained()
+ // .with_style(*theme.channel_row.style_for(is_selected, state))
+ // .with_padding_left(theme.channel_row.default_style().padding.left)
+ // })
+ // .on_click(MouseButton::Left, move |_, this, cx| {
+ // this.open_channel_notes(&OpenChannelNotes { channel_id }, cx);
+ // })
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .into_any()
+ // }
+
+ // fn render_channel_chat(
+ // &self,
+ // channel_id: ChannelId,
+ // theme: &theme::CollabPanel,
+ // is_selected: bool,
+ // ix: usize,
+ // cx: &mut ViewContext<Self>,
+ // ) -> AnyElement<Self> {
+ // enum ChannelChat {}
+ // let host_avatar_width = theme
+ // .contact_avatar
+ // .width
+ // .or(theme.contact_avatar.height)
+ // .unwrap_or(0.);
+
+ // MouseEventHandler::new::<ChannelChat, _>(ix as usize, cx, |state, cx| {
+ // let tree_branch = *theme.tree_branch.in_state(is_selected).style_for(state);
+ // let row = theme.project_row.in_state(is_selected).style_for(state);
+
+ // Flex::<Self>::row()
+ // .with_child(render_tree_branch(
+ // tree_branch,
+ // &row.name.text,
+ // true,
+ // vec2f(host_avatar_width, theme.row_height),
+ // cx.font_cache(),
+ // ))
+ // .with_child(
+ // Svg::new("icons/conversations.svg")
+ // .with_color(theme.channel_hash.color)
+ // .constrained()
+ // .with_width(theme.channel_hash.width)
+ // .aligned()
+ // .left(),
+ // )
+ // .with_child(
+ // Label::new("chat", theme.channel_name.text.clone())
+ // .contained()
+ // .with_style(theme.channel_name.container)
+ // .aligned()
+ // .left()
+ // .flex(1., true),
+ // )
+ // .constrained()
+ // .with_height(theme.row_height)
+ // .contained()
+ // .with_style(*theme.channel_row.style_for(is_selected, state))
+ // .with_padding_left(theme.channel_row.default_style().padding.left)
+ // })
+ // .on_click(MouseButton::Left, move |_, this, cx| {
+ // this.join_channel_chat(&JoinChannelChat { channel_id }, cx);
+ // })
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .into_any()
+ // }
+
+ // fn render_channel_invite(
+ // channel: Arc<Channel>,
+ // channel_store: ModelHandle<ChannelStore>,
+ // theme: &theme::CollabPanel,
+ // is_selected: bool,
+ // cx: &mut ViewContext<Self>,
+ // ) -> AnyElement<Self> {
+ // enum Decline {}
+ // enum Accept {}
+
+ // let channel_id = channel.id;
+ // let is_invite_pending = channel_store
+ // .read(cx)
+ // .has_pending_channel_invite_response(&channel);
+ // let button_spacing = theme.contact_button_spacing;
+
+ // Flex::row()
+ // .with_child(
+ // Svg::new("icons/hash.svg")
+ // .with_color(theme.channel_hash.color)
+ // .constrained()
+ // .with_width(theme.channel_hash.width)
+ // .aligned()
+ // .left(),
+ // )
+ // .with_child(
+ // Label::new(channel.name.clone(), theme.contact_username.text.clone())
+ // .contained()
+ // .with_style(theme.contact_username.container)
+ // .aligned()
+ // .left()
+ // .flex(1., true),
+ // )
+ // .with_child(
+ // MouseEventHandler::new::<Decline, _>(channel.id as usize, cx, |mouse_state, _| {
+ // let button_style = if is_invite_pending {
+ // &theme.disabled_button
+ // } else {
+ // theme.contact_button.style_for(mouse_state)
+ // };
+ // render_icon_button(button_style, "icons/x.svg").aligned()
+ // })
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .on_click(MouseButton::Left, move |_, this, cx| {
+ // this.respond_to_channel_invite(channel_id, false, cx);
+ // })
+ // .contained()
+ // .with_margin_right(button_spacing),
+ // )
+ // .with_child(
+ // MouseEventHandler::new::<Accept, _>(channel.id as usize, cx, |mouse_state, _| {
+ // let button_style = if is_invite_pending {
+ // &theme.disabled_button
+ // } else {
+ // theme.contact_button.style_for(mouse_state)
+ // };
+ // render_icon_button(button_style, "icons/check.svg")
+ // .aligned()
+ // .flex_float()
+ // })
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .on_click(MouseButton::Left, move |_, this, cx| {
+ // this.respond_to_channel_invite(channel_id, true, cx);
+ // }),
+ // )
+ // .constrained()
+ // .with_height(theme.row_height)
+ // .contained()
+ // .with_style(
+ // *theme
+ // .contact_row
+ // .in_state(is_selected)
+ // .style_for(&mut Default::default()),
+ // )
+ // .with_padding_left(
+ // theme.contact_row.default_style().padding.left + theme.channel_indent,
+ // )
+ // .into_any()
+ // }
+
+ // fn render_contact_request(
+ // user: Arc<User>,
+ // user_store: ModelHandle<UserStore>,
+ // theme: &theme::CollabPanel,
+ // is_incoming: bool,
+ // is_selected: bool,
+ // cx: &mut ViewContext<Self>,
+ // ) -> AnyElement<Self> {
+ // enum Decline {}
+ // enum Accept {}
+ // enum Cancel {}
+
+ // let mut row = 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(),
+ // theme.contact_username.text.clone(),
+ // )
+ // .contained()
+ // .with_style(theme.contact_username.container)
+ // .aligned()
+ // .left()
+ // .flex(1., true),
+ // );
+
+ // let user_id = user.id;
+ // let github_login = user.github_login.clone();
+ // let is_contact_request_pending = user_store.read(cx).is_contact_request_pending(&user);
+ // let button_spacing = theme.contact_button_spacing;
+
+ // if is_incoming {
+ // row.add_child(
+ // MouseEventHandler::new::<Decline, _>(user.id as usize, cx, |mouse_state, _| {
+ // let button_style = if is_contact_request_pending {
+ // &theme.disabled_button
+ // } else {
+ // theme.contact_button.style_for(mouse_state)
+ // };
+ // render_icon_button(button_style, "icons/x.svg").aligned()
+ // })
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .on_click(MouseButton::Left, move |_, this, cx| {
+ // this.respond_to_contact_request(user_id, false, cx);
+ // })
+ // .contained()
+ // .with_margin_right(button_spacing),
+ // );
+
+ // row.add_child(
+ // MouseEventHandler::new::<Accept, _>(user.id as usize, cx, |mouse_state, _| {
+ // let button_style = if is_contact_request_pending {
+ // &theme.disabled_button
+ // } else {
+ // theme.contact_button.style_for(mouse_state)
+ // };
+ // render_icon_button(button_style, "icons/check.svg")
+ // .aligned()
+ // .flex_float()
+ // })
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .on_click(MouseButton::Left, move |_, this, cx| {
+ // this.respond_to_contact_request(user_id, true, cx);
+ // }),
+ // );
+ // } else {
+ // row.add_child(
+ // MouseEventHandler::new::<Cancel, _>(user.id as usize, cx, |mouse_state, _| {
+ // let button_style = if is_contact_request_pending {
+ // &theme.disabled_button
+ // } else {
+ // theme.contact_button.style_for(mouse_state)
+ // };
+ // render_icon_button(button_style, "icons/x.svg")
+ // .aligned()
+ // .flex_float()
+ // })
+ // .with_padding(Padding::uniform(2.))
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .on_click(MouseButton::Left, move |_, this, cx| {
+ // this.remove_contact(user_id, &github_login, cx);
+ // })
+ // .flex_float(),
+ // );
+ // }
+
+ // row.constrained()
+ // .with_height(theme.row_height)
+ // .contained()
+ // .with_style(
+ // *theme
+ // .contact_row
+ // .in_state(is_selected)
+ // .style_for(&mut Default::default()),
+ // )
+ // .into_any()
+ // }
+
+ // fn has_subchannels(&self, ix: usize) -> bool {
+ // self.entries.get(ix).map_or(false, |entry| {
+ // if let ListEntry::Channel { has_children, .. } = entry {
+ // *has_children
+ // } else {
+ // false
+ // }
+ // })
+ // }
+
+ // fn deploy_channel_context_menu(
+ // &mut self,
+ // position: Option<Vector2F>,
+ // channel: &Channel,
+ // ix: usize,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // self.context_menu_on_selected = position.is_none();
+
+ // let clipboard_channel_name = self.channel_clipboard.as_ref().and_then(|clipboard| {
+ // self.channel_store
+ // .read(cx)
+ // .channel_for_id(clipboard.channel_id)
+ // .map(|channel| channel.name.clone())
+ // });
+
+ // self.context_menu.update(cx, |context_menu, cx| {
+ // context_menu.set_position_mode(if self.context_menu_on_selected {
+ // OverlayPositionMode::Local
+ // } else {
+ // OverlayPositionMode::Window
+ // });
+
+ // let mut items = Vec::new();
+
+ // let select_action_name = if self.selection == Some(ix) {
+ // "Unselect"
+ // } else {
+ // "Select"
+ // };
+
+ // items.push(ContextMenuItem::action(
+ // select_action_name,
+ // ToggleSelectedIx { ix },
+ // ));
+
+ // if self.has_subchannels(ix) {
+ // let expand_action_name = if self.is_channel_collapsed(channel.id) {
+ // "Expand Subchannels"
+ // } else {
+ // "Collapse Subchannels"
+ // };
+ // items.push(ContextMenuItem::action(
+ // expand_action_name,
+ // ToggleCollapse {
+ // location: channel.id,
+ // },
+ // ));
+ // }
+
+ // items.push(ContextMenuItem::action(
+ // "Open Notes",
+ // OpenChannelNotes {
+ // channel_id: channel.id,
+ // },
+ // ));
+
+ // items.push(ContextMenuItem::action(
+ // "Open Chat",
+ // JoinChannelChat {
+ // channel_id: channel.id,
+ // },
+ // ));
+
+ // items.push(ContextMenuItem::action(
+ // "Copy Channel Link",
+ // CopyChannelLink {
+ // channel_id: channel.id,
+ // },
+ // ));
+
+ // if self.channel_store.read(cx).is_channel_admin(channel.id) {
+ // items.extend([
+ // ContextMenuItem::Separator,
+ // ContextMenuItem::action(
+ // "New Subchannel",
+ // NewChannel {
+ // location: channel.id,
+ // },
+ // ),
+ // ContextMenuItem::action(
+ // "Rename",
+ // RenameChannel {
+ // channel_id: channel.id,
+ // },
+ // ),
+ // ContextMenuItem::action(
+ // "Move this channel",
+ // StartMoveChannelFor {
+ // channel_id: channel.id,
+ // },
+ // ),
+ // ]);
+
+ // if let Some(channel_name) = clipboard_channel_name {
+ // items.push(ContextMenuItem::Separator);
+ // items.push(ContextMenuItem::action(
+ // format!("Move '#{}' here", channel_name),
+ // MoveChannel { to: channel.id },
+ // ));
+ // }
+
+ // items.extend([
+ // ContextMenuItem::Separator,
+ // ContextMenuItem::action(
+ // "Invite Members",
+ // InviteMembers {
+ // channel_id: channel.id,
+ // },
+ // ),
+ // ContextMenuItem::action(
+ // "Manage Members",
+ // ManageMembers {
+ // channel_id: channel.id,
+ // },
+ // ),
+ // ContextMenuItem::Separator,
+ // ContextMenuItem::action(
+ // "Delete",
+ // RemoveChannel {
+ // channel_id: channel.id,
+ // },
+ // ),
+ // ]);
+ // }
+
+ // context_menu.show(
+ // position.unwrap_or_default(),
+ // if self.context_menu_on_selected {
+ // gpui::elements::AnchorCorner::TopRight
+ // } else {
+ // gpui::elements::AnchorCorner::BottomLeft
+ // },
+ // items,
+ // cx,
+ // );
+ // });
+
+ // cx.notify();
+ // }
+
+ // fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
+ // if self.take_editing_state(cx) {
+ // cx.focus(&self.filter_editor);
+ // } else {
+ // self.filter_editor.update(cx, |editor, cx| {
+ // if editor.buffer().read(cx).len(cx) > 0 {
+ // editor.set_text("", cx);
+ // }
+ // });
+ // }
+
+ // self.update_entries(false, cx);
+ // }
+
+ // fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
+ // let ix = self.selection.map_or(0, |ix| ix + 1);
+ // if ix < self.entries.len() {
+ // self.selection = Some(ix);
+ // }
+
+ // self.list_state.reset(self.entries.len());
+ // if let Some(ix) = self.selection {
+ // self.list_state.scroll_to(ListOffset {
+ // item_ix: ix,
+ // offset_in_item: 0.,
+ // });
+ // }
+ // cx.notify();
+ // }
+
+ // fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
+ // let ix = self.selection.take().unwrap_or(0);
+ // if ix > 0 {
+ // self.selection = Some(ix - 1);
+ // }
+
+ // self.list_state.reset(self.entries.len());
+ // if let Some(ix) = self.selection {
+ // self.list_state.scroll_to(ListOffset {
+ // item_ix: ix,
+ // offset_in_item: 0.,
+ // });
+ // }
+ // cx.notify();
+ // }
+
+ // fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
+ // if self.confirm_channel_edit(cx) {
+ // return;
+ // }
+
+ // if let Some(selection) = self.selection {
+ // if let Some(entry) = self.entries.get(selection) {
+ // match entry {
+ // ListEntry::Header(section) => match section {
+ // Section::ActiveCall => Self::leave_call(cx),
+ // Section::Channels => self.new_root_channel(cx),
+ // Section::Contacts => self.toggle_contact_finder(cx),
+ // Section::ContactRequests
+ // | Section::Online
+ // | Section::Offline
+ // | Section::ChannelInvites => {
+ // self.toggle_section_expanded(*section, cx);
+ // }
+ // },
+ // ListEntry::Contact { contact, calling } => {
+ // if contact.online && !contact.busy && !calling {
+ // self.call(contact.user.id, Some(self.project.clone()), cx);
+ // }
+ // }
+ // ListEntry::ParticipantProject {
+ // project_id,
+ // host_user_id,
+ // ..
+ // } => {
+ // if let Some(workspace) = self.workspace.upgrade(cx) {
+ // let app_state = workspace.read(cx).app_state().clone();
+ // workspace::join_remote_project(
+ // *project_id,
+ // *host_user_id,
+ // app_state,
+ // cx,
+ // )
+ // .detach_and_log_err(cx);
+ // }
+ // }
+ // ListEntry::ParticipantScreen { peer_id, .. } => {
+ // let Some(peer_id) = peer_id else {
+ // return;
+ // };
+ // if let Some(workspace) = self.workspace.upgrade(cx) {
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.open_shared_screen(*peer_id, cx)
+ // });
+ // }
+ // }
+ // ListEntry::Channel { channel, .. } => {
+ // let is_active = maybe!({
+ // let call_channel = ActiveCall::global(cx)
+ // .read(cx)
+ // .room()?
+ // .read(cx)
+ // .channel_id()?;
+
+ // Some(call_channel == channel.id)
+ // })
+ // .unwrap_or(false);
+ // if is_active {
+ // self.open_channel_notes(
+ // &OpenChannelNotes {
+ // channel_id: channel.id,
+ // },
+ // cx,
+ // )
+ // } else {
+ // self.join_channel(channel.id, cx)
+ // }
+ // }
+ // ListEntry::ContactPlaceholder => self.toggle_contact_finder(cx),
+ // _ => {}
+ // }
+ // }
+ // }
+ // }
+
+ // fn insert_space(&mut self, _: &InsertSpace, cx: &mut ViewContext<Self>) {
+ // if self.channel_editing_state.is_some() {
+ // self.channel_name_editor.update(cx, |editor, cx| {
+ // editor.insert(" ", cx);
+ // });
+ // }
+ // }
+
+ // fn confirm_channel_edit(&mut self, cx: &mut ViewContext<CollabPanel>) -> bool {
+ // if let Some(editing_state) = &mut self.channel_editing_state {
+ // match editing_state {
+ // ChannelEditingState::Create {
+ // location,
+ // pending_name,
+ // ..
+ // } => {
+ // if pending_name.is_some() {
+ // return false;
+ // }
+ // let channel_name = self.channel_name_editor.read(cx).text(cx);
+
+ // *pending_name = Some(channel_name.clone());
+
+ // self.channel_store
+ // .update(cx, |channel_store, cx| {
+ // channel_store.create_channel(&channel_name, *location, cx)
+ // })
+ // .detach();
+ // cx.notify();
+ // }
+ // ChannelEditingState::Rename {
+ // location,
+ // pending_name,
+ // } => {
+ // if pending_name.is_some() {
+ // return false;
+ // }
+ // let channel_name = self.channel_name_editor.read(cx).text(cx);
+ // *pending_name = Some(channel_name.clone());
+
+ // self.channel_store
+ // .update(cx, |channel_store, cx| {
+ // channel_store.rename(*location, &channel_name, cx)
+ // })
+ // .detach();
+ // cx.notify();
+ // }
+ // }
+ // cx.focus_self();
+ // true
+ // } else {
+ // false
+ // }
+ // }
+
+ // fn toggle_section_expanded(&mut self, section: Section, cx: &mut ViewContext<Self>) {
+ // if let Some(ix) = self.collapsed_sections.iter().position(|s| *s == section) {
+ // self.collapsed_sections.remove(ix);
+ // } else {
+ // self.collapsed_sections.push(section);
+ // }
+ // self.update_entries(false, cx);
+ // }
+
+ // fn collapse_selected_channel(
+ // &mut self,
+ // _: &CollapseSelectedChannel,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // let Some(channel_id) = self.selected_channel().map(|channel| channel.id) else {
+ // return;
+ // };
+
+ // if self.is_channel_collapsed(channel_id) {
+ // return;
+ // }
+
+ // self.toggle_channel_collapsed(channel_id, cx);
+ // }
+
+ // fn expand_selected_channel(&mut self, _: &ExpandSelectedChannel, cx: &mut ViewContext<Self>) {
+ // let Some(id) = self.selected_channel().map(|channel| channel.id) else {
+ // return;
+ // };
+
+ // if !self.is_channel_collapsed(id) {
+ // return;
+ // }
+
+ // self.toggle_channel_collapsed(id, cx)
+ // }
+
+ // fn toggle_channel_collapsed_action(
+ // &mut self,
+ // action: &ToggleCollapse,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // self.toggle_channel_collapsed(action.location, cx);
+ // }
+
+ // fn toggle_channel_collapsed<'a>(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
+ // match self.collapsed_channels.binary_search(&channel_id) {
+ // Ok(ix) => {
+ // self.collapsed_channels.remove(ix);
+ // }
+ // Err(ix) => {
+ // self.collapsed_channels.insert(ix, channel_id);
+ // }
+ // };
+ // self.serialize(cx);
+ // self.update_entries(true, cx);
+ // cx.notify();
+ // cx.focus_self();
+ // }
+
+ // fn is_channel_collapsed(&self, channel_id: ChannelId) -> bool {
+ // self.collapsed_channels.binary_search(&channel_id).is_ok()
+ // }
+
+ // fn leave_call(cx: &mut ViewContext<Self>) {
+ // ActiveCall::global(cx)
+ // .update(cx, |call, cx| call.hang_up(cx))
+ // .detach_and_log_err(cx);
+ // }
+
+ // fn toggle_contact_finder(&mut self, cx: &mut ViewContext<Self>) {
+ // if let Some(workspace) = self.workspace.upgrade(cx) {
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.toggle_modal(cx, |_, cx| {
+ // cx.add_view(|cx| {
+ // let mut finder = ContactFinder::new(self.user_store.clone(), cx);
+ // finder.set_query(self.filter_editor.read(cx).text(cx), cx);
+ // finder
+ // })
+ // });
+ // });
+ // }
+ // }
+
+ // fn new_root_channel(&mut self, cx: &mut ViewContext<Self>) {
+ // self.channel_editing_state = Some(ChannelEditingState::Create {
+ // location: None,
+ // pending_name: None,
+ // });
+ // self.update_entries(false, cx);
+ // self.select_channel_editor();
+ // cx.focus(self.channel_name_editor.as_any());
+ // cx.notify();
+ // }
+
+ // fn select_channel_editor(&mut self) {
+ // self.selection = self.entries.iter().position(|entry| match entry {
+ // ListEntry::ChannelEditor { .. } => true,
+ // _ => false,
+ // });
+ // }
+
+ // fn new_subchannel(&mut self, action: &NewChannel, cx: &mut ViewContext<Self>) {
+ // self.collapsed_channels
+ // .retain(|channel| *channel != action.location);
+ // self.channel_editing_state = Some(ChannelEditingState::Create {
+ // location: Some(action.location.to_owned()),
+ // pending_name: None,
+ // });
+ // self.update_entries(false, cx);
+ // self.select_channel_editor();
+ // cx.focus(self.channel_name_editor.as_any());
+ // cx.notify();
+ // }
+
+ // fn invite_members(&mut self, action: &InviteMembers, cx: &mut ViewContext<Self>) {
+ // self.show_channel_modal(action.channel_id, channel_modal::Mode::InviteMembers, cx);
+ // }
+
+ // fn manage_members(&mut self, action: &ManageMembers, cx: &mut ViewContext<Self>) {
+ // self.show_channel_modal(action.channel_id, channel_modal::Mode::ManageMembers, cx);
+ // }
+
+ // fn remove(&mut self, _: &Remove, cx: &mut ViewContext<Self>) {
+ // if let Some(channel) = self.selected_channel() {
+ // self.remove_channel(channel.id, cx)
+ // }
+ // }
+
+ // fn rename_selected_channel(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
+ // if let Some(channel) = self.selected_channel() {
+ // self.rename_channel(
+ // &RenameChannel {
+ // channel_id: channel.id,
+ // },
+ // cx,
+ // );
+ // }
+ // }
+
+ // fn rename_channel(&mut self, action: &RenameChannel, cx: &mut ViewContext<Self>) {
+ // let channel_store = self.channel_store.read(cx);
+ // if !channel_store.is_channel_admin(action.channel_id) {
+ // return;
+ // }
+ // if let Some(channel) = channel_store.channel_for_id(action.channel_id).cloned() {
+ // self.channel_editing_state = Some(ChannelEditingState::Rename {
+ // location: action.channel_id.to_owned(),
+ // pending_name: None,
+ // });
+ // self.channel_name_editor.update(cx, |editor, cx| {
+ // editor.set_text(channel.name.clone(), cx);
+ // editor.select_all(&Default::default(), cx);
+ // });
+ // cx.focus(self.channel_name_editor.as_any());
+ // self.update_entries(false, cx);
+ // self.select_channel_editor();
+ // }
+ // }
+
+ // fn open_channel_notes(&mut self, action: &OpenChannelNotes, cx: &mut ViewContext<Self>) {
+ // if let Some(workspace) = self.workspace.upgrade(cx) {
+ // ChannelView::open(action.channel_id, workspace, cx).detach();
+ // }
+ // }
+
+ // fn show_inline_context_menu(&mut self, _: &menu::ShowContextMenu, cx: &mut ViewContext<Self>) {
+ // let Some(channel) = self.selected_channel() else {
+ // return;
+ // };
+
+ // self.deploy_channel_context_menu(None, &channel.clone(), self.selection.unwrap(), cx);
+ // }
+
+ // fn selected_channel(&self) -> Option<&Arc<Channel>> {
+ // self.selection
+ // .and_then(|ix| self.entries.get(ix))
+ // .and_then(|entry| match entry {
+ // ListEntry::Channel { channel, .. } => Some(channel),
+ // _ => None,
+ // })
+ // }
+
+ // fn show_channel_modal(
+ // &mut self,
+ // channel_id: ChannelId,
+ // mode: channel_modal::Mode,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // let workspace = self.workspace.clone();
+ // let user_store = self.user_store.clone();
+ // let channel_store = self.channel_store.clone();
+ // let members = self.channel_store.update(cx, |channel_store, cx| {
+ // channel_store.get_channel_member_details(channel_id, cx)
+ // });
+
+ // cx.spawn(|_, mut cx| async move {
+ // let members = members.await?;
+ // workspace.update(&mut cx, |workspace, cx| {
+ // workspace.toggle_modal(cx, |_, cx| {
+ // cx.add_view(|cx| {
+ // ChannelModal::new(
+ // user_store.clone(),
+ // channel_store.clone(),
+ // channel_id,
+ // mode,
+ // members,
+ // cx,
+ // )
+ // })
+ // });
+ // })
+ // })
+ // .detach();
+ // }
+
+ // fn remove_selected_channel(&mut self, action: &RemoveChannel, cx: &mut ViewContext<Self>) {
+ // self.remove_channel(action.channel_id, cx)
+ // }
+
+ // fn remove_channel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
+ // let channel_store = self.channel_store.clone();
+ // if let Some(channel) = channel_store.read(cx).channel_for_id(channel_id) {
+ // let prompt_message = format!(
+ // "Are you sure you want to remove the channel \"{}\"?",
+ // channel.name
+ // );
+ // let mut answer =
+ // cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
+ // let window = cx.window();
+ // cx.spawn(|this, mut cx| async move {
+ // if answer.next().await == Some(0) {
+ // if let Err(e) = channel_store
+ // .update(&mut cx, |channels, _| channels.remove_channel(channel_id))
+ // .await
+ // {
+ // window.prompt(
+ // PromptLevel::Info,
+ // &format!("Failed to remove channel: {}", e),
+ // &["Ok"],
+ // &mut cx,
+ // );
+ // }
+ // this.update(&mut cx, |_, cx| cx.focus_self()).ok();
+ // }
+ // })
+ // .detach();
+ // }
+ // }
+
+ // // Should move to the filter editor if clicking on it
+ // // Should move selection to the channel editor if activating it
+
+ // fn remove_contact(&mut self, user_id: u64, github_login: &str, cx: &mut ViewContext<Self>) {
+ // let user_store = self.user_store.clone();
+ // let prompt_message = format!(
+ // "Are you sure you want to remove \"{}\" from your contacts?",
+ // github_login
+ // );
+ // let mut answer = cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
+ // let window = cx.window();
+ // cx.spawn(|_, mut cx| async move {
+ // if answer.next().await == Some(0) {
+ // if let Err(e) = user_store
+ // .update(&mut cx, |store, cx| store.remove_contact(user_id, cx))
+ // .await
+ // {
+ // window.prompt(
+ // PromptLevel::Info,
+ // &format!("Failed to remove contact: {}", e),
+ // &["Ok"],
+ // &mut cx,
+ // );
+ // }
+ // }
+ // })
+ // .detach();
+ // }
+
+ // fn respond_to_contact_request(
+ // &mut self,
+ // user_id: u64,
+ // accept: bool,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // self.user_store
+ // .update(cx, |store, cx| {
+ // store.respond_to_contact_request(user_id, accept, cx)
+ // })
+ // .detach();
+ // }
+
+ // fn respond_to_channel_invite(
+ // &mut self,
+ // channel_id: u64,
+ // accept: bool,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // self.channel_store
+ // .update(cx, |store, cx| {
+ // store.respond_to_channel_invite(channel_id, accept, cx)
+ // })
+ // .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 join_channel(&self, channel_id: u64, cx: &mut ViewContext<Self>) {
+ // let Some(workspace) = self.workspace.upgrade(cx) else {
+ // return;
+ // };
+ // let Some(handle) = cx.window().downcast::<Workspace>() else {
+ // return;
+ // };
+ // workspace::join_channel(
+ // channel_id,
+ // workspace.read(cx).app_state().clone(),
+ // Some(handle),
+ // cx,
+ // )
+ // .detach_and_log_err(cx)
+ // }
+
+ // fn join_channel_chat(&mut self, action: &JoinChannelChat, cx: &mut ViewContext<Self>) {
+ // let channel_id = action.channel_id;
+ // if let Some(workspace) = self.workspace.upgrade(cx) {
+ // cx.app_context().defer(move |cx| {
+ // workspace.update(cx, |workspace, cx| {
+ // if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
+ // panel.update(cx, |panel, cx| {
+ // panel
+ // .select_channel(channel_id, None, cx)
+ // .detach_and_log_err(cx);
+ // });
+ // }
+ // });
+ // });
+ // }
+ // }
+
+ // fn copy_channel_link(&mut self, action: &CopyChannelLink, cx: &mut ViewContext<Self>) {
+ // let channel_store = self.channel_store.read(cx);
+ // let Some(channel) = channel_store.channel_for_id(action.channel_id) else {
+ // return;
+ // };
+ // let item = ClipboardItem::new(channel.link());
+ // cx.write_to_clipboard(item)
+ // }
+}
+
// fn render_tree_branch(
// branch_style: theme::TreeBranch,
// row_style: &TextStyle,