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