collab_panel.rs

   1// mod channel_modal;
   2// mod contact_finder;
   3
   4// use crate::{
   5//     channel_view::{self, ChannelView},
   6//     chat_panel::ChatPanel,
   7//     face_pile::FacePile,
   8//     panel_settings, CollaborationPanelSettings,
   9// };
  10// use anyhow::Result;
  11// use call::ActiveCall;
  12// use channel::{Channel, ChannelEvent, ChannelId, ChannelStore};
  13// use channel_modal::ChannelModal;
  14// use client::{
  15//     proto::{self, PeerId},
  16//     Client, Contact, User, UserStore,
  17// };
  18// use contact_finder::ContactFinder;
  19// use context_menu::{ContextMenu, ContextMenuItem};
  20// use db::kvp::KEY_VALUE_STORE;
  21// use drag_and_drop::{DragAndDrop, Draggable};
  22// use editor::{Cancel, Editor};
  23// use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt};
  24// use futures::StreamExt;
  25// use fuzzy::{match_strings, StringMatchCandidate};
  26// use gpui::{
  27//     actions,
  28//     elements::{
  29//         Canvas, ChildView, Component, ContainerStyle, Empty, Flex, Image, Label, List, ListOffset,
  30//         ListState, MouseEventHandler, Orientation, OverlayPositionMode, Padding, ParentElement,
  31//         SafeStylable, Stack, Svg,
  32//     },
  33//     fonts::TextStyle,
  34//     geometry::{
  35//         rect::RectF,
  36//         vector::{vec2f, Vector2F},
  37//     },
  38//     impl_actions,
  39//     platform::{CursorStyle, MouseButton, PromptLevel},
  40//     serde_json, AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, FontCache,
  41//     ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
  42// };
  43// use menu::{Confirm, SelectNext, SelectPrev};
  44// use project::{Fs, Project};
  45// use serde_derive::{Deserialize, Serialize};
  46// use settings::SettingsStore;
  47// use std::{borrow::Cow, hash::Hash, mem, sync::Arc};
  48// use theme::{components::ComponentExt, IconButton, Interactive};
  49// use util::{maybe, ResultExt, TryFutureExt};
  50// use workspace::{
  51//     dock::{DockPosition, Panel},
  52//     item::ItemHandle,
  53//     FollowNextCollaborator, Workspace,
  54// };
  55
  56// #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
  57// struct ToggleCollapse {
  58//     location: ChannelId,
  59// }
  60
  61// #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
  62// struct NewChannel {
  63//     location: ChannelId,
  64// }
  65
  66// #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
  67// struct RenameChannel {
  68//     channel_id: ChannelId,
  69// }
  70
  71// #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
  72// struct ToggleSelectedIx {
  73//     ix: usize,
  74// }
  75
  76// #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
  77// struct RemoveChannel {
  78//     channel_id: ChannelId,
  79// }
  80
  81// #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
  82// struct InviteMembers {
  83//     channel_id: ChannelId,
  84// }
  85
  86// #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
  87// struct ManageMembers {
  88//     channel_id: ChannelId,
  89// }
  90
  91// #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
  92// pub struct OpenChannelNotes {
  93//     pub channel_id: ChannelId,
  94// }
  95
  96// #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
  97// pub struct JoinChannelCall {
  98//     pub channel_id: u64,
  99// }
 100
 101// #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 102// pub struct JoinChannelChat {
 103//     pub channel_id: u64,
 104// }
 105
 106// #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 107// pub struct CopyChannelLink {
 108//     pub channel_id: u64,
 109// }
 110
 111// #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 112// struct StartMoveChannelFor {
 113//     channel_id: ChannelId,
 114// }
 115
 116// #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 117// struct MoveChannel {
 118//     to: ChannelId,
 119// }
 120
 121// actions!(
 122//     collab_panel,
 123//     [
 124//         ToggleFocus,
 125//         Remove,
 126//         Secondary,
 127//         CollapseSelectedChannel,
 128//         ExpandSelectedChannel,
 129//         StartMoveChannel,
 130//         MoveSelected,
 131//         InsertSpace,
 132//     ]
 133// );
 134
 135// impl_actions!(
 136//     collab_panel,
 137//     [
 138//         RemoveChannel,
 139//         NewChannel,
 140//         InviteMembers,
 141//         ManageMembers,
 142//         RenameChannel,
 143//         ToggleCollapse,
 144//         OpenChannelNotes,
 145//         JoinChannelCall,
 146//         JoinChannelChat,
 147//         CopyChannelLink,
 148//         StartMoveChannelFor,
 149//         MoveChannel,
 150//         ToggleSelectedIx
 151//     ]
 152// );
 153
 154// #[derive(Debug, Copy, Clone, PartialEq, Eq)]
 155// struct ChannelMoveClipboard {
 156//     channel_id: ChannelId,
 157// }
 158
 159// const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
 160
 161// pub fn init(cx: &mut AppContext) {
 162//     settings::register::<panel_settings::CollaborationPanelSettings>(cx);
 163//     contact_finder::init(cx);
 164//     channel_modal::init(cx);
 165//     channel_view::init(cx);
 166
 167//     cx.add_action(CollabPanel::cancel);
 168//     cx.add_action(CollabPanel::select_next);
 169//     cx.add_action(CollabPanel::select_prev);
 170//     cx.add_action(CollabPanel::confirm);
 171//     cx.add_action(CollabPanel::insert_space);
 172//     cx.add_action(CollabPanel::remove);
 173//     cx.add_action(CollabPanel::remove_selected_channel);
 174//     cx.add_action(CollabPanel::show_inline_context_menu);
 175//     cx.add_action(CollabPanel::new_subchannel);
 176//     cx.add_action(CollabPanel::invite_members);
 177//     cx.add_action(CollabPanel::manage_members);
 178//     cx.add_action(CollabPanel::rename_selected_channel);
 179//     cx.add_action(CollabPanel::rename_channel);
 180//     cx.add_action(CollabPanel::toggle_channel_collapsed_action);
 181//     cx.add_action(CollabPanel::collapse_selected_channel);
 182//     cx.add_action(CollabPanel::expand_selected_channel);
 183//     cx.add_action(CollabPanel::open_channel_notes);
 184//     cx.add_action(CollabPanel::join_channel_chat);
 185//     cx.add_action(CollabPanel::copy_channel_link);
 186
 187//     cx.add_action(
 188//         |panel: &mut CollabPanel, action: &ToggleSelectedIx, cx: &mut ViewContext<CollabPanel>| {
 189//             if panel.selection.take() != Some(action.ix) {
 190//                 panel.selection = Some(action.ix)
 191//             }
 192
 193//             cx.notify();
 194//         },
 195//     );
 196
 197//     cx.add_action(
 198//         |panel: &mut CollabPanel,
 199//          action: &StartMoveChannelFor,
 200//          _: &mut ViewContext<CollabPanel>| {
 201//             panel.channel_clipboard = Some(ChannelMoveClipboard {
 202//                 channel_id: action.channel_id,
 203//             });
 204//         },
 205//     );
 206
 207//     cx.add_action(
 208//         |panel: &mut CollabPanel, _: &StartMoveChannel, _: &mut ViewContext<CollabPanel>| {
 209//             if let Some(channel) = panel.selected_channel() {
 210//                 panel.channel_clipboard = Some(ChannelMoveClipboard {
 211//                     channel_id: channel.id,
 212//                 })
 213//             }
 214//         },
 215//     );
 216
 217//     cx.add_action(
 218//         |panel: &mut CollabPanel, _: &MoveSelected, cx: &mut ViewContext<CollabPanel>| {
 219//             let Some(clipboard) = panel.channel_clipboard.take() else {
 220//                 return;
 221//             };
 222//             let Some(selected_channel) = panel.selected_channel() else {
 223//                 return;
 224//             };
 225
 226//             panel
 227//                 .channel_store
 228//                 .update(cx, |channel_store, cx| {
 229//                     channel_store.move_channel(clipboard.channel_id, Some(selected_channel.id), cx)
 230//                 })
 231//                 .detach_and_log_err(cx)
 232//         },
 233//     );
 234
 235//     cx.add_action(
 236//         |panel: &mut CollabPanel, action: &MoveChannel, cx: &mut ViewContext<CollabPanel>| {
 237//             if let Some(clipboard) = panel.channel_clipboard.take() {
 238//                 panel.channel_store.update(cx, |channel_store, cx| {
 239//                     channel_store
 240//                         .move_channel(clipboard.channel_id, Some(action.to), cx)
 241//                         .detach_and_log_err(cx)
 242//                 })
 243//             }
 244//         },
 245//     );
 246// }
 247
 248// #[derive(Debug)]
 249// pub enum ChannelEditingState {
 250//     Create {
 251//         location: Option<ChannelId>,
 252//         pending_name: Option<String>,
 253//     },
 254//     Rename {
 255//         location: ChannelId,
 256//         pending_name: Option<String>,
 257//     },
 258// }
 259
 260// impl ChannelEditingState {
 261//     fn pending_name(&self) -> Option<&str> {
 262//         match self {
 263//             ChannelEditingState::Create { pending_name, .. } => pending_name.as_deref(),
 264//             ChannelEditingState::Rename { pending_name, .. } => pending_name.as_deref(),
 265//         }
 266//     }
 267// }
 268
 269// pub struct CollabPanel {
 270//     width: Option<f32>,
 271//     fs: Arc<dyn Fs>,
 272//     has_focus: bool,
 273//     channel_clipboard: Option<ChannelMoveClipboard>,
 274//     pending_serialization: Task<Option<()>>,
 275//     context_menu: ViewHandle<ContextMenu>,
 276//     filter_editor: ViewHandle<Editor>,
 277//     channel_name_editor: ViewHandle<Editor>,
 278//     channel_editing_state: Option<ChannelEditingState>,
 279//     entries: Vec<ListEntry>,
 280//     selection: Option<usize>,
 281//     user_store: ModelHandle<UserStore>,
 282//     client: Arc<Client>,
 283//     channel_store: ModelHandle<ChannelStore>,
 284//     project: ModelHandle<Project>,
 285//     match_candidates: Vec<StringMatchCandidate>,
 286//     list_state: ListState<Self>,
 287//     subscriptions: Vec<Subscription>,
 288//     collapsed_sections: Vec<Section>,
 289//     collapsed_channels: Vec<ChannelId>,
 290//     drag_target_channel: ChannelDragTarget,
 291//     workspace: WeakViewHandle<Workspace>,
 292//     context_menu_on_selected: bool,
 293// }
 294
 295// #[derive(PartialEq, Eq)]
 296// enum ChannelDragTarget {
 297//     None,
 298//     Root,
 299//     Channel(ChannelId),
 300// }
 301
 302// #[derive(Serialize, Deserialize)]
 303// struct SerializedCollabPanel {
 304//     width: Option<f32>,
 305//     collapsed_channels: Option<Vec<ChannelId>>,
 306// }
 307
 308// #[derive(Debug)]
 309// pub enum Event {
 310//     DockPositionChanged,
 311//     Focus,
 312//     Dismissed,
 313// }
 314
 315// #[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)]
 316// enum Section {
 317//     ActiveCall,
 318//     Channels,
 319//     ChannelInvites,
 320//     ContactRequests,
 321//     Contacts,
 322//     Online,
 323//     Offline,
 324// }
 325
 326// #[derive(Clone, Debug)]
 327// enum ListEntry {
 328//     Header(Section),
 329//     CallParticipant {
 330//         user: Arc<User>,
 331//         peer_id: Option<PeerId>,
 332//         is_pending: bool,
 333//     },
 334//     ParticipantProject {
 335//         project_id: u64,
 336//         worktree_root_names: Vec<String>,
 337//         host_user_id: u64,
 338//         is_last: bool,
 339//     },
 340//     ParticipantScreen {
 341//         peer_id: Option<PeerId>,
 342//         is_last: bool,
 343//     },
 344//     IncomingRequest(Arc<User>),
 345//     OutgoingRequest(Arc<User>),
 346//     ChannelInvite(Arc<Channel>),
 347//     Channel {
 348//         channel: Arc<Channel>,
 349//         depth: usize,
 350//         has_children: bool,
 351//     },
 352//     ChannelNotes {
 353//         channel_id: ChannelId,
 354//     },
 355//     ChannelChat {
 356//         channel_id: ChannelId,
 357//     },
 358//     ChannelEditor {
 359//         depth: usize,
 360//     },
 361//     Contact {
 362//         contact: Arc<Contact>,
 363//         calling: bool,
 364//     },
 365//     ContactPlaceholder,
 366// }
 367
 368// impl Entity for CollabPanel {
 369//     type Event = Event;
 370// }
 371
 372// impl CollabPanel {
 373//     pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> ViewHandle<Self> {
 374//         cx.add_view::<Self, _>(|cx| {
 375//             let view_id = cx.view_id();
 376
 377//             let filter_editor = cx.add_view(|cx| {
 378//                 let mut editor = Editor::single_line(
 379//                     Some(Arc::new(|theme| {
 380//                         theme.collab_panel.user_query_editor.clone()
 381//                     })),
 382//                     cx,
 383//                 );
 384//                 editor.set_placeholder_text("Filter channels, contacts", cx);
 385//                 editor
 386//             });
 387
 388//             cx.subscribe(&filter_editor, |this, _, event, cx| {
 389//                 if let editor::Event::BufferEdited = event {
 390//                     let query = this.filter_editor.read(cx).text(cx);
 391//                     if !query.is_empty() {
 392//                         this.selection.take();
 393//                     }
 394//                     this.update_entries(true, cx);
 395//                     if !query.is_empty() {
 396//                         this.selection = this
 397//                             .entries
 398//                             .iter()
 399//                             .position(|entry| !matches!(entry, ListEntry::Header(_)));
 400//                     }
 401//                 } else if let editor::Event::Blurred = event {
 402//                     let query = this.filter_editor.read(cx).text(cx);
 403//                     if query.is_empty() {
 404//                         this.selection.take();
 405//                         this.update_entries(true, cx);
 406//                     }
 407//                 }
 408//             })
 409//             .detach();
 410
 411//             let channel_name_editor = cx.add_view(|cx| {
 412//                 Editor::single_line(
 413//                     Some(Arc::new(|theme| {
 414//                         theme.collab_panel.user_query_editor.clone()
 415//                     })),
 416//                     cx,
 417//                 )
 418//             });
 419
 420//             cx.subscribe(&channel_name_editor, |this, _, event, cx| {
 421//                 if let editor::Event::Blurred = event {
 422//                     if let Some(state) = &this.channel_editing_state {
 423//                         if state.pending_name().is_some() {
 424//                             return;
 425//                         }
 426//                     }
 427//                     this.take_editing_state(cx);
 428//                     this.update_entries(false, cx);
 429//                     cx.notify();
 430//                 }
 431//             })
 432//             .detach();
 433
 434//             let list_state =
 435//                 ListState::<Self>::new(0, Orientation::Top, 1000., move |this, ix, cx| {
 436//                     let theme = theme::current(cx).clone();
 437//                     let is_selected = this.selection == Some(ix);
 438//                     let current_project_id = this.project.read(cx).remote_id();
 439
 440//                     match &this.entries[ix] {
 441//                         ListEntry::Header(section) => {
 442//                             let is_collapsed = this.collapsed_sections.contains(section);
 443//                             this.render_header(*section, &theme, is_selected, is_collapsed, cx)
 444//                         }
 445//                         ListEntry::CallParticipant {
 446//                             user,
 447//                             peer_id,
 448//                             is_pending,
 449//                         } => Self::render_call_participant(
 450//                             user,
 451//                             *peer_id,
 452//                             this.user_store.clone(),
 453//                             *is_pending,
 454//                             is_selected,
 455//                             &theme,
 456//                             cx,
 457//                         ),
 458//                         ListEntry::ParticipantProject {
 459//                             project_id,
 460//                             worktree_root_names,
 461//                             host_user_id,
 462//                             is_last,
 463//                         } => Self::render_participant_project(
 464//                             *project_id,
 465//                             worktree_root_names,
 466//                             *host_user_id,
 467//                             Some(*project_id) == current_project_id,
 468//                             *is_last,
 469//                             is_selected,
 470//                             &theme,
 471//                             cx,
 472//                         ),
 473//                         ListEntry::ParticipantScreen { peer_id, is_last } => {
 474//                             Self::render_participant_screen(
 475//                                 *peer_id,
 476//                                 *is_last,
 477//                                 is_selected,
 478//                                 &theme.collab_panel,
 479//                                 cx,
 480//                             )
 481//                         }
 482//                         ListEntry::Channel {
 483//                             channel,
 484//                             depth,
 485//                             has_children,
 486//                         } => {
 487//                             let channel_row = this.render_channel(
 488//                                 &*channel,
 489//                                 *depth,
 490//                                 &theme,
 491//                                 is_selected,
 492//                                 *has_children,
 493//                                 ix,
 494//                                 cx,
 495//                             );
 496
 497//                             if is_selected && this.context_menu_on_selected {
 498//                                 Stack::new()
 499//                                     .with_child(channel_row)
 500//                                     .with_child(
 501//                                         ChildView::new(&this.context_menu, cx)
 502//                                             .aligned()
 503//                                             .bottom()
 504//                                             .right(),
 505//                                     )
 506//                                     .into_any()
 507//                             } else {
 508//                                 return channel_row;
 509//                             }
 510//                         }
 511//                         ListEntry::ChannelNotes { channel_id } => this.render_channel_notes(
 512//                             *channel_id,
 513//                             &theme.collab_panel,
 514//                             is_selected,
 515//                             ix,
 516//                             cx,
 517//                         ),
 518//                         ListEntry::ChannelChat { channel_id } => this.render_channel_chat(
 519//                             *channel_id,
 520//                             &theme.collab_panel,
 521//                             is_selected,
 522//                             ix,
 523//                             cx,
 524//                         ),
 525//                         ListEntry::ChannelInvite(channel) => Self::render_channel_invite(
 526//                             channel.clone(),
 527//                             this.channel_store.clone(),
 528//                             &theme.collab_panel,
 529//                             is_selected,
 530//                             cx,
 531//                         ),
 532//                         ListEntry::IncomingRequest(user) => Self::render_contact_request(
 533//                             user.clone(),
 534//                             this.user_store.clone(),
 535//                             &theme.collab_panel,
 536//                             true,
 537//                             is_selected,
 538//                             cx,
 539//                         ),
 540//                         ListEntry::OutgoingRequest(user) => Self::render_contact_request(
 541//                             user.clone(),
 542//                             this.user_store.clone(),
 543//                             &theme.collab_panel,
 544//                             false,
 545//                             is_selected,
 546//                             cx,
 547//                         ),
 548//                         ListEntry::Contact { contact, calling } => Self::render_contact(
 549//                             contact,
 550//                             *calling,
 551//                             &this.project,
 552//                             &theme,
 553//                             is_selected,
 554//                             cx,
 555//                         ),
 556//                         ListEntry::ChannelEditor { depth } => {
 557//                             this.render_channel_editor(&theme, *depth, cx)
 558//                         }
 559//                         ListEntry::ContactPlaceholder => {
 560//                             this.render_contact_placeholder(&theme.collab_panel, is_selected, cx)
 561//                         }
 562//                     }
 563//                 });
 564
 565//             let mut this = Self {
 566//                 width: None,
 567//                 has_focus: false,
 568//                 channel_clipboard: None,
 569//                 fs: workspace.app_state().fs.clone(),
 570//                 pending_serialization: Task::ready(None),
 571//                 context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
 572//                 channel_name_editor,
 573//                 filter_editor,
 574//                 entries: Vec::default(),
 575//                 channel_editing_state: None,
 576//                 selection: None,
 577//                 user_store: workspace.user_store().clone(),
 578//                 channel_store: ChannelStore::global(cx),
 579//                 project: workspace.project().clone(),
 580//                 subscriptions: Vec::default(),
 581//                 match_candidates: Vec::default(),
 582//                 collapsed_sections: vec![Section::Offline],
 583//                 collapsed_channels: Vec::default(),
 584//                 workspace: workspace.weak_handle(),
 585//                 client: workspace.app_state().client.clone(),
 586//                 context_menu_on_selected: true,
 587//                 drag_target_channel: ChannelDragTarget::None,
 588//                 list_state,
 589//             };
 590
 591//             this.update_entries(false, cx);
 592
 593//             // Update the dock position when the setting changes.
 594//             let mut old_dock_position = this.position(cx);
 595//             this.subscriptions
 596//                 .push(
 597//                     cx.observe_global::<SettingsStore, _>(move |this: &mut Self, cx| {
 598//                         let new_dock_position = this.position(cx);
 599//                         if new_dock_position != old_dock_position {
 600//                             old_dock_position = new_dock_position;
 601//                             cx.emit(Event::DockPositionChanged);
 602//                         }
 603//                         cx.notify();
 604//                     }),
 605//                 );
 606
 607//             let active_call = ActiveCall::global(cx);
 608//             this.subscriptions
 609//                 .push(cx.observe(&this.user_store, |this, _, cx| {
 610//                     this.update_entries(true, cx)
 611//                 }));
 612//             this.subscriptions
 613//                 .push(cx.observe(&this.channel_store, |this, _, cx| {
 614//                     this.update_entries(true, cx)
 615//                 }));
 616//             this.subscriptions
 617//                 .push(cx.observe(&active_call, |this, _, cx| this.update_entries(true, cx)));
 618//             this.subscriptions
 619//                 .push(cx.observe_flag::<ChannelsAlpha, _>(move |_, this, cx| {
 620//                     this.update_entries(true, cx)
 621//                 }));
 622//             this.subscriptions.push(cx.subscribe(
 623//                 &this.channel_store,
 624//                 |this, _channel_store, e, cx| match e {
 625//                     ChannelEvent::ChannelCreated(channel_id)
 626//                     | ChannelEvent::ChannelRenamed(channel_id) => {
 627//                         if this.take_editing_state(cx) {
 628//                             this.update_entries(false, cx);
 629//                             this.selection = this.entries.iter().position(|entry| {
 630//                                 if let ListEntry::Channel { channel, .. } = entry {
 631//                                     channel.id == *channel_id
 632//                                 } else {
 633//                                     false
 634//                                 }
 635//                             });
 636//                         }
 637//                     }
 638//                 },
 639//             ));
 640
 641//             this
 642//         })
 643//     }
 644
 645//     pub fn load(
 646//         workspace: WeakViewHandle<Workspace>,
 647//         cx: AsyncAppContext,
 648//     ) -> Task<Result<ViewHandle<Self>>> {
 649//         cx.spawn(|mut cx| async move {
 650//             let serialized_panel = if let Some(panel) = cx
 651//                 .background()
 652//                 .spawn(async move { KEY_VALUE_STORE.read_kvp(COLLABORATION_PANEL_KEY) })
 653//                 .await
 654//                 .log_err()
 655//                 .flatten()
 656//             {
 657//                 match serde_json::from_str::<SerializedCollabPanel>(&panel) {
 658//                     Ok(panel) => Some(panel),
 659//                     Err(err) => {
 660//                         log::error!("Failed to deserialize collaboration panel: {}", err);
 661//                         None
 662//                     }
 663//                 }
 664//             } else {
 665//                 None
 666//             };
 667
 668//             workspace.update(&mut cx, |workspace, cx| {
 669//                 let panel = CollabPanel::new(workspace, cx);
 670//                 if let Some(serialized_panel) = serialized_panel {
 671//                     panel.update(cx, |panel, cx| {
 672//                         panel.width = serialized_panel.width;
 673//                         panel.collapsed_channels = serialized_panel
 674//                             .collapsed_channels
 675//                             .unwrap_or_else(|| Vec::new());
 676//                         cx.notify();
 677//                     });
 678//                 }
 679//                 panel
 680//             })
 681//         })
 682//     }
 683
 684//     fn serialize(&mut self, cx: &mut ViewContext<Self>) {
 685//         let width = self.width;
 686//         let collapsed_channels = self.collapsed_channels.clone();
 687//         self.pending_serialization = cx.background().spawn(
 688//             async move {
 689//                 KEY_VALUE_STORE
 690//                     .write_kvp(
 691//                         COLLABORATION_PANEL_KEY.into(),
 692//                         serde_json::to_string(&SerializedCollabPanel {
 693//                             width,
 694//                             collapsed_channels: Some(collapsed_channels),
 695//                         })?,
 696//                     )
 697//                     .await?;
 698//                 anyhow::Ok(())
 699//             }
 700//             .log_err(),
 701//         );
 702//     }
 703
 704//     fn update_entries(&mut self, select_same_item: bool, cx: &mut ViewContext<Self>) {
 705//         let channel_store = self.channel_store.read(cx);
 706//         let user_store = self.user_store.read(cx);
 707//         let query = self.filter_editor.read(cx).text(cx);
 708//         let executor = cx.background().clone();
 709
 710//         let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned());
 711//         let old_entries = mem::take(&mut self.entries);
 712//         let mut scroll_to_top = false;
 713
 714//         if let Some(room) = ActiveCall::global(cx).read(cx).room() {
 715//             self.entries.push(ListEntry::Header(Section::ActiveCall));
 716//             if !old_entries
 717//                 .iter()
 718//                 .any(|entry| matches!(entry, ListEntry::Header(Section::ActiveCall)))
 719//             {
 720//                 scroll_to_top = true;
 721//             }
 722
 723//             if !self.collapsed_sections.contains(&Section::ActiveCall) {
 724//                 let room = room.read(cx);
 725
 726//                 if let Some(channel_id) = room.channel_id() {
 727//                     self.entries.push(ListEntry::ChannelNotes { channel_id });
 728//                     self.entries.push(ListEntry::ChannelChat { channel_id })
 729//                 }
 730
 731//                 // Populate the active user.
 732//                 if let Some(user) = user_store.current_user() {
 733//                     self.match_candidates.clear();
 734//                     self.match_candidates.push(StringMatchCandidate {
 735//                         id: 0,
 736//                         string: user.github_login.clone(),
 737//                         char_bag: user.github_login.chars().collect(),
 738//                     });
 739//                     let matches = executor.block(match_strings(
 740//                         &self.match_candidates,
 741//                         &query,
 742//                         true,
 743//                         usize::MAX,
 744//                         &Default::default(),
 745//                         executor.clone(),
 746//                     ));
 747//                     if !matches.is_empty() {
 748//                         let user_id = user.id;
 749//                         self.entries.push(ListEntry::CallParticipant {
 750//                             user,
 751//                             peer_id: None,
 752//                             is_pending: false,
 753//                         });
 754//                         let mut projects = room.local_participant().projects.iter().peekable();
 755//                         while let Some(project) = projects.next() {
 756//                             self.entries.push(ListEntry::ParticipantProject {
 757//                                 project_id: project.id,
 758//                                 worktree_root_names: project.worktree_root_names.clone(),
 759//                                 host_user_id: user_id,
 760//                                 is_last: projects.peek().is_none() && !room.is_screen_sharing(),
 761//                             });
 762//                         }
 763//                         if room.is_screen_sharing() {
 764//                             self.entries.push(ListEntry::ParticipantScreen {
 765//                                 peer_id: None,
 766//                                 is_last: true,
 767//                             });
 768//                         }
 769//                     }
 770//                 }
 771
 772//                 // Populate remote participants.
 773//                 self.match_candidates.clear();
 774//                 self.match_candidates
 775//                     .extend(room.remote_participants().iter().map(|(_, participant)| {
 776//                         StringMatchCandidate {
 777//                             id: participant.user.id as usize,
 778//                             string: participant.user.github_login.clone(),
 779//                             char_bag: participant.user.github_login.chars().collect(),
 780//                         }
 781//                     }));
 782//                 let matches = executor.block(match_strings(
 783//                     &self.match_candidates,
 784//                     &query,
 785//                     true,
 786//                     usize::MAX,
 787//                     &Default::default(),
 788//                     executor.clone(),
 789//                 ));
 790//                 for mat in matches {
 791//                     let user_id = mat.candidate_id as u64;
 792//                     let participant = &room.remote_participants()[&user_id];
 793//                     self.entries.push(ListEntry::CallParticipant {
 794//                         user: participant.user.clone(),
 795//                         peer_id: Some(participant.peer_id),
 796//                         is_pending: false,
 797//                     });
 798//                     let mut projects = participant.projects.iter().peekable();
 799//                     while let Some(project) = projects.next() {
 800//                         self.entries.push(ListEntry::ParticipantProject {
 801//                             project_id: project.id,
 802//                             worktree_root_names: project.worktree_root_names.clone(),
 803//                             host_user_id: participant.user.id,
 804//                             is_last: projects.peek().is_none()
 805//                                 && participant.video_tracks.is_empty(),
 806//                         });
 807//                     }
 808//                     if !participant.video_tracks.is_empty() {
 809//                         self.entries.push(ListEntry::ParticipantScreen {
 810//                             peer_id: Some(participant.peer_id),
 811//                             is_last: true,
 812//                         });
 813//                     }
 814//                 }
 815
 816//                 // Populate pending participants.
 817//                 self.match_candidates.clear();
 818//                 self.match_candidates
 819//                     .extend(room.pending_participants().iter().enumerate().map(
 820//                         |(id, participant)| StringMatchCandidate {
 821//                             id,
 822//                             string: participant.github_login.clone(),
 823//                             char_bag: participant.github_login.chars().collect(),
 824//                         },
 825//                     ));
 826//                 let matches = executor.block(match_strings(
 827//                     &self.match_candidates,
 828//                     &query,
 829//                     true,
 830//                     usize::MAX,
 831//                     &Default::default(),
 832//                     executor.clone(),
 833//                 ));
 834//                 self.entries
 835//                     .extend(matches.iter().map(|mat| ListEntry::CallParticipant {
 836//                         user: room.pending_participants()[mat.candidate_id].clone(),
 837//                         peer_id: None,
 838//                         is_pending: true,
 839//                     }));
 840//             }
 841//         }
 842
 843//         let mut request_entries = Vec::new();
 844
 845//         if cx.has_flag::<ChannelsAlpha>() {
 846//             self.entries.push(ListEntry::Header(Section::Channels));
 847
 848//             if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() {
 849//                 self.match_candidates.clear();
 850//                 self.match_candidates
 851//                     .extend(channel_store.ordered_channels().enumerate().map(
 852//                         |(ix, (_, channel))| StringMatchCandidate {
 853//                             id: ix,
 854//                             string: channel.name.clone(),
 855//                             char_bag: channel.name.chars().collect(),
 856//                         },
 857//                     ));
 858//                 let matches = executor.block(match_strings(
 859//                     &self.match_candidates,
 860//                     &query,
 861//                     true,
 862//                     usize::MAX,
 863//                     &Default::default(),
 864//                     executor.clone(),
 865//                 ));
 866//                 if let Some(state) = &self.channel_editing_state {
 867//                     if matches!(state, ChannelEditingState::Create { location: None, .. }) {
 868//                         self.entries.push(ListEntry::ChannelEditor { depth: 0 });
 869//                     }
 870//                 }
 871//                 let mut collapse_depth = None;
 872//                 for mat in matches {
 873//                     let channel = channel_store.channel_at_index(mat.candidate_id).unwrap();
 874//                     let depth = channel.parent_path.len();
 875
 876//                     if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) {
 877//                         collapse_depth = Some(depth);
 878//                     } else if let Some(collapsed_depth) = collapse_depth {
 879//                         if depth > collapsed_depth {
 880//                             continue;
 881//                         }
 882//                         if self.is_channel_collapsed(channel.id) {
 883//                             collapse_depth = Some(depth);
 884//                         } else {
 885//                             collapse_depth = None;
 886//                         }
 887//                     }
 888
 889//                     let has_children = channel_store
 890//                         .channel_at_index(mat.candidate_id + 1)
 891//                         .map_or(false, |next_channel| {
 892//                             next_channel.parent_path.ends_with(&[channel.id])
 893//                         });
 894
 895//                     match &self.channel_editing_state {
 896//                         Some(ChannelEditingState::Create {
 897//                             location: parent_id,
 898//                             ..
 899//                         }) if *parent_id == Some(channel.id) => {
 900//                             self.entries.push(ListEntry::Channel {
 901//                                 channel: channel.clone(),
 902//                                 depth,
 903//                                 has_children: false,
 904//                             });
 905//                             self.entries
 906//                                 .push(ListEntry::ChannelEditor { depth: depth + 1 });
 907//                         }
 908//                         Some(ChannelEditingState::Rename {
 909//                             location: parent_id,
 910//                             ..
 911//                         }) if parent_id == &channel.id => {
 912//                             self.entries.push(ListEntry::ChannelEditor { depth });
 913//                         }
 914//                         _ => {
 915//                             self.entries.push(ListEntry::Channel {
 916//                                 channel: channel.clone(),
 917//                                 depth,
 918//                                 has_children,
 919//                             });
 920//                         }
 921//                     }
 922//                 }
 923//             }
 924
 925//             let channel_invites = channel_store.channel_invitations();
 926//             if !channel_invites.is_empty() {
 927//                 self.match_candidates.clear();
 928//                 self.match_candidates
 929//                     .extend(channel_invites.iter().enumerate().map(|(ix, channel)| {
 930//                         StringMatchCandidate {
 931//                             id: ix,
 932//                             string: channel.name.clone(),
 933//                             char_bag: channel.name.chars().collect(),
 934//                         }
 935//                     }));
 936//                 let matches = executor.block(match_strings(
 937//                     &self.match_candidates,
 938//                     &query,
 939//                     true,
 940//                     usize::MAX,
 941//                     &Default::default(),
 942//                     executor.clone(),
 943//                 ));
 944//                 request_entries.extend(matches.iter().map(|mat| {
 945//                     ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone())
 946//                 }));
 947
 948//                 if !request_entries.is_empty() {
 949//                     self.entries
 950//                         .push(ListEntry::Header(Section::ChannelInvites));
 951//                     if !self.collapsed_sections.contains(&Section::ChannelInvites) {
 952//                         self.entries.append(&mut request_entries);
 953//                     }
 954//                 }
 955//             }
 956//         }
 957
 958//         self.entries.push(ListEntry::Header(Section::Contacts));
 959
 960//         request_entries.clear();
 961//         let incoming = user_store.incoming_contact_requests();
 962//         if !incoming.is_empty() {
 963//             self.match_candidates.clear();
 964//             self.match_candidates
 965//                 .extend(
 966//                     incoming
 967//                         .iter()
 968//                         .enumerate()
 969//                         .map(|(ix, user)| StringMatchCandidate {
 970//                             id: ix,
 971//                             string: user.github_login.clone(),
 972//                             char_bag: user.github_login.chars().collect(),
 973//                         }),
 974//                 );
 975//             let matches = executor.block(match_strings(
 976//                 &self.match_candidates,
 977//                 &query,
 978//                 true,
 979//                 usize::MAX,
 980//                 &Default::default(),
 981//                 executor.clone(),
 982//             ));
 983//             request_entries.extend(
 984//                 matches
 985//                     .iter()
 986//                     .map(|mat| ListEntry::IncomingRequest(incoming[mat.candidate_id].clone())),
 987//             );
 988//         }
 989
 990//         let outgoing = user_store.outgoing_contact_requests();
 991//         if !outgoing.is_empty() {
 992//             self.match_candidates.clear();
 993//             self.match_candidates
 994//                 .extend(
 995//                     outgoing
 996//                         .iter()
 997//                         .enumerate()
 998//                         .map(|(ix, user)| StringMatchCandidate {
 999//                             id: ix,
