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