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