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