collab_panel.rs

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