1000//                             string: user.github_login.clone(),
1001//                             char_bag: user.github_login.chars().collect(),
1002//                         }),
1003//                 );
1004//             let matches = executor.block(match_strings(
1005//                 &self.match_candidates,
1006//                 &query,
1007//                 true,
1008//                 usize::MAX,
1009//                 &Default::default(),
1010//                 executor.clone(),
1011//             ));
1012//             request_entries.extend(
1013//                 matches
1014//                     .iter()
1015//                     .map(|mat| ListEntry::OutgoingRequest(outgoing[mat.candidate_id].clone())),
1016//             );
1017//         }
1018
1019//         if !request_entries.is_empty() {
1020//             self.entries
1021//                 .push(ListEntry::Header(Section::ContactRequests));
1022//             if !self.collapsed_sections.contains(&Section::ContactRequests) {
1023//                 self.entries.append(&mut request_entries);
1024//             }
1025//         }
1026
1027//         let contacts = user_store.contacts();
1028//         if !contacts.is_empty() {
1029//             self.match_candidates.clear();
1030//             self.match_candidates
1031//                 .extend(
1032//                     contacts
1033//                         .iter()
1034//                         .enumerate()
1035//                         .map(|(ix, contact)| StringMatchCandidate {
1036//                             id: ix,
1037//                             string: contact.user.github_login.clone(),
1038//                             char_bag: contact.user.github_login.chars().collect(),
1039//                         }),
1040//                 );
1041
1042//             let matches = executor.block(match_strings(
1043//                 &self.match_candidates,
1044//                 &query,
1045//                 true,
1046//                 usize::MAX,
1047//                 &Default::default(),
1048//                 executor.clone(),
1049//             ));
1050
1051//             let (online_contacts, offline_contacts) = matches
1052//                 .iter()
1053//                 .partition::<Vec<_>, _>(|mat| contacts[mat.candidate_id].online);
1054
1055//             for (matches, section) in [
1056//                 (online_contacts, Section::Online),
1057//                 (offline_contacts, Section::Offline),
1058//             ] {
1059//                 if !matches.is_empty() {
1060//                     self.entries.push(ListEntry::Header(section));
1061//                     if !self.collapsed_sections.contains(&section) {
1062//                         let active_call = &ActiveCall::global(cx).read(cx);
1063//                         for mat in matches {
1064//                             let contact = &contacts[mat.candidate_id];
1065//                             self.entries.push(ListEntry::Contact {
1066//                                 contact: contact.clone(),
1067//                                 calling: active_call.pending_invites().contains(&contact.user.id),
1068//                             });
1069//                         }
1070//                     }
1071//                 }
1072//             }
1073//         }
1074
1075//         if incoming.is_empty() && outgoing.is_empty() && contacts.is_empty() {
1076//             self.entries.push(ListEntry::ContactPlaceholder);
1077//         }
1078
1079//         if select_same_item {
1080//             if let Some(prev_selected_entry) = prev_selected_entry {
1081//                 self.selection.take();
1082//                 for (ix, entry) in self.entries.iter().enumerate() {
1083//                     if *entry == prev_selected_entry {
1084//                         self.selection = Some(ix);
1085//                         break;
1086//                     }
1087//                 }
1088//             }
1089//         } else {
1090//             self.selection = self.selection.and_then(|prev_selection| {
1091//                 if self.entries.is_empty() {
1092//                     None
1093//                 } else {
1094//                     Some(prev_selection.min(self.entries.len() - 1))
1095//                 }
1096//             });
1097//         }
1098
1099//         let old_scroll_top = self.list_state.logical_scroll_top();
1100
1101//         self.list_state.reset(self.entries.len());
1102
1103//         if scroll_to_top {
1104//             self.list_state.scroll_to(ListOffset::default());
1105//         } else {
1106//             // Attempt to maintain the same scroll position.
1107//             if let Some(old_top_entry) = old_entries.get(old_scroll_top.item_ix) {
1108//                 let new_scroll_top = self
1109//                     .entries
1110//                     .iter()
1111//                     .position(|entry| entry == old_top_entry)
1112//                     .map(|item_ix| ListOffset {
1113//                         item_ix,
1114//                         offset_in_item: old_scroll_top.offset_in_item,
1115//                     })
1116//                     .or_else(|| {
1117//                         let entry_after_old_top = old_entries.get(old_scroll_top.item_ix + 1)?;
1118//                         let item_ix = self
1119//                             .entries
1120//                             .iter()
1121//                             .position(|entry| entry == entry_after_old_top)?;
1122//                         Some(ListOffset {
1123//                             item_ix,
1124//                             offset_in_item: 0.,
1125//                         })
1126//                     })
1127//                     .or_else(|| {
1128//                         let entry_before_old_top =
1129//                             old_entries.get(old_scroll_top.item_ix.saturating_sub(1))?;
1130//                         let item_ix = self
1131//                             .entries
1132//                             .iter()
1133//                             .position(|entry| entry == entry_before_old_top)?;
1134//                         Some(ListOffset {
1135//                             item_ix,
1136//                             offset_in_item: 0.,
1137//                         })
1138//                     });
1139
1140//                 self.list_state
1141//                     .scroll_to(new_scroll_top.unwrap_or(old_scroll_top));
1142//             }
1143//         }
1144
1145//         cx.notify();
1146//     }
1147
1148//     fn render_call_participant(
1149//         user: &User,
1150//         peer_id: Option<PeerId>,
1151//         user_store: ModelHandle<UserStore>,
1152//         is_pending: bool,
1153//         is_selected: bool,
1154//         theme: &theme::Theme,
1155//         cx: &mut ViewContext<Self>,
1156//     ) -> AnyElement<Self> {
1157//         enum CallParticipant {}
1158//         enum CallParticipantTooltip {}
1159//         enum LeaveCallButton {}
1160//         enum LeaveCallTooltip {}
1161
1162//         let collab_theme = &theme.collab_panel;
1163
1164//         let is_current_user =
1165//             user_store.read(cx).current_user().map(|user| user.id) == Some(user.id);
1166
1167//         let content = MouseEventHandler::new::<CallParticipant, _>(
1168//             user.id as usize,
1169//             cx,
1170//             |mouse_state, cx| {
1171//                 let style = if is_current_user {
1172//                     *collab_theme
1173//                         .contact_row
1174//                         .in_state(is_selected)
1175//                         .style_for(&mut Default::default())
1176//                 } else {
1177//                     *collab_theme
1178//                         .contact_row
1179//                         .in_state(is_selected)
1180//                         .style_for(mouse_state)
1181//                 };
1182
1183//                 Flex::row()
1184//                     .with_children(user.avatar.clone().map(|avatar| {
1185//                         Image::from_data(avatar)
1186//                             .with_style(collab_theme.contact_avatar)
1187//                             .aligned()
1188//                             .left()
1189//                     }))
1190//                     .with_child(
1191//                         Label::new(
1192//                             user.github_login.clone(),
1193//                             collab_theme.contact_username.text.clone(),
1194//                         )
1195//                         .contained()
1196//                         .with_style(collab_theme.contact_username.container)
1197//                         .aligned()
1198//                         .left()
1199//                         .flex(1., true),
1200//                     )
1201//                     .with_children(if is_pending {
1202//                         Some(
1203//                             Label::new("Calling", collab_theme.calling_indicator.text.clone())
1204//                                 .contained()
1205//                                 .with_style(collab_theme.calling_indicator.container)
1206//                                 .aligned()
1207//                                 .into_any(),
1208//                         )
1209//                     } else if is_current_user {
1210//                         Some(
1211//                             MouseEventHandler::new::<LeaveCallButton, _>(0, cx, |state, _| {
1212//                                 render_icon_button(
1213//                                     theme
1214//                                         .collab_panel
1215//                                         .leave_call_button
1216//                                         .style_for(is_selected, state),
1217//                                     "icons/exit.svg",
1218//                                 )
1219//                             })
1220//                             .with_cursor_style(CursorStyle::PointingHand)
1221//                             .on_click(MouseButton::Left, |_, _, cx| {
1222//                                 Self::leave_call(cx);
1223//                             })
1224//                             .with_tooltip::<LeaveCallTooltip>(
1225//                                 0,
1226//                                 "Leave call",
1227//                                 None,
1228//                                 theme.tooltip.clone(),
1229//                                 cx,
1230//                             )
1231//                             .into_any(),
1232//                         )
1233//                     } else {
1234//                         None
1235//                     })
1236//                     .constrained()
1237//                     .with_height(collab_theme.row_height)
1238//                     .contained()
1239//                     .with_style(style)
1240//             },
1241//         );
1242
1243//         if is_current_user || is_pending || peer_id.is_none() {
1244//             return content.into_any();
1245//         }
1246
1247//         let tooltip = format!("Follow {}", user.github_login);
1248
1249//         content
1250//             .on_click(MouseButton::Left, move |_, this, cx| {
1251//                 if let Some(workspace) = this.workspace.upgrade(cx) {
1252//                     workspace
1253//                         .update(cx, |workspace, cx| workspace.follow(peer_id.unwrap(), cx))
1254//                         .map(|task| task.detach_and_log_err(cx));
1255//                 }
1256//             })
1257//             .with_cursor_style(CursorStyle::PointingHand)
1258//             .with_tooltip::<CallParticipantTooltip>(
1259//                 user.id as usize,
1260//                 tooltip,
1261//                 Some(Box::new(FollowNextCollaborator)),
1262//                 theme.tooltip.clone(),
1263//                 cx,
1264//             )
1265//             .into_any()
1266//     }
1267
1268//     fn render_participant_project(
1269//         project_id: u64,
1270//         worktree_root_names: &[String],
1271//         host_user_id: u64,
1272//         is_current: bool,
1273//         is_last: bool,
1274//         is_selected: bool,
1275//         theme: &theme::Theme,
1276//         cx: &mut ViewContext<Self>,
1277//     ) -> AnyElement<Self> {
1278//         enum JoinProject {}
1279//         enum JoinProjectTooltip {}
1280
1281//         let collab_theme = &theme.collab_panel;
1282//         let host_avatar_width = collab_theme
1283//             .contact_avatar
1284//             .width
1285//             .or(collab_theme.contact_avatar.height)
1286//             .unwrap_or(0.);
1287//         let tree_branch = collab_theme.tree_branch;
1288//         let project_name = if worktree_root_names.is_empty() {
1289//             "untitled".to_string()
1290//         } else {
1291//             worktree_root_names.join(", ")
1292//         };
1293
1294//         let content =
1295//             MouseEventHandler::new::<JoinProject, _>(project_id as usize, cx, |mouse_state, cx| {
1296//                 let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
1297//                 let row = if is_current {
1298//                     collab_theme
1299//                         .project_row
1300//                         .in_state(true)
1301//                         .style_for(&mut Default::default())
1302//                 } else {
1303//                     collab_theme
1304//                         .project_row
1305//                         .in_state(is_selected)
1306//                         .style_for(mouse_state)
1307//                 };
1308
1309//                 Flex::row()
1310//                     .with_child(render_tree_branch(
1311//                         tree_branch,
1312//                         &row.name.text,
1313//                         is_last,
1314//                         vec2f(host_avatar_width, collab_theme.row_height),
1315//                         cx.font_cache(),
1316//                     ))
1317//                     .with_child(
1318//                         Svg::new("icons/file_icons/folder.svg")
1319//                             .with_color(collab_theme.channel_hash.color)
1320//                             .constrained()
1321//                             .with_width(collab_theme.channel_hash.width)
1322//                             .aligned()
1323//                             .left(),
1324//                     )
1325//                     .with_child(
1326//                         Label::new(project_name.clone(), row.name.text.clone())
1327//                             .aligned()
1328//                             .left()
1329//                             .contained()
1330//                             .with_style(row.name.container)
1331//                             .flex(1., false),
1332//                     )
1333//                     .constrained()
1334//                     .with_height(collab_theme.row_height)
1335//                     .contained()
1336//                     .with_style(row.container)
1337//             });
1338
1339//         if is_current {
1340//             return content.into_any();
1341//         }
1342
1343//         content
1344//             .with_cursor_style(CursorStyle::PointingHand)
1345//             .on_click(MouseButton::Left, move |_, this, cx| {
1346//                 if let Some(workspace) = this.workspace.upgrade(cx) {
1347//                     let app_state = workspace.read(cx).app_state().clone();
1348//                     workspace::join_remote_project(project_id, host_user_id, app_state, cx)
1349//                         .detach_and_log_err(cx);
1350//                 }
1351//             })
1352//             .with_tooltip::<JoinProjectTooltip>(
1353//                 project_id as usize,
1354//                 format!("Open {}", project_name),
1355//                 None,
1356//                 theme.tooltip.clone(),
1357//                 cx,
1358//             )
1359//             .into_any()
1360//     }
1361
1362//     fn render_participant_screen(
1363//         peer_id: Option<PeerId>,
1364//         is_last: bool,
1365//         is_selected: bool,
1366//         theme: &theme::CollabPanel,
1367//         cx: &mut ViewContext<Self>,
1368//     ) -> AnyElement<Self> {
1369//         enum OpenSharedScreen {}
1370
1371//         let host_avatar_width = theme
1372//             .contact_avatar
1373//             .width
1374//             .or(theme.contact_avatar.height)
1375//             .unwrap_or(0.);
1376//         let tree_branch = theme.tree_branch;
1377
1378//         let handler = MouseEventHandler::new::<OpenSharedScreen, _>(
1379//             peer_id.map(|id| id.as_u64()).unwrap_or(0) as usize,
1380//             cx,
1381//             |mouse_state, cx| {
1382//                 let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
1383//                 let row = theme
1384//                     .project_row
1385//                     .in_state(is_selected)
1386//                     .style_for(mouse_state);
1387
1388//                 Flex::row()
1389//                     .with_child(render_tree_branch(
1390//                         tree_branch,
1391//                         &row.name.text,
1392//                         is_last,
1393//                         vec2f(host_avatar_width, theme.row_height),
1394//                         cx.font_cache(),
1395//                     ))
1396//                     .with_child(
1397//                         Svg::new("icons/desktop.svg")
1398//                             .with_color(theme.channel_hash.color)
1399//                             .constrained()
1400//                             .with_width(theme.channel_hash.width)
1401//                             .aligned()
1402//                             .left(),
1403//                     )
1404//                     .with_child(
1405//                         Label::new("Screen", row.name.text.clone())
1406//                             .aligned()
1407//                             .left()
1408//                             .contained()
1409//                             .with_style(row.name.container)
1410//                             .flex(1., false),
1411//                     )
1412//                     .constrained()
1413//                     .with_height(theme.row_height)
1414//                     .contained()
1415//                     .with_style(row.container)
1416//             },
1417//         );
1418//         if peer_id.is_none() {
1419//             return handler.into_any();
1420//         }
1421//         handler
1422//             .with_cursor_style(CursorStyle::PointingHand)
1423//             .on_click(MouseButton::Left, move |_, this, cx| {
1424//                 if let Some(workspace) = this.workspace.upgrade(cx) {
1425//                     workspace.update(cx, |workspace, cx| {
1426//                         workspace.open_shared_screen(peer_id.unwrap(), cx)
1427//                     });
1428//                 }
1429//             })
1430//             .into_any()
1431//     }
1432
1433//     fn take_editing_state(&mut self, cx: &mut ViewContext<Self>) -> bool {
1434//         if let Some(_) = self.channel_editing_state.take() {
1435//             self.channel_name_editor.update(cx, |editor, cx| {
1436//                 editor.set_text("", cx);
1437//             });
1438//             true
1439//         } else {
1440//             false
1441//         }
1442//     }
1443
1444//     fn render_header(
1445//         &self,
1446//         section: Section,
1447//         theme: &theme::Theme,
1448//         is_selected: bool,
1449//         is_collapsed: bool,
1450//         cx: &mut ViewContext<Self>,
1451//     ) -> AnyElement<Self> {
1452//         enum Header {}
1453//         enum LeaveCallContactList {}
1454//         enum AddChannel {}
1455
1456//         let tooltip_style = &theme.tooltip;
1457//         let mut channel_link = None;
1458//         let mut channel_tooltip_text = None;
1459//         let mut channel_icon = None;
1460//         let mut is_dragged_over = false;
1461
1462//         let text = match section {
1463//             Section::ActiveCall => {
1464//                 let channel_name = maybe!({
1465//                     let channel_id = ActiveCall::global(cx).read(cx).channel_id(cx)?;
1466
1467//                     let channel = self.channel_store.read(cx).channel_for_id(channel_id)?;
1468
1469//                     channel_link = Some(channel.link());
1470//                     (channel_icon, channel_tooltip_text) = match channel.visibility {
1471//                         proto::ChannelVisibility::Public => {
1472//                             (Some("icons/public.svg"), Some("Copy public channel link."))
1473//                         }
1474//                         proto::ChannelVisibility::Members => {
1475//                             (Some("icons/hash.svg"), Some("Copy private channel link."))
1476//                         }
1477//                     };
1478
1479//                     Some(channel.name.as_str())
1480//                 });
1481
1482//                 if let Some(name) = channel_name {
1483//                     Cow::Owned(format!("{}", name))
1484//                 } else {
1485//                     Cow::Borrowed("Current Call")
1486//                 }
1487//             }
1488//             Section::ContactRequests => Cow::Borrowed("Requests"),
1489//             Section::Contacts => Cow::Borrowed("Contacts"),
1490//             Section::Channels => Cow::Borrowed("Channels"),
1491//             Section::ChannelInvites => Cow::Borrowed("Invites"),
1492//             Section::Online => Cow::Borrowed("Online"),
1493//             Section::Offline => Cow::Borrowed("Offline"),
1494//         };
1495
1496//         enum AddContact {}
1497//         let button = match section {
1498//             Section::ActiveCall => channel_link.map(|channel_link| {
1499//                 let channel_link_copy = channel_link.clone();
1500//                 MouseEventHandler::new::<AddContact, _>(0, cx, |state, _| {
1501//                     render_icon_button(
1502//                         theme
1503//                             .collab_panel
1504//                             .leave_call_button
1505//                             .style_for(is_selected, state),
1506//                         "icons/link.svg",
1507//                     )
1508//                 })
1509//                 .with_cursor_style(CursorStyle::PointingHand)
1510//                 .on_click(MouseButton::Left, move |_, _, cx| {
1511//                     let item = ClipboardItem::new(channel_link_copy.clone());
1512//                     cx.write_to_clipboard(item)
1513//                 })
1514//                 .with_tooltip::<AddContact>(
1515//                     0,
1516//                     channel_tooltip_text.unwrap(),
1517//                     None,
1518//                     tooltip_style.clone(),
1519//                     cx,
1520//                 )
1521//             }),
1522//             Section::Contacts => Some(
1523//                 MouseEventHandler::new::<LeaveCallContactList, _>(0, cx, |state, _| {
1524//                     render_icon_button(
1525//                         theme
1526//                             .collab_panel
1527//                             .add_contact_button
1528//                             .style_for(is_selected, state),
1529//                         "icons/plus.svg",
1530//                     )
1531//                 })
1532//                 .with_cursor_style(CursorStyle::PointingHand)
1533//                 .on_click(MouseButton::Left, |_, this, cx| {
1534//                     this.toggle_contact_finder(cx);
1535//                 })
1536//                 .with_tooltip::<LeaveCallContactList>(
1537//                     0,
1538//                     "Search for new contact",
1539//                     None,
1540//                     tooltip_style.clone(),
1541//                     cx,
1542//                 ),
1543//             ),
1544//             Section::Channels => {
1545//                 if cx
1546//                     .global::<DragAndDrop<Workspace>>()
1547//                     .currently_dragged::<Channel>(cx.window())
1548//                     .is_some()
1549//                     && self.drag_target_channel == ChannelDragTarget::Root
1550//                 {
1551//                     is_dragged_over = true;
1552//                 }
1553
1554//                 Some(
1555//                     MouseEventHandler::new::<AddChannel, _>(0, cx, |state, _| {
1556//                         render_icon_button(
1557//                             theme
1558//                                 .collab_panel
1559//                                 .add_contact_button
1560//                                 .style_for(is_selected, state),
1561//                             "icons/plus.svg",
1562//                         )
1563//                     })
1564//                     .with_cursor_style(CursorStyle::PointingHand)
1565//                     .on_click(MouseButton::Left, |_, this, cx| this.new_root_channel(cx))
1566//                     .with_tooltip::<AddChannel>(
1567//                         0,
1568//                         "Create a channel",
1569//                         None,
1570//                         tooltip_style.clone(),
1571//                         cx,
1572//                     ),
1573//                 )
1574//             }
1575//             _ => None,
1576//         };
1577
1578//         let can_collapse = match section {
1579//             Section::ActiveCall | Section::Channels | Section::Contacts => false,
1580//             Section::ChannelInvites
1581//             | Section::ContactRequests
1582//             | Section::Online
1583//             | Section::Offline => true,
1584//         };
1585//         let icon_size = (&theme.collab_panel).section_icon_size;
1586//         let mut result = MouseEventHandler::new::<Header, _>(section as usize, cx, |state, _| {
1587//             let header_style = if can_collapse {
1588//                 theme
1589//                     .collab_panel
1590//                     .subheader_row
1591//                     .in_state(is_selected)
1592//                     .style_for(state)
1593//             } else {
1594//                 &theme.collab_panel.header_row
1595//             };
1596
1597//             Flex::row()
1598//                 .with_children(if can_collapse {
1599//                     Some(
1600//                         Svg::new(if is_collapsed {
1601//                             "icons/chevron_right.svg"
1602//                         } else {
1603//                             "icons/chevron_down.svg"
1604//                         })
1605//                         .with_color(header_style.text.color)
1606//                         .constrained()
1607//                         .with_max_width(icon_size)
1608//                         .with_max_height(icon_size)
1609//                         .aligned()
1610//                         .constrained()
1611//                         .with_width(icon_size)
1612//                         .contained()
1613//                         .with_margin_right(
1614//                             theme.collab_panel.contact_username.container.margin.left,
1615//                         ),
1616//                     )
1617//                 } else if let Some(channel_icon) = channel_icon {
1618//                     Some(
1619//                         Svg::new(channel_icon)
1620//                             .with_color(header_style.text.color)
1621//                             .constrained()
1622//                             .with_max_width(icon_size)
1623//                             .with_max_height(icon_size)
1624//                             .aligned()
1625//                             .constrained()
1626//                             .with_width(icon_size)
1627//                             .contained()
1628//                             .with_margin_right(
1629//                                 theme.collab_panel.contact_username.container.margin.left,
1630//                             ),
1631//                     )
1632//                 } else {
1633//                     None
1634//                 })
1635//                 .with_child(
1636//                     Label::new(text, header_style.text.clone())
1637//                         .aligned()
1638//                         .left()
1639//                         .flex(1., true),
1640//                 )
1641//                 .with_children(button.map(|button| button.aligned().right()))
1642//                 .constrained()
1643//                 .with_height(theme.collab_panel.row_height)
1644//                 .contained()
1645//                 .with_style(if is_dragged_over {
1646//                     theme.collab_panel.dragged_over_header
1647//                 } else {
1648//                     header_style.container
1649//                 })
1650//         });
1651
1652//         result = result
1653//             .on_move(move |_, this, cx| {
1654//                 if cx
1655//                     .global::<DragAndDrop<Workspace>>()
1656//                     .currently_dragged::<Channel>(cx.window())
1657//                     .is_some()
1658//                 {
1659//                     this.drag_target_channel = ChannelDragTarget::Root;
1660//                     cx.notify()
1661//                 }
1662//             })
1663//             .on_up(MouseButton::Left, move |_, this, cx| {
1664//                 if let Some((_, dragged_channel)) = cx
1665//                     .global::<DragAndDrop<Workspace>>()
1666//                     .currently_dragged::<Channel>(cx.window())
1667//                 {
1668//                     this.channel_store
1669//                         .update(cx, |channel_store, cx| {
1670//                             channel_store.move_channel(dragged_channel.id, None, cx)
1671//                         })
1672//                         .detach_and_log_err(cx)
1673//                 }
1674//             });
1675
1676//         if can_collapse {
1677//             result = result
1678//                 .with_cursor_style(CursorStyle::PointingHand)
1679//                 .on_click(MouseButton::Left, move |_, this, cx| {
1680//                     if can_collapse {
1681//                         this.toggle_section_expanded(section, cx);
1682//                     }
1683//                 })
1684//         }
1685
1686//         result.into_any()
1687//     }
1688
1689//     fn render_contact(
1690//         contact: &Contact,
1691//         calling: bool,
1692//         project: &ModelHandle<Project>,
1693//         theme: &theme::Theme,
1694//         is_selected: bool,
1695//         cx: &mut ViewContext<Self>,
1696//     ) -> AnyElement<Self> {
1697//         enum ContactTooltip {}
1698
1699//         let collab_theme = &theme.collab_panel;
1700//         let online = contact.online;
1701//         let busy = contact.busy || calling;
1702//         let user_id = contact.user.id;
1703//         let github_login = contact.user.github_login.clone();
1704//         let initial_project = project.clone();
1705
1706//         let event_handler =
1707//             MouseEventHandler::new::<Contact, _>(contact.user.id as usize, cx, |state, cx| {
1708//                 Flex::row()
1709//                     .with_children(contact.user.avatar.clone().map(|avatar| {
1710//                         let status_badge = if contact.online {
1711//                             Some(
1712//                                 Empty::new()
1713//                                     .collapsed()
1714//                                     .contained()
1715//                                     .with_style(if busy {
1716//                                         collab_theme.contact_status_busy
1717//                                     } else {
1718//                                         collab_theme.contact_status_free
1719//                                     })
1720//                                     .aligned(),
1721//                             )
1722//                         } else {
1723//                             None
1724//                         };
1725//                         Stack::new()
1726//                             .with_child(
1727//                                 Image::from_data(avatar)
1728//                                     .with_style(collab_theme.contact_avatar)
1729//                                     .aligned()
1730//                                     .left(),
1731//                             )
1732//                             .with_children(status_badge)
1733//                     }))
1734//                     .with_child(
1735//                         Label::new(
1736//                             contact.user.github_login.clone(),
1737//                             collab_theme.contact_username.text.clone(),
1738//                         )
1739//                         .contained()
1740//                         .with_style(collab_theme.contact_username.container)
1741//                         .aligned()
1742//                         .left()
1743//                         .flex(1., true),
1744//                     )
1745//                     .with_children(if state.hovered() {
1746//                         Some(
1747//                             MouseEventHandler::new::<Cancel, _>(
1748//                                 contact.user.id as usize,
1749//                                 cx,
1750//                                 |mouse_state, _| {
1751//                                     let button_style =
1752//                                         collab_theme.contact_button.style_for(mouse_state);
1753//                                     render_icon_button(button_style, "icons/x.svg")
1754//                                         .aligned()
1755//                                         .flex_float()
1756//                                 },
1757//                             )
1758//                             .with_padding(Padding::uniform(2.))
1759//                             .with_cursor_style(CursorStyle::PointingHand)
1760//                             .on_click(MouseButton::Left, move |_, this, cx| {
1761//                                 this.remove_contact(user_id, &github_login, cx);
1762//                             })
1763//                             .flex_float(),
1764//                         )
1765//                     } else {
1766//                         None
1767//                     })
1768//                     .with_children(if calling {
1769//                         Some(
1770//                             Label::new("Calling", collab_theme.calling_indicator.text.clone())
1771//                                 .contained()
1772//                                 .with_style(collab_theme.calling_indicator.container)
1773//                                 .aligned(),
1774//                         )
1775//                     } else {
1776//                         None
1777//                     })
1778//                     .constrained()
1779//                     .with_height(collab_theme.row_height)
1780//                     .contained()
1781//                     .with_style(
1782//                         *collab_theme
1783//                             .contact_row
1784//                             .in_state(is_selected)
1785//                             .style_for(state),
1786//                     )
1787//             });
1788
1789//         if online && !busy {
1790//             let room = ActiveCall::global(cx).read(cx).room();
1791//             let label = if room.is_some() {
1792//                 format!("Invite {} to join call", contact.user.github_login)
1793//             } else {
1794//                 format!("Call {}", contact.user.github_login)
1795//             };
1796
1797//             event_handler
1798//                 .on_click(MouseButton::Left, move |_, this, cx| {
1799//                     this.call(user_id, Some(initial_project.clone()), cx);
1800//                 })
1801//                 .with_cursor_style(CursorStyle::PointingHand)
1802//                 .with_tooltip::<ContactTooltip>(
1803//                     contact.user.id as usize,
1804//                     label,
1805//                     None,
1806//                     theme.tooltip.clone(),
1807//                     cx,
1808//                 )
1809//                 .into_any()
1810//         } else {
1811//             event_handler
1812//                 .with_tooltip::<ContactTooltip>(
1813//                     contact.user.id as usize,
1814//                     format!(
1815//                         "{} is {}",
1816//                         contact.user.github_login,
1817//                         if busy { "on a call" } else { "offline" }
1818//                     ),
1819//                     None,
1820//                     theme.tooltip.clone(),
1821//                     cx,
1822//                 )
1823//                 .into_any()
1824//         }
1825//     }
1826
1827//     fn render_contact_placeholder(
1828//         &self,
1829//         theme: &theme::CollabPanel,
1830//         is_selected: bool,
1831//         cx: &mut ViewContext<Self>,
1832//     ) -> AnyElement<Self> {
1833//         enum AddContacts {}
1834//         MouseEventHandler::new::<AddContacts, _>(0, cx, |state, _| {
1835//             let style = theme.list_empty_state.style_for(is_selected, state);
1836//             Flex::row()
1837//                 .with_child(
1838//                     Svg::new("icons/plus.svg")
1839//                         .with_color(theme.list_empty_icon.color)
1840//                         .constrained()
1841//                         .with_width(theme.list_empty_icon.width)
1842//                         .aligned()
1843//                         .left(),
1844//                 )
1845//                 .with_child(
1846//                     Label::new("Add a contact", style.text.clone())
1847//                         .contained()
1848//                         .with_style(theme.list_empty_label_container),
1849//                 )
1850//                 .align_children_center()
1851//                 .contained()
1852//                 .with_style(style.container)
1853//                 .into_any()
1854//         })
1855//         .on_click(MouseButton::Left, |_, this, cx| {
1856//             this.toggle_contact_finder(cx);
1857//         })
1858//         .into_any()
1859//     }
1860
1861//     fn render_channel_editor(
1862//         &self,
1863//         theme: &theme::Theme,
1864//         depth: usize,
1865//         cx: &AppContext,
1866//     ) -> AnyElement<Self> {
1867//         Flex::row()
1868//             .with_child(
1869//                 Empty::new()
1870//                     .constrained()
1871//                     .with_width(theme.collab_panel.disclosure.button_space()),
1872//             )
1873//             .with_child(
1874//                 Svg::new("icons/hash.svg")
1875//                     .with_color(theme.collab_panel.channel_hash.color)
1876//                     .constrained()
1877//                     .with_width(theme.collab_panel.channel_hash.width)
1878//                     .aligned()
1879//                     .left(),
1880//             )
1881//             .with_child(
1882//                 if let Some(pending_name) = self
1883//                     .channel_editing_state
1884//                     .as_ref()
1885//                     .and_then(|state| state.pending_name())
1886//                 {
1887//                     Label::new(
1888//                         pending_name.to_string(),
1889//                         theme.collab_panel.contact_username.text.clone(),
1890//                     )
1891//                     .contained()
1892//                     .with_style(theme.collab_panel.contact_username.container)
1893//                     .aligned()
1894//                     .left()
1895//                     .flex(1., true)
1896//                     .into_any()
1897//                 } else {
1898//                     ChildView::new(&self.channel_name_editor, cx)
1899//                         .aligned()
1900//                         .left()
1901//                         .contained()
1902//                         .with_style(theme.collab_panel.channel_editor)
1903//                         .flex(1.0, true)
1904//                         .into_any()
1905//                 },
1906//             )
1907//             .align_children_center()
1908//             .constrained()
1909//             .with_height(theme.collab_panel.row_height)
1910//             .contained()
1911//             .with_style(ContainerStyle {
1912//                 background_color: Some(theme.editor.background),
1913//                 ..*theme.collab_panel.contact_row.default_style()
1914//             })
1915//             .with_padding_left(
1916//                 theme.collab_panel.contact_row.default_style().padding.left
1917//                     + theme.collab_panel.channel_indent * depth as f32,
1918//             )
1919//             .into_any()
1920//     }
1921
1922//     fn render_channel(
1923//         &self,
1924//         channel: &Channel,
1925//         depth: usize,
1926//         theme: &theme::Theme,
1927//         is_selected: bool,
1928//         has_children: bool,
1929//         ix: usize,
1930//         cx: &mut ViewContext<Self>,
1931//     ) -> AnyElement<Self> {
1932//         let channel_id = channel.id;
1933//         let collab_theme = &theme.collab_panel;
1934//         let is_public = self
1935//             .channel_store
1936//             .read(cx)
1937//             .channel_for_id(channel_id)
1938//             .map(|channel| channel.visibility)
1939//             == Some(proto::ChannelVisibility::Public);
1940//         let other_selected = self.selected_channel().map(|channel| channel.id) == Some(channel.id);
1941//         let disclosed =
1942//             has_children.then(|| !self.collapsed_channels.binary_search(&channel.id).is_ok());
1943
1944//         let is_active = maybe!({
1945//             let call_channel = ActiveCall::global(cx)
1946//                 .read(cx)
1947//                 .room()?
1948//                 .read(cx)
1949//                 .channel_id()?;
1950//             Some(call_channel == channel_id)
1951//         })
1952//         .unwrap_or(false);
1953
1954//         const FACEPILE_LIMIT: usize = 3;
1955
1956//         enum ChannelCall {}
1957//         enum ChannelNote {}
1958//         enum NotesTooltip {}
1959//         enum ChatTooltip {}
1960//         enum ChannelTooltip {}
1961
1962//         let mut is_dragged_over = false;
1963//         if cx
1964//             .global::<DragAndDrop<Workspace>>()
1965//             .currently_dragged::<Channel>(cx.window())
1966//             .is_some()
1967//             && self.drag_target_channel == ChannelDragTarget::Channel(channel_id)
1968//         {
1969//             is_dragged_over = true;
1970//         }
1971
1972//         let has_messages_notification = channel.unseen_message_id.is_some();
1973
1974//         MouseEventHandler::new::<Channel, _>(ix, cx, |state, cx| {
1975//             let row_hovered = state.hovered();
1976
1977//             let mut select_state = |interactive: &Interactive<ContainerStyle>| {
1978//                 if state.clicked() == Some(MouseButton::Left) && interactive.clicked.is_some() {
1979//                     interactive.clicked.as_ref().unwrap().clone()
1980//                 } else if state.hovered() || other_selected {
1981//                     interactive
1982//                         .hovered
1983//                         .as_ref()
1984//                         .unwrap_or(&interactive.default)
1985//                         .clone()
1986//                 } else {
1987//                     interactive.default.clone()
1988//                 }
1989//             };
1990
1991//             Flex::<Self>::row()
1992//                 .with_child(
1993//                     Svg::new(if is_public {
1994//                         "icons/public.svg"
1995//                     } else {
1996//                         "icons/hash.svg"
1997//                     })
1998//                     .with_color(collab_theme.channel_hash.color)
1999//                     .constrained()
2000//                     .with_width(collab_theme.channel_hash.width)
2001//                     .aligned()
2002//                     .left(),
2003//                 )
2004//                 .with_child({
2005//                     let style = collab_theme.channel_name.inactive_state();
2006//                     Flex::row()
2007//                         .with_child(
2008//                             Label::new(channel.name.clone(), style.text.clone())
2009//                                 .contained()
2010//                                 .with_style(style.container)
2011//                                 .aligned()
2012//                                 .left()
2013//                                 .with_tooltip::<ChannelTooltip>(
2014//                                     ix,
2015//                                     "Join channel",
2016//                                     None,
2017//                                     theme.tooltip.clone(),
2018//                                     cx,
2019//                                 ),
2020//                         )
2021//                         .with_children({
2022//                             let participants =
2023//                                 self.channel_store.read(cx).channel_participants(channel_id);
2024
2025//                             if !participants.is_empty() {
2026//                                 let extra_count = participants.len().saturating_sub(FACEPILE_LIMIT);
2027
2028//                                 let result = FacePile::new(collab_theme.face_overlap)
2029//                                     .with_children(
2030//                                         participants
2031//                                             .iter()
2032//                                             .filter_map(|user| {
2033//                                                 Some(
2034//                                                     Image::from_data(user.avatar.clone()?)
2035//                                                         .with_style(collab_theme.channel_avatar),
2036//                                                 )
2037//                                             })
2038//                                             .take(FACEPILE_LIMIT),
2039//                                     )
2040//                                     .with_children((extra_count > 0).then(|| {
2041//                                         Label::new(
2042//                                             format!("+{}", extra_count),
2043//                                             collab_theme.extra_participant_label.text.clone(),
2044//                                         )
2045//                                         .contained()
2046//                                         .with_style(collab_theme.extra_participant_label.container)
2047//                                     }));
2048
2049//                                 Some(result)
2050//                             } else {
2051//                                 None
2052//                             }
2053//                         })
2054//                         .with_spacing(8.)
2055//                         .align_children_center()
2056//                         .flex(1., true)
2057//                 })
2058//                 .with_child(
2059//                     MouseEventHandler::new::<ChannelNote, _>(ix, cx, move |mouse_state, _| {
2060//                         let container_style = collab_theme
2061//                             .disclosure
2062//                             .button
2063//                             .style_for(mouse_state)
2064//                             .container;
2065
2066//                         if channel.unseen_message_id.is_some() {
2067//                             Svg::new("icons/conversations.svg")
2068//                                 .with_color(collab_theme.channel_note_active_color)
2069//                                 .constrained()
2070//                                 .with_width(collab_theme.channel_hash.width)
2071//                                 .contained()
2072//                                 .with_style(container_style)
2073//                                 .with_uniform_padding(4.)
2074//                                 .into_any()
2075//                         } else if row_hovered {
2076//                             Svg::new("icons/conversations.svg")
2077//                                 .with_color(collab_theme.channel_hash.color)
2078//                                 .constrained()
2079//                                 .with_width(collab_theme.channel_hash.width)
2080//                                 .contained()
2081//                                 .with_style(container_style)
2082//                                 .with_uniform_padding(4.)
2083//                                 .into_any()
2084//                         } else {
2085//                             Empty::new().into_any()
2086//                         }
2087//                     })
2088//                     .on_click(MouseButton::Left, move |_, this, cx| {
2089//                         this.join_channel_chat(&JoinChannelChat { channel_id }, cx);
2090//                     })
2091//                     .with_tooltip::<ChatTooltip>(
2092//                         ix,
2093//                         "Open channel chat",
2094//                         None,
2095//                         theme.tooltip.clone(),
2096//                         cx,
2097//                     )
2098//                     .contained()
2099//                     .with_margin_right(4.),
2100//                 )
2101//                 .with_child(
2102//                     MouseEventHandler::new::<ChannelCall, _>(ix, cx, move |mouse_state, cx| {
2103//                         let container_style = collab_theme
2104//                             .disclosure
2105//                             .button
2106//                             .style_for(mouse_state)
2107//                             .container;
2108//                         if row_hovered || channel.unseen_note_version.is_some() {
2109//                             Svg::new("icons/file.svg")
2110//                                 .with_color(if channel.unseen_note_version.is_some() {
2111//                                     collab_theme.channel_note_active_color
2112//                                 } else {
2113//                                     collab_theme.channel_hash.color
2114//                                 })
2115//                                 .constrained()
2116//                                 .with_width(collab_theme.channel_hash.width)
2117//                                 .contained()
2118//                                 .with_style(container_style)
2119//                                 .with_uniform_padding(4.)
2120//                                 .with_margin_right(collab_theme.channel_hash.container.margin.left)
2121//                                 .with_tooltip::<NotesTooltip>(
2122//                                     ix as usize,
2123//                                     "Open channel notes",
2124//                                     None,
2125//                                     theme.tooltip.clone(),
2126//                                     cx,
2127//                                 )
2128//                                 .into_any()
2129//                         } else if has_messages_notification {
2130//                             Empty::new()
2131//                                 .constrained()
2132//                                 .with_width(collab_theme.channel_hash.width)
2133//                                 .contained()
2134//                                 .with_uniform_padding(4.)
2135//                                 .with_margin_right(collab_theme.channel_hash.container.margin.left)
2136//                                 .into_any()
2137//                         } else {
2138//                             Empty::new().into_any()
2139//                         }
2140//                     })
2141//                     .on_click(MouseButton::Left, move |_, this, cx| {
2142//                         this.open_channel_notes(&OpenChannelNotes { channel_id }, cx);
2143//                     }),
2144//                 )
2145//                 .align_children_center()
2146//                 .styleable_component()
2147//                 .disclosable(
2148//                     disclosed,
2149//                     Box::new(ToggleCollapse {
2150//                         location: channel.id.clone(),
2151//                     }),
2152//                 )
2153//                 .with_id(ix)
2154//                 .with_style(collab_theme.disclosure.clone())
2155//                 .element()
2156//                 .constrained()
2157//                 .with_height(collab_theme.row_height)
2158//                 .contained()
2159//                 .with_style(select_state(
2160//                     collab_theme
2161//                         .channel_row
2162//                         .in_state(is_selected || is_active || is_dragged_over),
2163//                 ))
2164//                 .with_padding_left(
2165//                     collab_theme.channel_row.default_style().padding.left
2166//                         + collab_theme.channel_indent * depth as f32,
2167//                 )
2168//         })
2169//         .on_click(MouseButton::Left, move |_, this, cx| {
2170//             if this.drag_target_channel == ChannelDragTarget::None {
2171//                 if is_active {
2172//                     this.open_channel_notes(&OpenChannelNotes { channel_id }, cx)
2173//                 } else {
2174//                     this.join_channel(channel_id, cx)
2175//                 }
2176//             }
2177//         })
2178//         .on_click(MouseButton::Right, {
2179//             let channel = channel.clone();
2180//             move |e, this, cx| {
2181//                 this.deploy_channel_context_menu(Some(e.position), &channel, ix, cx);
2182//             }
2183//         })
2184//         .on_up(MouseButton::Left, move |_, this, cx| {
2185//             if let Some((_, dragged_channel)) = cx
2186//                 .global::<DragAndDrop<Workspace>>()
2187//                 .currently_dragged::<Channel>(cx.window())
2188//             {
2189//                 this.channel_store
2190//                     .update(cx, |channel_store, cx| {
2191//                         channel_store.move_channel(dragged_channel.id, Some(channel_id), cx)
2192//                     })
2193//                     .detach_and_log_err(cx)
2194//             }
2195//         })
2196//         .on_move({
2197//             let channel = channel.clone();
2198//             move |_, this, cx| {
2199//                 if let Some((_, dragged_channel)) = cx
2200//                     .global::<DragAndDrop<Workspace>>()
2201//                     .currently_dragged::<Channel>(cx.window())
2202//                 {
2203//                     if channel.id != dragged_channel.id {
2204//                         this.drag_target_channel = ChannelDragTarget::Channel(channel.id);
2205//                     }
2206//                     cx.notify()
2207//                 }
2208//             }
2209//         })
2210//         .as_draggable::<_, Channel>(
2211//             channel.clone(),
2212//             move |_, channel, cx: &mut ViewContext<Workspace>| {
2213//                 let theme = &theme::current(cx).collab_panel;
2214
2215//                 Flex::<Workspace>::row()
2216//                     .with_child(
2217//                         Svg::new("icons/hash.svg")
2218//                             .with_color(theme.channel_hash.color)
2219//                             .constrained()
2220//                             .with_width(theme.channel_hash.width)
2221//                             .aligned()
2222//                             .left(),
2223//                     )
2224//                     .with_child(
2225//                         Label::new(channel.name.clone(), theme.channel_name.text.clone())
2226//                             .contained()
2227//                             .with_style(theme.channel_name.container)
2228//                             .aligned()
2229//                             .left(),
2230//                     )
2231//                     .align_children_center()
2232//                     .contained()
2233//                     .with_background_color(
2234//                         theme
2235//                             .container
2236//                             .background_color
2237//                             .unwrap_or(gpui::color::Color::transparent_black()),
2238//                     )
2239//                     .contained()
2240//                     .with_padding_left(
2241//                         theme.channel_row.default_style().padding.left
2242//                             + theme.channel_indent * depth as f32,
2243//                     )
2244//                     .into_any()
2245//             },
2246//         )
2247//         .with_cursor_style(CursorStyle::PointingHand)
2248//         .into_any()
2249//     }
2250
2251//     fn render_channel_notes(
2252//         &self,
2253//         channel_id: ChannelId,
2254//         theme: &theme::CollabPanel,
2255//         is_selected: bool,
2256//         ix: usize,
2257//         cx: &mut ViewContext<Self>,
2258//     ) -> AnyElement<Self> {
2259//         enum ChannelNotes {}
2260//         let host_avatar_width = theme
2261//             .contact_avatar
2262//             .width
2263//             .or(theme.contact_avatar.height)
2264//             .unwrap_or(0.);
2265
2266//         MouseEventHandler::new::<ChannelNotes, _>(ix as usize, cx, |state, cx| {
2267//             let tree_branch = *theme.tree_branch.in_state(is_selected).style_for(state);
2268//             let row = theme.project_row.in_state(is_selected).style_for(state);
2269
2270//             Flex::<Self>::row()
2271//                 .with_child(render_tree_branch(
2272//                     tree_branch,
2273//                     &row.name.text,
2274//                     false,
2275//                     vec2f(host_avatar_width, theme.row_height),
2276//                     cx.font_cache(),
2277//                 ))
2278//                 .with_child(
2279//                     Svg::new("icons/file.svg")
2280//                         .with_color(theme.channel_hash.color)
2281//                         .constrained()
2282//                         .with_width(theme.channel_hash.width)
2283//                         .aligned()
2284//                         .left(),
2285//                 )
2286//                 .with_child(
2287//                     Label::new("notes", theme.channel_name.text.clone())
2288//                         .contained()
2289//                         .with_style(theme.channel_name.container)
2290//                         .aligned()
2291//                         .left()
2292//                         .flex(1., true),
2293//                 )
2294//                 .constrained()
2295//                 .with_height(theme.row_height)
2296//                 .contained()
2297//                 .with_style(*theme.channel_row.style_for(is_selected, state))
2298//                 .with_padding_left(theme.channel_row.default_style().padding.left)
2299//         })
2300//         .on_click(MouseButton::Left, move |_, this, cx| {
2301//             this.open_channel_notes(&OpenChannelNotes { channel_id }, cx);
2302//         })
2303//         .with_cursor_style(CursorStyle::PointingHand)
2304//         .into_any()
2305//     }
2306
2307//     fn render_channel_chat(
2308//         &self,
2309//         channel_id: ChannelId,
2310//         theme: &theme::CollabPanel,
2311//         is_selected: bool,
2312//         ix: usize,
2313//         cx: &mut ViewContext<Self>,
2314//     ) -> AnyElement<Self> {
2315//         enum ChannelChat {}
2316//         let host_avatar_width = theme
2317//             .contact_avatar
2318//             .width
2319//             .or(theme.contact_avatar.height)
2320//             .unwrap_or(0.);
2321
2322//         MouseEventHandler::new::<ChannelChat, _>(ix as usize, cx, |state, cx| {
2323//             let tree_branch = *theme.tree_branch.in_state(is_selected).style_for(state);
2324//             let row = theme.project_row.in_state(is_selected).style_for(state);
2325
2326//             Flex::<Self>::row()
2327//                 .with_child(render_tree_branch(
2328//                     tree_branch,
2329//                     &row.name.text,
2330//                     true,
2331//                     vec2f(host_avatar_width, theme.row_height),
2332//                     cx.font_cache(),
2333//                 ))
2334//                 .with_child(
2335//                     Svg::new("icons/conversations.svg")
2336//                         .with_color(theme.channel_hash.color)
2337//                         .constrained()
2338//                         .with_width(theme.channel_hash.width)
2339//                         .aligned()
2340//                         .left(),
2341//                 )
2342//                 .with_child(
2343//                     Label::new("chat", theme.channel_name.text.clone())
2344//                         .contained()
2345//                         .with_style(theme.channel_name.container)
2346//                         .aligned()
2347//                         .left()
2348//                         .flex(1., true),
2349//                 )
2350//                 .constrained()
2351//                 .with_height(theme.row_height)
2352//                 .contained()
2353//                 .with_style(*theme.channel_row.style_for(is_selected, state))
2354//                 .with_padding_left(theme.channel_row.default_style().padding.left)
2355//         })
2356//         .on_click(MouseButton::Left, move |_, this, cx| {
2357//             this.join_channel_chat(&JoinChannelChat { channel_id }, cx);
2358//         })
2359//         .with_cursor_style(CursorStyle::PointingHand)
2360//         .into_any()
2361//     }
2362
2363//     fn render_channel_invite(
2364//         channel: Arc<Channel>,
2365//         channel_store: ModelHandle<ChannelStore>,
2366//         theme: &theme::CollabPanel,
2367//         is_selected: bool,
2368//         cx: &mut ViewContext<Self>,
2369//     ) -> AnyElement<Self> {
2370//         enum Decline {}
2371//         enum Accept {}
2372
2373//         let channel_id = channel.id;
2374//         let is_invite_pending = channel_store
2375//             .read(cx)
2376//             .has_pending_channel_invite_response(&channel);
2377//         let button_spacing = theme.contact_button_spacing;
2378
2379//         Flex::row()
2380//             .with_child(
2381//                 Svg::new("icons/hash.svg")
2382//                     .with_color(theme.channel_hash.color)
2383//                     .constrained()
2384//                     .with_width(theme.channel_hash.width)
2385//                     .aligned()
2386//                     .left(),
2387//             )
2388//             .with_child(
2389//                 Label::new(channel.name.clone(), theme.contact_username.text.clone())
2390//                     .contained()
2391//                     .with_style(theme.contact_username.container)
2392//                     .aligned()
2393//                     .left()
2394//                     .flex(1., true),
2395//             )
2396//             .with_child(
2397//                 MouseEventHandler::new::<Decline, _>(channel.id as usize, cx, |mouse_state, _| {
2398//                     let button_style = if is_invite_pending {
2399//                         &theme.disabled_button
2400//                     } else {
2401//                         theme.contact_button.style_for(mouse_state)
2402//                     };
2403//                     render_icon_button(button_style, "icons/x.svg").aligned()
2404//                 })
2405//                 .with_cursor_style(CursorStyle::PointingHand)
2406//                 .on_click(MouseButton::Left, move |_, this, cx| {
2407//                     this.respond_to_channel_invite(channel_id, false, cx);
2408//                 })
2409//                 .contained()
2410//                 .with_margin_right(button_spacing),
2411//             )
2412//             .with_child(
2413//                 MouseEventHandler::new::<Accept, _>(channel.id as usize, cx, |mouse_state, _| {
2414//                     let button_style = if is_invite_pending {
2415//                         &theme.disabled_button
2416//                     } else {
2417//                         theme.contact_button.style_for(mouse_state)
2418//                     };
2419//                     render_icon_button(button_style, "icons/check.svg")
2420//                         .aligned()
2421//                         .flex_float()
2422//                 })
2423//                 .with_cursor_style(CursorStyle::PointingHand)
2424//                 .on_click(MouseButton::Left, move |_, this, cx| {
2425//                     this.respond_to_channel_invite(channel_id, true, cx);
2426//                 }),
2427//             )
2428//             .constrained()
2429//             .with_height(theme.row_height)
2430//             .contained()
2431//             .with_style(
2432//                 *theme
2433//                     .contact_row
2434//                     .in_state(is_selected)
2435//                     .style_for(&mut Default::default()),
2436//             )
2437//             .with_padding_left(
2438//                 theme.contact_row.default_style().padding.left + theme.channel_indent,
2439//             )
2440//             .into_any()
2441//     }
2442
2443//     fn render_contact_request(
2444//         user: Arc<User>,
2445//         user_store: ModelHandle<UserStore>,
2446//         theme: &theme::CollabPanel,
2447//         is_incoming: bool,
2448//         is_selected: bool,
2449//         cx: &mut ViewContext<Self>,
2450//     ) -> AnyElement<Self> {
2451//         enum Decline {}
2452//         enum Accept {}
2453//         enum Cancel {}
2454
2455//         let mut row = Flex::row()
2456//             .with_children(user.avatar.clone().map(|avatar| {
2457//                 Image::from_data(avatar)
2458//                     .with_style(theme.contact_avatar)
2459//                     .aligned()
2460//                     .left()
2461//             }))
2462//             .with_child(
2463//                 Label::new(
2464//                     user.github_login.clone(),
2465//                     theme.contact_username.text.clone(),
2466//                 )
2467//                 .contained()
2468//                 .with_style(theme.contact_username.container)
2469//                 .aligned()
2470//                 .left()
2471//                 .flex(1., true),
2472//             );
2473
2474//         let user_id = user.id;
2475//         let github_login = user.github_login.clone();
2476//         let is_contact_request_pending = user_store.read(cx).is_contact_request_pending(&user);
2477//         let button_spacing = theme.contact_button_spacing;
2478
2479//         if is_incoming {
2480//             row.add_child(
2481//                 MouseEventHandler::new::<Decline, _>(user.id as usize, cx, |mouse_state, _| {
2482//                     let button_style = if is_contact_request_pending {
2483//                         &theme.disabled_button
2484//                     } else {
2485//                         theme.contact_button.style_for(mouse_state)
2486//                     };
2487//                     render_icon_button(button_style, "icons/x.svg").aligned()
2488//                 })
2489//                 .with_cursor_style(CursorStyle::PointingHand)
2490//                 .on_click(MouseButton::Left, move |_, this, cx| {
2491//                     this.respond_to_contact_request(user_id, false, cx);
2492//                 })
2493//                 .contained()
2494//                 .with_margin_right(button_spacing),
2495//             );
2496
2497//             row.add_child(
2498//                 MouseEventHandler::new::<Accept, _>(user.id as usize, cx, |mouse_state, _| {
2499//                     let button_style = if is_contact_request_pending {
2500//                         &theme.disabled_button
2501//                     } else {
2502//                         theme.contact_button.style_for(mouse_state)
2503//                     };
2504//                     render_icon_button(button_style, "icons/check.svg")
2505//                         .aligned()
2506//                         .flex_float()
2507//                 })
2508//                 .with_cursor_style(CursorStyle::PointingHand)
2509//                 .on_click(MouseButton::Left, move |_, this, cx| {
2510//                     this.respond_to_contact_request(user_id, true, cx);
2511//                 }),
2512//             );
2513//         } else {
2514//             row.add_child(
2515//                 MouseEventHandler::new::<Cancel, _>(user.id as usize, cx, |mouse_state, _| {
2516//                     let button_style = if is_contact_request_pending {
2517//                         &theme.disabled_button
2518//                     } else {
2519//                         theme.contact_button.style_for(mouse_state)
2520//                     };
2521//                     render_icon_button(button_style, "icons/x.svg")
2522//                         .aligned()
2523//                         .flex_float()
2524//                 })
2525//                 .with_padding(Padding::uniform(2.))
2526//                 .with_cursor_style(CursorStyle::PointingHand)
2527//                 .on_click(MouseButton::Left, move |_, this, cx| {
2528//                     this.remove_contact(user_id, &github_login, cx);
2529//                 })
2530//                 .flex_float(),
2531//             );
2532//         }
2533
2534//         row.constrained()
2535//             .with_height(theme.row_height)
2536//             .contained()
2537//             .with_style(
2538//                 *theme
2539//                     .contact_row
2540//                     .in_state(is_selected)
2541//                     .style_for(&mut Default::default()),
2542//             )
2543//             .into_any()
2544//     }
2545
2546//     fn has_subchannels(&self, ix: usize) -> bool {
2547//         self.entries.get(ix).map_or(false, |entry| {
2548//             if let ListEntry::Channel { has_children, .. } = entry {
2549//                 *has_children
2550//             } else {
2551//                 false
2552//             }
2553//         })
2554//     }
2555
2556//     fn deploy_channel_context_menu(
2557//         &mut self,
2558//         position: Option<Vector2F>,
2559//         channel: &Channel,
2560//         ix: usize,
2561//         cx: &mut ViewContext<Self>,
2562//     ) {
2563//         self.context_menu_on_selected = position.is_none();
2564
2565//         let clipboard_channel_name = self.channel_clipboard.as_ref().and_then(|clipboard| {
2566//             self.channel_store
2567//                 .read(cx)
2568//                 .channel_for_id(clipboard.channel_id)
2569//                 .map(|channel| channel.name.clone())
2570//         });
2571
2572//         self.context_menu.update(cx, |context_menu, cx| {
2573//             context_menu.set_position_mode(if self.context_menu_on_selected {
2574//                 OverlayPositionMode::Local
2575//             } else {
2576//                 OverlayPositionMode::Window
2577//             });
2578
2579//             let mut items = Vec::new();
2580
2581//             let select_action_name = if self.selection == Some(ix) {
2582//                 "Unselect"
2583//             } else {
2584//                 "Select"
2585//             };
2586
2587//             items.push(ContextMenuItem::action(
2588//                 select_action_name,
2589//                 ToggleSelectedIx { ix },
2590//             ));
2591
2592//             if self.has_subchannels(ix) {
2593//                 let expand_action_name = if self.is_channel_collapsed(channel.id) {
2594//                     "Expand Subchannels"
2595//                 } else {
2596//                     "Collapse Subchannels"
2597//                 };
2598//                 items.push(ContextMenuItem::action(
2599//                     expand_action_name,
2600//                     ToggleCollapse {
2601//                         location: channel.id,
2602//                     },
2603//                 ));
2604//             }
2605
2606//             items.push(ContextMenuItem::action(
2607//                 "Open Notes",
2608//                 OpenChannelNotes {
2609//                     channel_id: channel.id,
2610//                 },
2611//             ));
2612
2613//             items.push(ContextMenuItem::action(
2614//                 "Open Chat",
2615//                 JoinChannelChat {
2616//                     channel_id: channel.id,
2617//                 },
2618//             ));
2619
2620//             items.push(ContextMenuItem::action(
2621//                 "Copy Channel Link",
2622//                 CopyChannelLink {
2623//                     channel_id: channel.id,
2624//                 },
2625//             ));
2626
2627//             if self.channel_store.read(cx).is_channel_admin(channel.id) {
2628//                 items.extend([
2629//                     ContextMenuItem::Separator,
2630//                     ContextMenuItem::action(
2631//                         "New Subchannel",
2632//                         NewChannel {
2633//                             location: channel.id,
2634//                         },
2635//                     ),
2636//                     ContextMenuItem::action(
2637//                         "Rename",
2638//                         RenameChannel {
2639//                             channel_id: channel.id,
2640//                         },
2641//                     ),
2642//                     ContextMenuItem::action(
2643//                         "Move this channel",
2644//                         StartMoveChannelFor {
2645//                             channel_id: channel.id,
2646//                         },
2647//                     ),
2648//                 ]);
2649
2650//                 if let Some(channel_name) = clipboard_channel_name {
2651//                     items.push(ContextMenuItem::Separator);
2652//                     items.push(ContextMenuItem::action(
2653//                         format!("Move '#{}' here", channel_name),
2654//                         MoveChannel { to: channel.id },
2655//                     ));
2656//                 }
2657
2658//                 items.extend([
2659//                     ContextMenuItem::Separator,
2660//                     ContextMenuItem::action(
2661//                         "Invite Members",
2662//                         InviteMembers {
2663//                             channel_id: channel.id,
2664//                         },
2665//                     ),
2666//                     ContextMenuItem::action(
2667//                         "Manage Members",
2668//                         ManageMembers {
2669//                             channel_id: channel.id,
2670//                         },
2671//                     ),
2672//                     ContextMenuItem::Separator,
2673//                     ContextMenuItem::action(
2674//                         "Delete",
2675//                         RemoveChannel {
2676//                             channel_id: channel.id,
2677//                         },
2678//                     ),
2679//                 ]);
2680//             }
2681
2682//             context_menu.show(
2683//                 position.unwrap_or_default(),
2684//                 if self.context_menu_on_selected {
2685//                     gpui::elements::AnchorCorner::TopRight
2686//                 } else {
2687//                     gpui::elements::AnchorCorner::BottomLeft
2688//                 },
2689//                 items,
2690//                 cx,
2691//             );
2692//         });
2693
2694//         cx.notify();
2695//     }
2696
2697//     fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
2698//         if self.take_editing_state(cx) {
2699//             cx.focus(&self.filter_editor);
2700//         } else {
2701//             self.filter_editor.update(cx, |editor, cx| {
2702//                 if editor.buffer().read(cx).len(cx) > 0 {
2703//                     editor.set_text("", cx);
2704//                 }
2705//             });
2706//         }
2707
2708//         self.update_entries(false, cx);
2709//     }
2710
2711//     fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
2712//         let ix = self.selection.map_or(0, |ix| ix + 1);
2713//         if ix < self.entries.len() {
2714//             self.selection = Some(ix);
2715//         }
2716
2717//         self.list_state.reset(self.entries.len());
2718//         if let Some(ix) = self.selection {
2719//             self.list_state.scroll_to(ListOffset {
2720//                 item_ix: ix,
2721//                 offset_in_item: 0.,
2722//             });
2723//         }
2724//         cx.notify();
2725//     }
2726
2727//     fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
2728//         let ix = self.selection.take().unwrap_or(0);
2729//         if ix > 0 {
2730//             self.selection = Some(ix - 1);
2731//         }
2732
2733//         self.list_state.reset(self.entries.len());
2734//         if let Some(ix) = self.selection {
2735//             self.list_state.scroll_to(ListOffset {
2736//                 item_ix: ix,
2737//                 offset_in_item: 0.,
2738//             });
2739//         }
2740//         cx.notify();
2741//     }
2742
2743//     fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
2744//         if self.confirm_channel_edit(cx) {
2745//             return;
2746//         }
2747
2748//         if let Some(selection) = self.selection {
2749//             if let Some(entry) = self.entries.get(selection) {
2750//                 match entry {
2751//                     ListEntry::Header(section) => match section {
2752//                         Section::ActiveCall => Self::leave_call(cx),
2753//                         Section::Channels => self.new_root_channel(cx),
2754//                         Section::Contacts => self.toggle_contact_finder(cx),
2755//                         Section::ContactRequests
2756//                         | Section::Online
2757//                         | Section::Offline
2758//                         | Section::ChannelInvites => {
2759//                             self.toggle_section_expanded(*section, cx);
2760//                         }
2761//                     },
2762//                     ListEntry::Contact { contact, calling } => {
2763//                         if contact.online && !contact.busy && !calling {
2764//                             self.call(contact.user.id, Some(self.project.clone()), cx);
2765//                         }
2766//                     }
2767//                     ListEntry::ParticipantProject {
2768//                         project_id,
2769//                         host_user_id,
2770//                         ..
2771//                     } => {
2772//                         if let Some(workspace) = self.workspace.upgrade(cx) {
2773//                             let app_state = workspace.read(cx).app_state().clone();
2774//                             workspace::join_remote_project(
2775//                                 *project_id,
2776//                                 *host_user_id,
2777//                                 app_state,
2778//                                 cx,
2779//                             )
2780//                             .detach_and_log_err(cx);
2781//                         }
2782//                     }
2783//                     ListEntry::ParticipantScreen { peer_id, .. } => {
2784//                         let Some(peer_id) = peer_id else {
2785//                             return;
2786//                         };
2787//                         if let Some(workspace) = self.workspace.upgrade(cx) {
2788//                             workspace.update(cx, |workspace, cx| {
2789//                                 workspace.open_shared_screen(*peer_id, cx)
2790//                             });
2791//                         }
2792//                     }
2793//                     ListEntry::Channel { channel, .. } => {
2794//                         let is_active = maybe!({
2795//                             let call_channel = ActiveCall::global(cx)
2796//                                 .read(cx)
2797//                                 .room()?
2798//                                 .read(cx)
2799//                                 .channel_id()?;
2800
2801//                             Some(call_channel == channel.id)
2802//                         })
2803//                         .unwrap_or(false);
2804//                         if is_active {
2805//                             self.open_channel_notes(
2806//                                 &OpenChannelNotes {
2807//                                     channel_id: channel.id,
2808//                                 },
2809//                                 cx,
2810//                             )
2811//                         } else {
2812//                             self.join_channel(channel.id, cx)
2813//                         }
2814//                     }
2815//                     ListEntry::ContactPlaceholder => self.toggle_contact_finder(cx),
2816//                     _ => {}
2817//                 }
2818//             }
2819//         }
2820//     }
2821
2822//     fn insert_space(&mut self, _: &InsertSpace, cx: &mut ViewContext<Self>) {
2823//         if self.channel_editing_state.is_some() {
2824//             self.channel_name_editor.update(cx, |editor, cx| {
2825//                 editor.insert(" ", cx);
2826//             });
2827//         }
2828//     }
2829
2830//     fn confirm_channel_edit(&mut self, cx: &mut ViewContext<CollabPanel>) -> bool {
2831//         if let Some(editing_state) = &mut self.channel_editing_state {
2832//             match editing_state {
2833//                 ChannelEditingState::Create {
2834//                     location,
2835//                     pending_name,
2836//                     ..
2837//                 } => {
2838//                     if pending_name.is_some() {
2839//                         return false;
2840//                     }
2841//                     let channel_name = self.channel_name_editor.read(cx).text(cx);
2842
2843//                     *pending_name = Some(channel_name.clone());
2844
2845//                     self.channel_store
2846//                         .update(cx, |channel_store, cx| {
2847//                             channel_store.create_channel(&channel_name, *location, cx)
2848//                         })
2849//                         .detach();
2850//                     cx.notify();
2851//                 }
2852//                 ChannelEditingState::Rename {
2853//                     location,
2854//                     pending_name,
2855//                 } => {
2856//                     if pending_name.is_some() {
2857//                         return false;
2858//                     }
2859//                     let channel_name = self.channel_name_editor.read(cx).text(cx);
2860//                     *pending_name = Some(channel_name.clone());
2861
2862//                     self.channel_store
2863//                         .update(cx, |channel_store, cx| {
2864//                             channel_store.rename(*location, &channel_name, cx)
2865//                         })
2866//                         .detach();
2867//                     cx.notify();
2868//                 }
2869//             }
2870//             cx.focus_self();
2871//             true
2872//         } else {
2873//             false
2874//         }
2875//     }
2876
2877//     fn toggle_section_expanded(&mut self, section: Section, cx: &mut ViewContext<Self>) {
2878//         if let Some(ix) = self.collapsed_sections.iter().position(|s| *s == section) {
2879//             self.collapsed_sections.remove(ix);
2880//         } else {
2881//             self.collapsed_sections.push(section);
2882//         }
2883//         self.update_entries(false, cx);
2884//     }
2885
2886//     fn collapse_selected_channel(
2887//         &mut self,
2888//         _: &CollapseSelectedChannel,
2889//         cx: &mut ViewContext<Self>,
2890//     ) {
2891//         let Some(channel_id) = self.selected_channel().map(|channel| channel.id) else {
2892//             return;
2893//         };
2894
2895//         if self.is_channel_collapsed(channel_id) {
2896//             return;
2897//         }
2898
2899//         self.toggle_channel_collapsed(channel_id, cx);
2900//     }
2901
2902//     fn expand_selected_channel(&mut self, _: &ExpandSelectedChannel, cx: &mut ViewContext<Self>) {
2903//         let Some(id) = self.selected_channel().map(|channel| channel.id) else {
2904//             return;
2905//         };
2906
2907//         if !self.is_channel_collapsed(id) {
2908//             return;
2909//         }
2910
2911//         self.toggle_channel_collapsed(id, cx)
2912//     }
2913
2914//     fn toggle_channel_collapsed_action(
2915//         &mut self,
2916//         action: &ToggleCollapse,
2917//         cx: &mut ViewContext<Self>,
2918//     ) {
2919//         self.toggle_channel_collapsed(action.location, cx);
2920//     }
2921
2922//     fn toggle_channel_collapsed<'a>(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
2923//         match self.collapsed_channels.binary_search(&channel_id) {
2924//             Ok(ix) => {
2925//                 self.collapsed_channels.remove(ix);
2926//             }
2927//             Err(ix) => {
2928//                 self.collapsed_channels.insert(ix, channel_id);
2929//             }
2930//         };
2931//         self.serialize(cx);
2932//         self.update_entries(true, cx);
2933//         cx.notify();
2934//         cx.focus_self();
2935//     }
2936
2937//     fn is_channel_collapsed(&self, channel_id: ChannelId) -> bool {
2938//         self.collapsed_channels.binary_search(&channel_id).is_ok()
2939//     }
2940
2941//     fn leave_call(cx: &mut ViewContext<Self>) {
2942//         ActiveCall::global(cx)
2943//             .update(cx, |call, cx| call.hang_up(cx))
2944//             .detach_and_log_err(cx);
2945//     }
2946
2947//     fn toggle_contact_finder(&mut self, cx: &mut ViewContext<Self>) {
2948//         if let Some(workspace) = self.workspace.upgrade(cx) {
2949//             workspace.update(cx, |workspace, cx| {
2950//                 workspace.toggle_modal(cx, |_, cx| {
2951//                     cx.add_view(|cx| {
2952//                         let mut finder = ContactFinder::new(self.user_store.clone(), cx);
2953//                         finder.set_query(self.filter_editor.read(cx).text(cx), cx);
2954//                         finder
2955//                     })
2956//                 });
2957//             });
2958//         }
2959//     }
2960
2961//     fn new_root_channel(&mut self, cx: &mut ViewContext<Self>) {
2962//         self.channel_editing_state = Some(ChannelEditingState::Create {
2963//             location: None,
2964//             pending_name: None,
2965//         });
2966//         self.update_entries(false, cx);
2967//         self.select_channel_editor();
2968//         cx.focus(self.channel_name_editor.as_any());
2969//         cx.notify();
2970//     }
2971
2972//     fn select_channel_editor(&mut self) {
2973//         self.selection = self.entries.iter().position(|entry| match entry {
2974//             ListEntry::ChannelEditor { .. } => true,
2975//             _ => false,
2976//         });
2977//     }
2978
2979//     fn new_subchannel(&mut self, action: &NewChannel, cx: &mut ViewContext<Self>) {
2980//         self.collapsed_channels
2981//             .retain(|channel| *channel != action.location);
2982//         self.channel_editing_state = Some(ChannelEditingState::Create {
2983//             location: Some(action.location.to_owned()),
2984//             pending_name: None,
2985//         });
2986//         self.update_entries(false, cx);
2987//         self.select_channel_editor();
2988//         cx.focus(self.channel_name_editor.as_any());
2989//         cx.notify();
2990//     }
2991
2992//     fn invite_members(&mut self, action: &InviteMembers, cx: &mut ViewContext<Self>) {
2993//         self.show_channel_modal(action.channel_id, channel_modal::Mode::InviteMembers, cx);
2994//     }
2995
2996//     fn manage_members(&mut self, action: &ManageMembers, cx: &mut ViewContext<Self>) {
2997//         self.show_channel_modal(action.channel_id, channel_modal::Mode::ManageMembers, cx);
2998//     }
2999
3000//     fn remove(&mut self, _: &Remove, cx: &mut ViewContext<Self>) {
3001//         if let Some(channel) = self.selected_channel() {
3002//             self.remove_channel(channel.id, cx)
3003//         }
3004//     }
3005
3006//     fn rename_selected_channel(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
3007//         if let Some(channel) = self.selected_channel() {
3008//             self.rename_channel(
3009//                 &RenameChannel {
3010//                     channel_id: channel.id,
3011//                 },
3012//                 cx,
3013//             );
3014//         }
3015//     }
3016
3017//     fn rename_channel(&mut self, action: &RenameChannel, cx: &mut ViewContext<Self>) {
3018//         let channel_store = self.channel_store.read(cx);
3019//         if !channel_store.is_channel_admin(action.channel_id) {
3020//             return;
3021//         }
3022//         if let Some(channel) = channel_store.channel_for_id(action.channel_id).cloned() {
3023//             self.channel_editing_state = Some(ChannelEditingState::Rename {
3024//                 location: action.channel_id.to_owned(),
3025//                 pending_name: None,
3026//             });
3027//             self.channel_name_editor.update(cx, |editor, cx| {
3028//                 editor.set_text(channel.name.clone(), cx);
3029//                 editor.select_all(&Default::default(), cx);
3030//             });
3031//             cx.focus(self.channel_name_editor.as_any());
3032//             self.update_entries(false, cx);
3033//             self.select_channel_editor();
3034//         }
3035//     }
3036
3037//     fn open_channel_notes(&mut self, action: &OpenChannelNotes, cx: &mut ViewContext<Self>) {
3038//         if let Some(workspace) = self.workspace.upgrade(cx) {
3039//             ChannelView::open(action.channel_id, workspace, cx).detach();
3040//         }
3041//     }
3042
3043//     fn show_inline_context_menu(&mut self, _: &menu::ShowContextMenu, cx: &mut ViewContext<Self>) {
3044//         let Some(channel) = self.selected_channel() else {
3045//             return;
3046//         };
3047
3048//         self.deploy_channel_context_menu(None, &channel.clone(), self.selection.unwrap(), cx);
3049//     }
3050
3051//     fn selected_channel(&self) -> Option<&Arc<Channel>> {
3052//         self.selection
3053//             .and_then(|ix| self.entries.get(ix))
3054//             .and_then(|entry| match entry {
3055//                 ListEntry::Channel { channel, .. } => Some(channel),
3056//                 _ => None,
3057//             })
3058//     }
3059
3060//     fn show_channel_modal(
3061//         &mut self,
3062//         channel_id: ChannelId,
3063//         mode: channel_modal::Mode,
3064//         cx: &mut ViewContext<Self>,
3065//     ) {
3066//         let workspace = self.workspace.clone();
3067//         let user_store = self.user_store.clone();
3068//         let channel_store = self.channel_store.clone();
3069//         let members = self.channel_store.update(cx, |channel_store, cx| {
3070//             channel_store.get_channel_member_details(channel_id, cx)
3071//         });
3072
3073//         cx.spawn(|_, mut cx| async move {
3074//             let members = members.await?;
3075//             workspace.update(&mut cx, |workspace, cx| {
3076//                 workspace.toggle_modal(cx, |_, cx| {
3077//                     cx.add_view(|cx| {
3078//                         ChannelModal::new(
3079//                             user_store.clone(),
3080//                             channel_store.clone(),
3081//                             channel_id,
3082//                             mode,
3083//                             members,
3084//                             cx,
3085//                         )
3086//                     })
3087//                 });
3088//             })
3089//         })
3090//         .detach();
3091//     }
3092
3093//     fn remove_selected_channel(&mut self, action: &RemoveChannel, cx: &mut ViewContext<Self>) {
3094//         self.remove_channel(action.channel_id, cx)
3095//     }
3096
3097//     fn remove_channel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
3098//         let channel_store = self.channel_store.clone();
3099//         if let Some(channel) = channel_store.read(cx).channel_for_id(channel_id) {
3100//             let prompt_message = format!(
3101//                 "Are you sure you want to remove the channel \"{}\"?",
3102//                 channel.name
3103//             );
3104//             let mut answer =
3105//                 cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
3106//             let window = cx.window();
3107//             cx.spawn(|this, mut cx| async move {
3108//                 if answer.next().await == Some(0) {
3109//                     if let Err(e) = channel_store
3110//                         .update(&mut cx, |channels, _| channels.remove_channel(channel_id))
3111//                         .await
3112//                     {
3113//                         window.prompt(
3114//                             PromptLevel::Info,
3115//                             &format!("Failed to remove channel: {}", e),
3116//                             &["Ok"],
3117//                             &mut cx,
3118//                         );
3119//                     }
3120//                     this.update(&mut cx, |_, cx| cx.focus_self()).ok();
3121//                 }
3122//             })
3123//             .detach();
3124//         }
3125//     }
3126
3127//     // Should move to the filter editor if clicking on it
3128//     // Should move selection to the channel editor if activating it
3129
3130//     fn remove_contact(&mut self, user_id: u64, github_login: &str, cx: &mut ViewContext<Self>) {
3131//         let user_store = self.user_store.clone();
3132//         let prompt_message = format!(
3133//             "Are you sure you want to remove \"{}\" from your contacts?",
3134//             github_login
3135//         );
3136//         let mut answer = cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
3137//         let window = cx.window();
3138//         cx.spawn(|_, mut cx| async move {
3139//             if answer.next().await == Some(0) {
3140//                 if let Err(e) = user_store
3141//                     .update(&mut cx, |store, cx| store.remove_contact(user_id, cx))
3142//                     .await
3143//                 {
3144//                     window.prompt(
3145//                         PromptLevel::Info,
3146//                         &format!("Failed to remove contact: {}", e),
3147//                         &["Ok"],
3148//                         &mut cx,
3149//                     );
3150//                 }
3151//             }
3152//         })
3153//         .detach();
3154//     }
3155
3156//     fn respond_to_contact_request(
3157//         &mut self,
3158//         user_id: u64,
3159//         accept: bool,
3160//         cx: &mut ViewContext<Self>,
3161//     ) {
3162//         self.user_store
3163//             .update(cx, |store, cx| {
3164//                 store.respond_to_contact_request(user_id, accept, cx)
3165//             })
3166//             .detach();
3167//     }
3168
3169//     fn respond_to_channel_invite(
3170//         &mut self,
3171//         channel_id: u64,
3172//         accept: bool,
3173//         cx: &mut ViewContext<Self>,
3174//     ) {
3175//         self.channel_store
3176//             .update(cx, |store, cx| {
3177//                 store.respond_to_channel_invite(channel_id, accept, cx)
3178//             })
3179//             .detach();
3180//     }
3181
3182//     fn call(
3183//         &mut self,
3184//         recipient_user_id: u64,
3185//         initial_project: Option<ModelHandle<Project>>,
3186//         cx: &mut ViewContext<Self>,
3187//     ) {
3188//         ActiveCall::global(cx)
3189//             .update(cx, |call, cx| {
3190//                 call.invite(recipient_user_id, initial_project, cx)
3191//             })
3192//             .detach_and_log_err(cx);
3193//     }
3194
3195//     fn join_channel(&self, channel_id: u64, cx: &mut ViewContext<Self>) {
3196//         let Some(workspace) = self.workspace.upgrade(cx) else {
3197//             return;
3198//         };
3199//         let Some(handle) = cx.window().downcast::<Workspace>() else {
3200//             return;
3201//         };
3202//         workspace::join_channel(
3203//             channel_id,
3204//             workspace.read(cx).app_state().clone(),
3205//             Some(handle),
3206//             cx,
3207//         )
3208//         .detach_and_log_err(cx)
3209//     }
3210
3211//     fn join_channel_chat(&mut self, action: &JoinChannelChat, cx: &mut ViewContext<Self>) {
3212//         let channel_id = action.channel_id;
3213//         if let Some(workspace) = self.workspace.upgrade(cx) {
3214//             cx.app_context().defer(move |cx| {
3215//                 workspace.update(cx, |workspace, cx| {
3216//                     if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
3217//                         panel.update(cx, |panel, cx| {
3218//                             panel
3219//                                 .select_channel(channel_id, None, cx)
3220//                                 .detach_and_log_err(cx);
3221//                         });
3222//                     }
3223//                 });
3224//             });
3225//         }
3226//     }
3227
3228//     fn copy_channel_link(&mut self, action: &CopyChannelLink, cx: &mut ViewContext<Self>) {
3229//         let channel_store = self.channel_store.read(cx);
3230//         let Some(channel) = channel_store.channel_for_id(action.channel_id) else {
3231//             return;
3232//         };
3233//         let item = ClipboardItem::new(channel.link());
3234//         cx.write_to_clipboard(item)
3235//     }
3236// }
3237
3238// fn render_tree_branch(
3239//     branch_style: theme::TreeBranch,
3240//     row_style: &TextStyle,
3241//     is_last: bool,
3242//     size: Vector2F,
3243//     font_cache: &FontCache,
3244// ) -> gpui::elements::ConstrainedBox<CollabPanel> {
3245//     let line_height = row_style.line_height(font_cache);
3246//     let cap_height = row_style.cap_height(font_cache);
3247//     let baseline_offset = row_style.baseline_offset(font_cache) + (size.y() - line_height) / 2.;
3248
3249//     Canvas::new(move |bounds, _, _, cx| {
3250//         cx.paint_layer(None, |cx| {
3251//             let start_x = bounds.min_x() + (bounds.width() / 2.) - (branch_style.width / 2.);
3252//             let end_x = bounds.max_x();
3253//             let start_y = bounds.min_y();
3254//             let end_y = bounds.min_y() + baseline_offset - (cap_height / 2.);
3255
3256//             cx.scene().push_quad(gpui::Quad {
3257//                 bounds: RectF::from_points(
3258//                     vec2f(start_x, start_y),
3259//                     vec2f(
3260//                         start_x + branch_style.width,
3261//                         if is_last { end_y } else { bounds.max_y() },
3262//                     ),
3263//                 ),
3264//                 background: Some(branch_style.color),
3265//                 border: gpui::Border::default(),
3266//                 corner_radii: (0.).into(),
3267//             });
3268//             cx.scene().push_quad(gpui::Quad {
3269//                 bounds: RectF::from_points(
3270//                     vec2f(start_x, end_y),
3271//                     vec2f(end_x, end_y + branch_style.width),
3272//                 ),
3273//                 background: Some(branch_style.color),
3274//                 border: gpui::Border::default(),
3275//                 corner_radii: (0.).into(),
3276//             });
3277//         })
3278//     })
3279//     .constrained()
3280//     .with_width(size.x())
3281// }
3282
3283// impl View for CollabPanel {
3284//     fn ui_name() -> &'static str {
3285//         "CollabPanel"
3286//     }
3287
3288//     fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
3289//         if !self.has_focus {
3290//             self.has_focus = true;
3291//             if !self.context_menu.is_focused(cx) {
3292//                 if let Some(editing_state) = &self.channel_editing_state {
3293//                     if editing_state.pending_name().is_none() {
3294//                         cx.focus(&self.channel_name_editor);
3295//                     } else {
3296//                         cx.focus(&self.filter_editor);
3297//                     }
3298//                 } else {
3299//                     cx.focus(&self.filter_editor);
3300//                 }
3301//             }
3302//             cx.emit(Event::Focus);
3303//         }
3304//     }
3305
3306//     fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
3307//         self.has_focus = false;
3308//     }
3309
3310//     fn render(&mut self, cx: &mut gpui::ViewContext<'_, '_, Self>) -> gpui::AnyElement<Self> {
3311//         let theme = &theme::current(cx).collab_panel;
3312
3313//         if self.user_store.read(cx).current_user().is_none() {
3314//             enum LogInButton {}
3315
3316//             return Flex::column()
3317//                 .with_child(
3318//                     MouseEventHandler::new::<LogInButton, _>(0, cx, |state, _| {
3319//                         let button = theme.log_in_button.style_for(state);
3320//                         Label::new("Sign in to collaborate", button.text.clone())
3321//                             .aligned()
3322//                             .left()
3323//                             .contained()
3324//                             .with_style(button.container)
3325//                     })
3326//                     .on_click(MouseButton::Left, |_, this, cx| {
3327//                         let client = this.client.clone();
3328//                         cx.spawn(|_, cx| async move {
3329//                             client.authenticate_and_connect(true, &cx).await.log_err();
3330//                         })
3331//                         .detach();
3332//                     })
3333//                     .with_cursor_style(CursorStyle::PointingHand),
3334//                 )
3335//                 .contained()
3336//                 .with_style(theme.container)
3337//                 .into_any();
3338//         }
3339
3340//         enum PanelFocus {}
3341//         MouseEventHandler::new::<PanelFocus, _>(0, cx, |_, cx| {
3342//             Stack::new()
3343//                 .with_child(
3344//                     Flex::column()
3345//                         .with_child(
3346//                             Flex::row().with_child(
3347//                                 ChildView::new(&self.filter_editor, cx)
3348//                                     .contained()
3349//                                     .with_style(theme.user_query_editor.container)
3350//                                     .flex(1.0, true),
3351//                             ),
3352//                         )
3353//                         .with_child(List::new(self.list_state.clone()).flex(1., true).into_any())
3354//                         .contained()
3355//                         .with_style(theme.container)
3356//                         .into_any(),
3357//                 )
3358//                 .with_children(
3359//                     (!self.context_menu_on_selected)
3360//                         .then(|| ChildView::new(&self.context_menu, cx)),
3361//                 )
3362//                 .into_any()
3363//         })
3364//         .on_click(MouseButton::Left, |_, _, cx| cx.focus_self())
3365//         .into_any_named("collab panel")
3366//     }
3367
3368//     fn update_keymap_context(
3369//         &self,
3370//         keymap: &mut gpui::keymap_matcher::KeymapContext,
3371//         _: &AppContext,
3372//     ) {
3373//         Self::reset_to_default_keymap_context(keymap);
3374//         if self.channel_editing_state.is_some() {
3375//             keymap.add_identifier("editing");
3376//         } else {
3377//             keymap.add_identifier("not_editing");
3378//         }
3379//     }
3380// }
3381
3382// impl Panel for CollabPanel {
3383//     fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
3384//         settings::get::<CollaborationPanelSettings>(cx).dock
3385//     }
3386
3387//     fn position_is_valid(&self, position: DockPosition) -> bool {
3388//         matches!(position, DockPosition::Left | DockPosition::Right)
3389//     }
3390
3391//     fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
3392//         settings::update_settings_file::<CollaborationPanelSettings>(
3393//             self.fs.clone(),
3394//             cx,
3395//             move |settings| settings.dock = Some(position),
3396//         );
3397//     }
3398
3399//     fn size(&self, cx: &gpui::WindowContext) -> f32 {
3400//         self.width
3401//             .unwrap_or_else(|| settings::get::<CollaborationPanelSettings>(cx).default_width)
3402//     }
3403
3404//     fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
3405//         self.width = size;
3406//         self.serialize(cx);
3407//         cx.notify();
3408//     }
3409
3410//     fn icon_path(&self, cx: &gpui::WindowContext) -> Option<&'static str> {
3411//         settings::get::<CollaborationPanelSettings>(cx)
3412//             .button
3413//             .then(|| "icons/user_group_16.svg")
3414//     }
3415
3416//     fn icon_tooltip(&self) -> (String, Option<Box<dyn gpui::Action>>) {
3417//         (
3418//             "Collaboration Panel".to_string(),
3419//             Some(Box::new(ToggleFocus)),
3420//         )
3421//     }
3422
3423//     fn should_change_position_on_event(event: &Self::Event) -> bool {
3424//         matches!(event, Event::DockPositionChanged)
3425//     }
3426
3427//     fn has_focus(&self, _cx: &gpui::WindowContext) -> bool {
3428//         self.has_focus
3429//     }
3430
3431//     fn is_focus_event(event: &Self::Event) -> bool {
3432//         matches!(event, Event::Focus)
3433//     }
3434// }
3435
3436// impl PartialEq for ListEntry {
3437//     fn eq(&self, other: &Self) -> bool {
3438//         match self {
3439//             ListEntry::Header(section_1) => {
3440//                 if let ListEntry::Header(section_2) = other {
3441//                     return section_1 == section_2;
3442//                 }
3443//             }
3444//             ListEntry::CallParticipant { user: user_1, .. } => {
3445//                 if let ListEntry::CallParticipant { user: user_2, .. } = other {
3446//                     return user_1.id == user_2.id;
3447//                 }
3448//             }
3449//             ListEntry::ParticipantProject {
3450//                 project_id: project_id_1,
3451//                 ..
3452//             } => {
3453//                 if let ListEntry::ParticipantProject {
3454//                     project_id: project_id_2,
3455//                     ..
3456//                 } = other
3457//                 {
3458//                     return project_id_1 == project_id_2;
3459//                 }
3460//             }
3461//             ListEntry::ParticipantScreen {
3462//                 peer_id: peer_id_1, ..
3463//             } => {
3464//                 if let ListEntry::ParticipantScreen {
3465//                     peer_id: peer_id_2, ..
3466//                 } = other
3467//                 {
3468//                     return peer_id_1 == peer_id_2;
3469//                 }
3470//             }
3471//             ListEntry::Channel {
3472//                 channel: channel_1, ..
3473//             } => {
3474//                 if let ListEntry::Channel {
3475//                     channel: channel_2, ..
3476//                 } = other
3477//                 {
3478//                     return channel_1.id == channel_2.id;
3479//                 }
3480//             }
3481//             ListEntry::ChannelNotes { channel_id } => {
3482//                 if let ListEntry::ChannelNotes {
3483//                     channel_id: other_id,
3484//                 } = other
3485//                 {
3486//                     return channel_id == other_id;
3487//                 }
3488//             }
3489//             ListEntry::ChannelChat { channel_id } => {
3490//                 if let ListEntry::ChannelChat {
3491//                     channel_id: other_id,
3492//                 } = other
3493//                 {
3494//                     return channel_id == other_id;
3495//                 }
3496//             }
3497//             ListEntry::ChannelInvite(channel_1) => {
3498//                 if let ListEntry::ChannelInvite(channel_2) = other {
3499//                     return channel_1.id == channel_2.id;
3500//                 }
3501//             }
3502//             ListEntry::IncomingRequest(user_1) => {
3503//                 if let ListEntry::IncomingRequest(user_2) = other {
3504//                     return user_1.id == user_2.id;
3505//                 }
3506//             }
3507//             ListEntry::OutgoingRequest(user_1) => {
3508//                 if let ListEntry::OutgoingRequest(user_2) = other {
3509//                     return user_1.id == user_2.id;
3510//                 }
3511//             }
3512//             ListEntry::Contact {
3513//                 contact: contact_1, ..
3514//             } => {
3515//                 if let ListEntry::Contact {
3516//                     contact: contact_2, ..
3517//                 } = other
3518//                 {
3519//                     return contact_1.user.id == contact_2.user.id;
3520//                 }
3521//             }
3522//             ListEntry::ChannelEditor { depth } => {
3523//                 if let ListEntry::ChannelEditor { depth: other_depth } = other {
3524//                     return depth == other_depth;
3525//                 }
3526//             }
3527//             ListEntry::ContactPlaceholder => {
3528//                 if let ListEntry::ContactPlaceholder = other {
3529//                     return true;
3530//                 }
3531//             }
3532//         }
3533//         false
3534//     }
3535// }
3536
3537// fn render_icon_button(style: &IconButton, svg_path: &'static str) -> impl Element<CollabPanel> {
3538//     Svg::new(svg_path)
3539//         .with_color(style.color)
3540//         .constrained()
3541//         .with_width(style.icon_width)
3542//         .aligned()
3543//         .constrained()
3544//         .with_width(style.button_width)
3545//         .with_height(style.button_width)
3546//         .contained()
3547//         .with_style(style.container)
3548// }