1use crate::face_pile::FacePile;
2use call::{ActiveCall, ParticipantLocation, Room};
3use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore};
4use gpui::{
5 actions, canvas, div, point, px, rems, AppContext, Div, Element, Hsla, InteractiveElement,
6 IntoElement, Model, ParentElement, Path, Render, Stateful, StatefulInteractiveElement, Styled,
7 Subscription, ViewContext, VisualContext, WeakView, WindowBounds,
8};
9use project::{Project, RepositoryEntry};
10use std::sync::Arc;
11use theme::{ActiveTheme, PlayerColors};
12use ui::{
13 h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon,
14 IconButton, IconElement, KeyBinding, Tooltip,
15};
16use util::ResultExt;
17use workspace::{notifications::NotifyResultExt, Workspace};
18
19const MAX_PROJECT_NAME_LENGTH: usize = 40;
20const MAX_BRANCH_NAME_LENGTH: usize = 40;
21
22actions!(
23 collab,
24 [
25 ShareProject,
26 UnshareProject,
27 ToggleUserMenu,
28 ToggleProjectMenu,
29 SwitchBranch
30 ]
31);
32
33pub fn init(cx: &mut AppContext) {
34 cx.observe_new_views(|workspace: &mut Workspace, cx| {
35 let titlebar_item = cx.build_view(|cx| CollabTitlebarItem::new(workspace, cx));
36 workspace.set_titlebar_item(titlebar_item.into(), cx)
37 })
38 .detach();
39 // cx.add_action(CollabTitlebarItem::share_project);
40 // cx.add_action(CollabTitlebarItem::unshare_project);
41 // cx.add_action(CollabTitlebarItem::toggle_user_menu);
42 // cx.add_action(CollabTitlebarItem::toggle_vcs_menu);
43 // cx.add_action(CollabTitlebarItem::toggle_project_menu);
44}
45
46pub struct CollabTitlebarItem {
47 project: Model<Project>,
48 user_store: Model<UserStore>,
49 client: Arc<Client>,
50 workspace: WeakView<Workspace>,
51 //branch_popover: Option<ViewHandle<BranchList>>,
52 //project_popover: Option<ViewHandle<recent_projects::RecentProjects>>,
53 //user_menu: ViewHandle<ContextMenu>,
54 _subscriptions: Vec<Subscription>,
55}
56
57impl Render for CollabTitlebarItem {
58 type Element = Stateful<Div>;
59
60 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
61 let room = ActiveCall::global(cx).read(cx).room().cloned();
62 let current_user = self.user_store.read(cx).current_user();
63 let client = self.client.clone();
64 let project_id = self.project.read(cx).remote_id();
65
66 h_stack()
67 .id("titlebar")
68 .justify_between()
69 .w_full()
70 .h(rems(1.75))
71 // Set a non-scaling min-height here to ensure the titlebar is
72 // always at least the height of the traffic lights.
73 .min_h(px(32.))
74 .when(
75 !matches!(cx.window_bounds(), WindowBounds::Fullscreen),
76 // Use pixels here instead of a rem-based size because the macOS traffic
77 // lights are a static size, and don't scale with the rest of the UI.
78 |s| s.pl(px(68.)),
79 )
80 .bg(cx.theme().colors().title_bar_background)
81 .on_click(|event, cx| {
82 if event.up.click_count == 2 {
83 cx.zoom_window();
84 }
85 })
86 // left side
87 .child(
88 h_stack()
89 .gap_1()
90 .children(self.render_project_host(cx))
91 .child(self.render_project_name(cx))
92 .children(self.render_project_branch(cx))
93 .when_some(
94 current_user.clone().zip(client.peer_id()).zip(room.clone()),
95 |this, ((current_user, peer_id), room)| {
96 let player_colors = cx.theme().players();
97 let room = room.read(cx);
98 let mut remote_participants =
99 room.remote_participants().values().collect::<Vec<_>>();
100 remote_participants.sort_by_key(|p| p.participant_index.0);
101
102 this.children(self.render_collaborator(
103 ¤t_user,
104 peer_id,
105 true,
106 room.is_speaking(),
107 room.is_muted(cx),
108 &room,
109 project_id,
110 ¤t_user,
111 ))
112 .children(
113 remote_participants.iter().filter_map(|collaborator| {
114 let is_present = project_id.map_or(false, |project_id| {
115 collaborator.location
116 == ParticipantLocation::SharedProject { project_id }
117 });
118
119 let face_pile = self.render_collaborator(
120 &collaborator.user,
121 collaborator.peer_id,
122 is_present,
123 collaborator.speaking,
124 collaborator.muted,
125 &room,
126 project_id,
127 ¤t_user,
128 )?;
129
130 Some(
131 v_stack()
132 .id(("collaborator", collaborator.user.id))
133 .child(face_pile)
134 .child(render_color_ribbon(
135 collaborator.participant_index,
136 player_colors,
137 ))
138 .cursor_pointer()
139 .on_click({
140 let peer_id = collaborator.peer_id;
141 cx.listener(move |this, _, cx| {
142 this.workspace
143 .update(cx, |workspace, cx| {
144 workspace.follow(peer_id, cx);
145 })
146 .ok();
147 })
148 })
149 .tooltip({
150 let login = collaborator.user.github_login.clone();
151 move |cx| {
152 Tooltip::text(format!("Follow {login}"), cx)
153 }
154 }),
155 )
156 }),
157 )
158 },
159 ),
160 )
161 // right side
162 .child(
163 h_stack()
164 .gap_1()
165 .when_some(room, |this, room| {
166 let room = room.read(cx);
167 let is_shared = self.project.read(cx).is_shared();
168 let is_muted = room.is_muted(cx);
169 let is_deafened = room.is_deafened().unwrap_or(false);
170
171 this.child(
172 Button::new(
173 "toggle_sharing",
174 if is_shared { "Unshare" } else { "Share" },
175 )
176 .style(ButtonStyle::Subtle)
177 .on_click(cx.listener(
178 move |this, _, cx| {
179 if is_shared {
180 this.unshare_project(&Default::default(), cx);
181 } else {
182 this.share_project(&Default::default(), cx);
183 }
184 },
185 )),
186 )
187 .child(
188 IconButton::new("leave-call", ui::Icon::Exit)
189 .style(ButtonStyle::Subtle)
190 .on_click(move |_, cx| {
191 ActiveCall::global(cx)
192 .update(cx, |call, cx| call.hang_up(cx))
193 .detach_and_log_err(cx);
194 }),
195 )
196 .child(
197 IconButton::new(
198 "mute-microphone",
199 if is_muted {
200 ui::Icon::MicMute
201 } else {
202 ui::Icon::Mic
203 },
204 )
205 .style(ButtonStyle::Subtle)
206 .selected(is_muted)
207 .on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
208 )
209 .child(
210 IconButton::new(
211 "mute-sound",
212 if is_deafened {
213 ui::Icon::AudioOff
214 } else {
215 ui::Icon::AudioOn
216 },
217 )
218 .style(ButtonStyle::Subtle)
219 .selected(is_deafened.clone())
220 .tooltip(move |cx| {
221 Tooltip::with_meta("Deafen Audio", None, "Mic will be muted", cx)
222 })
223 .on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
224 )
225 .child(
226 IconButton::new("screen-share", ui::Icon::Screen)
227 .style(ButtonStyle::Subtle)
228 .on_click(move |_, cx| {
229 crate::toggle_screen_sharing(&Default::default(), cx)
230 }),
231 )
232 })
233 .child(h_stack().px_1p5().map(|this| {
234 if let Some(user) = current_user {
235 // TODO: Finish implementing user menu popover
236 //
237 this.child(
238 popover_menu("user-menu")
239 .menu(|cx| {
240 ContextMenu::build(cx, |menu, _| menu.header("ADADA"))
241 })
242 .trigger(
243 ButtonLike::new("user-menu")
244 .child(
245 h_stack()
246 .gap_0p5()
247 .child(Avatar::new(user.avatar_uri.clone()))
248 .child(
249 IconElement::new(Icon::ChevronDown)
250 .color(Color::Muted),
251 ),
252 )
253 .style(ButtonStyle::Subtle)
254 .tooltip(move |cx| {
255 Tooltip::text("Toggle User Menu", cx)
256 }),
257 )
258 .anchor(gpui::AnchorCorner::TopRight),
259 )
260 // this.child(
261 // ButtonLike::new("user-menu")
262 // .child(
263 // h_stack().gap_0p5().child(Avatar::data(avatar)).child(
264 // IconElement::new(Icon::ChevronDown).color(Color::Muted),
265 // ),
266 // )
267 // .style(ButtonStyle::Subtle)
268 // .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),
269 // )
270 } else {
271 this.child(Button::new("sign_in", "Sign in").on_click(move |_, cx| {
272 let client = client.clone();
273 cx.spawn(move |mut cx| async move {
274 client
275 .authenticate_and_connect(true, &cx)
276 .await
277 .notify_async_err(&mut cx);
278 })
279 .detach();
280 }))
281 }
282 })),
283 )
284 }
285}
286
287fn render_color_ribbon(participant_index: ParticipantIndex, colors: &PlayerColors) -> gpui::Canvas {
288 let color = colors.color_for_participant(participant_index.0).cursor;
289 canvas(move |bounds, cx| {
290 let mut path = Path::new(bounds.lower_left());
291 let height = bounds.size.height;
292 path.curve_to(bounds.origin + point(height, px(0.)), bounds.origin);
293 path.line_to(bounds.upper_right() - point(height, px(0.)));
294 path.curve_to(bounds.lower_right(), bounds.upper_right());
295 path.line_to(bounds.lower_left());
296 cx.paint_path(path, color);
297 })
298 .h_1()
299 .w_full()
300}
301
302impl CollabTitlebarItem {
303 pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
304 let project = workspace.project().clone();
305 let user_store = workspace.app_state().user_store.clone();
306 let client = workspace.app_state().client.clone();
307 let active_call = ActiveCall::global(cx);
308 let mut subscriptions = Vec::new();
309 subscriptions.push(
310 cx.observe(&workspace.weak_handle().upgrade().unwrap(), |_, _, cx| {
311 cx.notify()
312 }),
313 );
314 subscriptions.push(cx.observe(&project, |_, _, cx| cx.notify()));
315 subscriptions.push(cx.observe(&active_call, |this, _, cx| this.active_call_changed(cx)));
316 subscriptions.push(cx.observe_window_activation(Self::window_activation_changed));
317 subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify()));
318
319 Self {
320 workspace: workspace.weak_handle(),
321 project,
322 user_store,
323 client,
324 // user_menu: cx.add_view(|cx| {
325 // let view_id = cx.view_id();
326 // let mut menu = ContextMenu::new(view_id, cx);
327 // menu.set_position_mode(OverlayPositionMode::Local);
328 // menu
329 // }),
330 // branch_popover: None,
331 // project_popover: None,
332 _subscriptions: subscriptions,
333 }
334 }
335
336 // resolve if you are in a room -> render_project_owner
337 // render_project_owner -> resolve if you are in a room -> Option<foo>
338
339 pub fn render_project_host(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
340 let host = self.project.read(cx).host()?;
341 let host = self.user_store.read(cx).get_cached_user(host.user_id)?;
342 let participant_index = self
343 .user_store
344 .read(cx)
345 .participant_indices()
346 .get(&host.id)?;
347 Some(
348 div().border().border_color(gpui::red()).child(
349 Button::new("project_owner_trigger", host.github_login.clone())
350 .color(Color::Player(participant_index.0))
351 .style(ButtonStyle::Subtle)
352 .tooltip(move |cx| Tooltip::text("Toggle following", cx)),
353 ),
354 )
355 }
356
357 pub fn render_project_name(&self, cx: &mut ViewContext<Self>) -> impl Element {
358 let name = {
359 let mut names = self.project.read(cx).visible_worktrees(cx).map(|worktree| {
360 let worktree = worktree.read(cx);
361 worktree.root_name()
362 });
363
364 names.next().unwrap_or("")
365 };
366
367 let name = util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH);
368
369 div().border().border_color(gpui::red()).child(
370 Button::new("project_name_trigger", name)
371 .style(ButtonStyle::Subtle)
372 .tooltip(move |cx| Tooltip::text("Recent Projects", cx)),
373 )
374 }
375
376 pub fn render_project_branch(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
377 let entry = {
378 let mut names_and_branches =
379 self.project.read(cx).visible_worktrees(cx).map(|worktree| {
380 let worktree = worktree.read(cx);
381 worktree.root_git_entry()
382 });
383
384 names_and_branches.next().flatten()
385 };
386
387 let branch_name = entry
388 .as_ref()
389 .and_then(RepositoryEntry::branch)
390 .map(|branch| util::truncate_and_trailoff(&branch, MAX_BRANCH_NAME_LENGTH))?;
391
392 Some(
393 div().border().border_color(gpui::red()).child(
394 Button::new("project_branch_trigger", branch_name)
395 .style(ButtonStyle::Subtle)
396 .tooltip(move |cx| {
397 cx.build_view(|_| {
398 Tooltip::new("Recent Branches")
399 .key_binding(KeyBinding::new(gpui::KeyBinding::new(
400 "cmd-b",
401 // todo!() Replace with real action.
402 gpui::NoAction,
403 None,
404 )))
405 .meta("Local branches only")
406 })
407 .into()
408 }),
409 ),
410 )
411 }
412
413 fn render_collaborator(
414 &self,
415 user: &Arc<User>,
416 peer_id: PeerId,
417 is_present: bool,
418 is_speaking: bool,
419 is_muted: bool,
420 room: &Room,
421 project_id: Option<u64>,
422 current_user: &Arc<User>,
423 ) -> Option<FacePile> {
424 let followers = project_id.map_or(&[] as &[_], |id| room.followers_for(peer_id, id));
425
426 let pile = FacePile::default().child(
427 div()
428 .child(
429 Avatar::new(user.avatar_uri.clone())
430 .grayscale(!is_present)
431 .border_color(if is_speaking {
432 gpui::blue()
433 } else if is_muted {
434 gpui::red()
435 } else {
436 Hsla::default()
437 }),
438 )
439 .children(followers.iter().filter_map(|follower_peer_id| {
440 let follower = room
441 .remote_participants()
442 .values()
443 .find_map(|p| (p.peer_id == *follower_peer_id).then_some(&p.user))
444 .or_else(|| {
445 (self.client.peer_id() == Some(*follower_peer_id))
446 .then_some(current_user)
447 })?
448 .clone();
449
450 Some(div().child(Avatar::new(follower.avatar_uri.clone())))
451 })),
452 );
453
454 Some(pile)
455 }
456
457 fn window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
458 let project = if cx.is_window_active() {
459 Some(self.project.clone())
460 } else {
461 None
462 };
463 ActiveCall::global(cx)
464 .update(cx, |call, cx| call.set_location(project.as_ref(), cx))
465 .detach_and_log_err(cx);
466 }
467
468 fn active_call_changed(&mut self, cx: &mut ViewContext<Self>) {
469 cx.notify();
470 }
471
472 fn share_project(&mut self, _: &ShareProject, cx: &mut ViewContext<Self>) {
473 let active_call = ActiveCall::global(cx);
474 let project = self.project.clone();
475 active_call
476 .update(cx, |call, cx| call.share_project(project, cx))
477 .detach_and_log_err(cx);
478 }
479
480 fn unshare_project(&mut self, _: &UnshareProject, cx: &mut ViewContext<Self>) {
481 let active_call = ActiveCall::global(cx);
482 let project = self.project.clone();
483 active_call
484 .update(cx, |call, cx| call.unshare_project(project, cx))
485 .log_err();
486 }
487
488 // pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext<Self>) {
489 // self.user_menu.update(cx, |user_menu, cx| {
490 // let items = if let Some(_) = self.user_store.read(cx).current_user() {
491 // vec![
492 // ContextMenuItem::action("Settings", zed_actions::OpenSettings),
493 // ContextMenuItem::action("Theme", theme_selector::Toggle),
494 // ContextMenuItem::separator(),
495 // ContextMenuItem::action(
496 // "Share Feedback",
497 // feedback::feedback_editor::GiveFeedback,
498 // ),
499 // ContextMenuItem::action("Sign Out", SignOut),
500 // ]
501 // } else {
502 // vec![
503 // ContextMenuItem::action("Settings", zed_actions::OpenSettings),
504 // ContextMenuItem::action("Theme", theme_selector::Toggle),
505 // ContextMenuItem::separator(),
506 // ContextMenuItem::action(
507 // "Share Feedback",
508 // feedback::feedback_editor::GiveFeedback,
509 // ),
510 // ]
511 // };
512 // user_menu.toggle(Default::default(), AnchorCorner::TopRight, items, cx);
513 // });
514 // }
515
516 // fn render_branches_popover_host<'a>(
517 // &'a self,
518 // _theme: &'a theme::Titlebar,
519 // cx: &'a mut ViewContext<Self>,
520 // ) -> Option<AnyElement<Self>> {
521 // self.branch_popover.as_ref().map(|child| {
522 // let theme = theme::current(cx).clone();
523 // let child = ChildView::new(child, cx);
524 // let child = MouseEventHandler::new::<BranchList, _>(0, cx, |_, _| {
525 // child
526 // .flex(1., true)
527 // .contained()
528 // .constrained()
529 // .with_width(theme.titlebar.menu.width)
530 // .with_height(theme.titlebar.menu.height)
531 // })
532 // .on_click(MouseButton::Left, |_, _, _| {})
533 // .on_down_out(MouseButton::Left, move |_, this, cx| {
534 // this.branch_popover.take();
535 // cx.emit(());
536 // cx.notify();
537 // })
538 // .contained()
539 // .into_any();
540
541 // Overlay::new(child)
542 // .with_fit_mode(OverlayFitMode::SwitchAnchor)
543 // .with_anchor_corner(AnchorCorner::TopLeft)
544 // .with_z_index(999)
545 // .aligned()
546 // .bottom()
547 // .left()
548 // .into_any()
549 // })
550 // }
551
552 // fn render_project_popover_host<'a>(
553 // &'a self,
554 // _theme: &'a theme::Titlebar,
555 // cx: &'a mut ViewContext<Self>,
556 // ) -> Option<AnyElement<Self>> {
557 // self.project_popover.as_ref().map(|child| {
558 // let theme = theme::current(cx).clone();
559 // let child = ChildView::new(child, cx);
560 // let child = MouseEventHandler::new::<RecentProjects, _>(0, cx, |_, _| {
561 // child
562 // .flex(1., true)
563 // .contained()
564 // .constrained()
565 // .with_width(theme.titlebar.menu.width)
566 // .with_height(theme.titlebar.menu.height)
567 // })
568 // .on_click(MouseButton::Left, |_, _, _| {})
569 // .on_down_out(MouseButton::Left, move |_, this, cx| {
570 // this.project_popover.take();
571 // cx.emit(());
572 // cx.notify();
573 // })
574 // .into_any();
575
576 // Overlay::new(child)
577 // .with_fit_mode(OverlayFitMode::SwitchAnchor)
578 // .with_anchor_corner(AnchorCorner::TopLeft)
579 // .with_z_index(999)
580 // .aligned()
581 // .bottom()
582 // .left()
583 // .into_any()
584 // })
585 // }
586
587 // pub fn toggle_vcs_menu(&mut self, _: &ToggleVcsMenu, cx: &mut ViewContext<Self>) {
588 // if self.branch_popover.take().is_none() {
589 // if let Some(workspace) = self.workspace.upgrade(cx) {
590 // let Some(view) =
591 // cx.add_option_view(|cx| build_branch_list(workspace, cx).log_err())
592 // else {
593 // return;
594 // };
595 // cx.subscribe(&view, |this, _, event, cx| {
596 // match event {
597 // PickerEvent::Dismiss => {
598 // this.branch_popover = None;
599 // }
600 // }
601
602 // cx.notify();
603 // })
604 // .detach();
605 // self.project_popover.take();
606 // cx.focus(&view);
607 // self.branch_popover = Some(view);
608 // }
609 // }
610
611 // cx.notify();
612 // }
613
614 // pub fn toggle_project_menu(&mut self, _: &ToggleProjectMenu, cx: &mut ViewContext<Self>) {
615 // let workspace = self.workspace.clone();
616 // if self.project_popover.take().is_none() {
617 // cx.spawn(|this, mut cx| async move {
618 // let workspaces = WORKSPACE_DB
619 // .recent_workspaces_on_disk()
620 // .await
621 // .unwrap_or_default()
622 // .into_iter()
623 // .map(|(_, location)| location)
624 // .collect();
625
626 // let workspace = workspace.clone();
627 // this.update(&mut cx, move |this, cx| {
628 // let view = cx.add_view(|cx| build_recent_projects(workspace, workspaces, cx));
629
630 // cx.subscribe(&view, |this, _, event, cx| {
631 // match event {
632 // PickerEvent::Dismiss => {
633 // this.project_popover = None;
634 // }
635 // }
636
637 // cx.notify();
638 // })
639 // .detach();
640 // cx.focus(&view);
641 // this.branch_popover.take();
642 // this.project_popover = Some(view);
643 // cx.notify();
644 // })
645 // .log_err();
646 // })
647 // .detach();
648 // }
649 // cx.notify();
650 // }
651
652 // fn render_user_menu_button(
653 // &self,
654 // theme: &Theme,
655 // avatar: Option<Arc<ImageData>>,
656 // cx: &mut ViewContext<Self>,
657 // ) -> AnyElement<Self> {
658 // let tooltip = theme.tooltip.clone();
659 // let user_menu_button_style = if avatar.is_some() {
660 // &theme.titlebar.user_menu.user_menu_button_online
661 // } else {
662 // &theme.titlebar.user_menu.user_menu_button_offline
663 // };
664
665 // let avatar_style = &user_menu_button_style.avatar;
666 // Stack::new()
667 // .with_child(
668 // MouseEventHandler::new::<ToggleUserMenu, _>(0, cx, |state, _| {
669 // let style = user_menu_button_style
670 // .user_menu
671 // .inactive_state()
672 // .style_for(state);
673
674 // let mut dropdown = Flex::row().align_children_center();
675
676 // if let Some(avatar_img) = avatar {
677 // dropdown = dropdown.with_child(Self::render_face(
678 // avatar_img,
679 // *avatar_style,
680 // Color::transparent_black(),
681 // None,
682 // ));
683 // };
684
685 // dropdown
686 // .with_child(
687 // Svg::new("icons/caret_down.svg")
688 // .with_color(user_menu_button_style.icon.color)
689 // .constrained()
690 // .with_width(user_menu_button_style.icon.width)
691 // .contained()
692 // .into_any(),
693 // )
694 // .aligned()
695 // .constrained()
696 // .with_height(style.width)
697 // .contained()
698 // .with_style(style.container)
699 // .into_any()
700 // })
701 // .with_cursor_style(CursorStyle::PointingHand)
702 // .on_down(MouseButton::Left, move |_, this, cx| {
703 // this.user_menu.update(cx, |menu, _| menu.delay_cancel());
704 // })
705 // .on_click(MouseButton::Left, move |_, this, cx| {
706 // this.toggle_user_menu(&Default::default(), cx)
707 // })
708 // .with_tooltip::<ToggleUserMenu>(
709 // 0,
710 // "Toggle User Menu".to_owned(),
711 // Some(Box::new(ToggleUserMenu)),
712 // tooltip,
713 // cx,
714 // )
715 // .contained(),
716 // )
717 // .with_child(
718 // ChildView::new(&self.user_menu, cx)
719 // .aligned()
720 // .bottom()
721 // .right(),
722 // )
723 // .into_any()
724 // }
725
726 // fn render_sign_in_button(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
727 // let titlebar = &theme.titlebar;
728 // MouseEventHandler::new::<SignIn, _>(0, cx, |state, _| {
729 // let style = titlebar.sign_in_button.inactive_state().style_for(state);
730 // Label::new("Sign In", style.text.clone())
731 // .contained()
732 // .with_style(style.container)
733 // })
734 // .with_cursor_style(CursorStyle::PointingHand)
735 // .on_click(MouseButton::Left, move |_, this, cx| {
736 // let client = this.client.clone();
737 // cx.app_context()
738 // .spawn(|cx| async move { client.authenticate_and_connect(true, &cx).await })
739 // .detach_and_log_err(cx);
740 // })
741 // .into_any()
742 // }
743
744 // fn render_connection_status(
745 // &self,
746 // status: &client::Status,
747 // cx: &mut ViewContext<Self>,
748 // ) -> Option<AnyElement<Self>> {
749 // enum ConnectionStatusButton {}
750
751 // let theme = &theme::current(cx).clone();
752 // match status {
753 // client::Status::ConnectionError
754 // | client::Status::ConnectionLost
755 // | client::Status::Reauthenticating { .. }
756 // | client::Status::Reconnecting { .. }
757 // | client::Status::ReconnectionError { .. } => Some(
758 // Svg::new("icons/disconnected.svg")
759 // .with_color(theme.titlebar.offline_icon.color)
760 // .constrained()
761 // .with_width(theme.titlebar.offline_icon.width)
762 // .aligned()
763 // .contained()
764 // .with_style(theme.titlebar.offline_icon.container)
765 // .into_any(),
766 // ),
767 // client::Status::UpgradeRequired => {
768 // let auto_updater = auto_update::AutoUpdater::get(cx);
769 // let label = match auto_updater.map(|auto_update| auto_update.read(cx).status()) {
770 // Some(AutoUpdateStatus::Updated) => "Please restart Zed to Collaborate",
771 // Some(AutoUpdateStatus::Installing)
772 // | Some(AutoUpdateStatus::Downloading)
773 // | Some(AutoUpdateStatus::Checking) => "Updating...",
774 // Some(AutoUpdateStatus::Idle) | Some(AutoUpdateStatus::Errored) | None => {
775 // "Please update Zed to Collaborate"
776 // }
777 // };
778
779 // Some(
780 // MouseEventHandler::new::<ConnectionStatusButton, _>(0, cx, |_, _| {
781 // Label::new(label, theme.titlebar.outdated_warning.text.clone())
782 // .contained()
783 // .with_style(theme.titlebar.outdated_warning.container)
784 // .aligned()
785 // })
786 // .with_cursor_style(CursorStyle::PointingHand)
787 // .on_click(MouseButton::Left, |_, _, cx| {
788 // if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) {
789 // if auto_updater.read(cx).status() == AutoUpdateStatus::Updated {
790 // workspace::restart(&Default::default(), cx);
791 // return;
792 // }
793 // }
794 // auto_update::check(&Default::default(), cx);
795 // })
796 // .into_any(),
797 // )
798 // }
799 // _ => None,
800 // }
801 // }
802}