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