collab_panel.rs

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