Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T01:34:13.931Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

disconnected_overlay.rs

215 lines · 7.5 KB · rust
1use gpui::{ClickEvent, DismissEvent, EventEmitter, FocusHandle, Focusable, Render, WeakEntity};
2use project::project_settings::ProjectSettings;
3use remote::RemoteConnectionOptions;
4use settings::Settings;
5use ui::{ElevationIndex, Modal, ModalFooter, ModalHeader, Section, prelude::*};
6use workspace::{
7    ModalView, MultiWorkspace, OpenOptions, Workspace, notifications::DetachAndPromptErr,
8};
9
10use crate::open_remote_project;
11
12enum Host {
13    CollabGuestProject,
14    RemoteServerProject(RemoteConnectionOptions, bool),
15}
16
17pub struct DisconnectedOverlay {
18    workspace: WeakEntity<Workspace>,
19    host: Host,
20    focus_handle: FocusHandle,
21    finished: bool,
22}
23
24impl EventEmitter<DismissEvent> for DisconnectedOverlay {}
25impl Focusable for DisconnectedOverlay {
26    fn focus_handle(&self, _cx: &gpui::App) -> gpui::FocusHandle {
27        self.focus_handle.clone()
28    }
29}
30impl ModalView for DisconnectedOverlay {
31    fn on_before_dismiss(
32        &mut self,
33        _window: &mut Window,
34        _: &mut Context<Self>,
35    ) -> workspace::DismissDecision {
36        workspace::DismissDecision::Dismiss(self.finished)
37    }
38    fn fade_out_background(&self) -> bool {
39        true
40    }
41}
42
43impl DisconnectedOverlay {
44    pub fn register(
45        workspace: &mut Workspace,
46        window: Option<&mut Window>,
47        cx: &mut Context<Workspace>,
48    ) {
49        let Some(window) = window else {
50            return;
51        };
52        cx.subscribe_in(
53            workspace.project(),
54            window,
55            |workspace, project, event, window, cx| {
56                if !matches!(
57                    event,
58                    project::Event::DisconnectedFromHost
59                        | project::Event::DisconnectedFromRemote { .. }
60                ) {
61                    return;
62                }
63                let handle = cx.entity().downgrade();
64
65                let remote_connection_options = project.read(cx).remote_connection_options(cx);
66                let host = if let Some(remote_connection_options) = remote_connection_options {
67                    Host::RemoteServerProject(
68                        remote_connection_options,
69                        matches!(
70                            event,
71                            project::Event::DisconnectedFromRemote {
72                                server_not_running: true
73                            }
74                        ),
75                    )
76                } else {
77                    Host::CollabGuestProject
78                };
79
80                workspace.toggle_modal(window, cx, |_, cx| DisconnectedOverlay {
81                    finished: false,
82                    workspace: handle,
83                    host,
84                    focus_handle: cx.focus_handle(),
85                });
86            },
87        )
88        .detach();
89    }
90
91    fn handle_reconnect(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
92        self.finished = true;
93        cx.emit(DismissEvent);
94
95        if let Host::RemoteServerProject(remote_connection_options, _) = &self.host {
96            self.reconnect_to_remote_project(remote_connection_options.clone(), window, cx);
97        }
98    }
99
100    fn reconnect_to_remote_project(
101        &self,
102        connection_options: RemoteConnectionOptions,
103        window: &mut Window,
104        cx: &mut Context<Self>,
105    ) {
106        let Some(workspace) = self.workspace.upgrade() else {
107            return;
108        };
109
110        let Some(window_handle) = window.window_handle().downcast::<MultiWorkspace>() else {
111            return;
112        };
113
114        let app_state = workspace.read(cx).app_state().clone();
115        let paths = workspace
116            .read(cx)
117            .root_paths(cx)
118            .iter()
119            .map(|path| path.to_path_buf())
120            .collect();
121
122        cx.spawn_in(window, async move |_, cx| {
123            open_remote_project(
124                connection_options,
125                paths,
126                app_state,
127                OpenOptions {
128                    requesting_window: Some(window_handle),
129                    ..Default::default()
130                },
131                cx,
132            )
133            .await?;
134            Ok(())
135        })
136        .detach_and_prompt_err("Failed to reconnect", window, cx, |_, _, _| None);
137    }
138
139    fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
140        self.finished = true;
141        cx.emit(DismissEvent)
142    }
143}
144
145impl Render for DisconnectedOverlay {
146    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
147        let can_reconnect = matches!(self.host, Host::RemoteServerProject(..));
148
149        let message = match &self.host {
150            Host::CollabGuestProject => {
151                "Your connection to the remote project has been lost.".to_string()
152            }
153            Host::RemoteServerProject(options, server_not_running) => {
154                let autosave = if ProjectSettings::get_global(cx)
155                    .session
156                    .restore_unsaved_buffers
157                {
158                    "\nUnsaved changes are stored locally."
159                } else {
160                    ""
161                };
162                let reason = if *server_not_running {
163                    "process exiting unexpectedly"
164                } else {
165                    "not responding"
166                };
167                format!(
168                    "Your connection to {} has been lost due to the server {reason}.{autosave}",
169                    options.display_name(),
170                )
171            }
172        };
173
174        div()
175            .track_focus(&self.focus_handle(cx))
176            .elevation_3(cx)
177            .on_action(cx.listener(Self::cancel))
178            .occlude()
179            .w(rems(24.))
180            .max_h(rems(40.))
181            .child(
182                Modal::new("disconnected", None)
183                    .header(
184                        ModalHeader::new()
185                            .show_dismiss_button(true)
186                            .child(Headline::new("Disconnected").size(HeadlineSize::Small)),
187                    )
188                    .section(Section::new().child(Label::new(message)))
189                    .footer(
190                        ModalFooter::new().end_slot(
191                            h_flex()
192                                .gap_2()
193                                .child(
194                                    Button::new("close-window", "Close Window")
195                                        .style(ButtonStyle::Filled)
196                                        .layer(ElevationIndex::ModalSurface)
197                                        .on_click(cx.listener(move |_, _, window, _| {
198                                            window.remove_window();
199                                        })),
200                                )
201                                .when(can_reconnect, |el| {
202                                    el.child(
203                                        Button::new("reconnect", "Reconnect")
204                                            .style(ButtonStyle::Filled)
205                                            .layer(ElevationIndex::ModalSurface)
206                                            .start_icon(Icon::new(IconName::ArrowCircle))
207                                            .on_click(cx.listener(Self::handle_reconnect)),
208                                    )
209                                }),
210                        ),
211                    ),
212            )
213    }
214}
215
Served at tenant.openagents/omega Member data and write actions are omitted.