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