disconnected_overlay.rs

  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}