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