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