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