collab_panel.rs

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