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