1use dev_server_projects::DevServer;
2use gpui::{ClickEvent, DismissEvent, EventEmitter, FocusHandle, FocusableView, Render, WeakView};
3use ui::{
4 div, h_flex, rems, Button, ButtonCommon, ButtonStyle, Clickable, ElevationIndex, FluentBuilder,
5 Headline, HeadlineSize, IconName, IconPosition, InteractiveElement, IntoElement, Label, Modal,
6 ModalFooter, ModalHeader, ParentElement, Section, Styled, StyledExt, ViewContext,
7};
8use workspace::{notifications::DetachAndPromptErr, ModalView, Workspace};
9
10use crate::{
11 dev_servers::reconnect_to_dev_server_project, open_dev_server_project, DevServerProjects,
12};
13
14pub struct DisconnectedOverlay {
15 workspace: WeakView<Workspace>,
16 dev_server: Option<DevServer>,
17 focus_handle: FocusHandle,
18}
19
20impl EventEmitter<DismissEvent> for DisconnectedOverlay {}
21impl FocusableView for DisconnectedOverlay {
22 fn focus_handle(&self, _cx: &gpui::AppContext) -> gpui::FocusHandle {
23 self.focus_handle.clone()
24 }
25}
26impl ModalView for DisconnectedOverlay {
27 fn fade_out_background(&self) -> bool {
28 true
29 }
30}
31
32impl DisconnectedOverlay {
33 pub fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
34 cx.subscribe(workspace.project(), |workspace, project, event, cx| {
35 if !matches!(event, project::Event::DisconnectedFromHost) {
36 return;
37 }
38 let handle = cx.view().downgrade();
39 let dev_server = project
40 .read(cx)
41 .dev_server_project_id()
42 .and_then(|id| {
43 dev_server_projects::Store::global(cx)
44 .read(cx)
45 .dev_server_for_project(id)
46 })
47 .cloned();
48 workspace.toggle_modal(cx, |cx| DisconnectedOverlay {
49 workspace: handle,
50 dev_server,
51 focus_handle: cx.focus_handle(),
52 });
53 })
54 .detach();
55 }
56
57 fn handle_reconnect(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
58 cx.emit(DismissEvent);
59 let Some(workspace) = self.workspace.upgrade() else {
60 return;
61 };
62 let Some(dev_server) = self.dev_server.clone() else {
63 return;
64 };
65 let Some(dev_server_project_id) = workspace
66 .read(cx)
67 .project()
68 .read(cx)
69 .dev_server_project_id()
70 else {
71 return;
72 };
73
74 if let Some(project_id) = dev_server_projects::Store::global(cx)
75 .read(cx)
76 .dev_server_project(dev_server_project_id)
77 .and_then(|project| project.project_id)
78 {
79 return workspace.update(cx, move |_, cx| {
80 open_dev_server_project(true, dev_server_project_id, project_id, cx)
81 .detach_and_prompt_err("Failed to reconnect", cx, |_, _| None)
82 });
83 }
84
85 if dev_server.ssh_connection_string.is_some() {
86 let task = workspace.update(cx, |_, cx| {
87 reconnect_to_dev_server_project(
88 cx.view().clone(),
89 dev_server,
90 dev_server_project_id,
91 true,
92 cx,
93 )
94 });
95
96 task.detach_and_prompt_err("Failed to reconnect", cx, |_, _| None);
97 } else {
98 return workspace.update(cx, |workspace, cx| {
99 let handle = cx.view().downgrade();
100 workspace.toggle_modal(cx, |cx| DevServerProjects::new(cx, handle))
101 });
102 }
103 }
104
105 fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
106 cx.emit(DismissEvent)
107 }
108}
109
110impl Render for DisconnectedOverlay {
111 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
112 div()
113 .track_focus(&self.focus_handle)
114 .elevation_3(cx)
115 .on_action(cx.listener(Self::cancel))
116 .occlude()
117 .w(rems(24.))
118 .max_h(rems(40.))
119 .child(
120 Modal::new("disconnected", None)
121 .header(
122 ModalHeader::new()
123 .show_dismiss_button(true)
124 .child(Headline::new("Disconnected").size(HeadlineSize::Small)),
125 )
126 .section(Section::new().child(Label::new(
127 "Your connection to the remote project has been lost.",
128 )))
129 .footer(
130 ModalFooter::new().end_slot(
131 h_flex()
132 .gap_2()
133 .child(
134 Button::new("close-window", "Close Window")
135 .style(ButtonStyle::Filled)
136 .layer(ElevationIndex::ModalSurface)
137 .on_click(cx.listener(move |_, _, cx| {
138 cx.remove_window();
139 })),
140 )
141 .when_some(self.dev_server.clone(), |el, _| {
142 el.child(
143 Button::new("reconnect", "Reconnect")
144 .style(ButtonStyle::Filled)
145 .layer(ElevationIndex::ModalSurface)
146 .icon(IconName::ArrowCircle)
147 .icon_position(IconPosition::Start)
148 .on_click(cx.listener(Self::handle_reconnect)),
149 )
150 }),
151 ),
152 ),
153 )
154 }
155}