Improve disconnected modal for SSH (#19567)

Closes #ISSUE

Release Notes:

- SSH Remoting: made reconnect modal more robust
This commit is contained in:
Conrad Irwin 2024-10-22 13:22:27 -06:00 committed by GitHub
parent 5dbf68ddc4
commit e93d62680d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 156 additions and 122 deletions

View file

@ -2,6 +2,7 @@ use std::path::PathBuf;
use dev_server_projects::DevServer;
use gpui::{ClickEvent, DismissEvent, EventEmitter, FocusHandle, FocusableView, Render, WeakView};
use project::project_settings::ProjectSettings;
use remote::SshConnectionOptions;
use settings::Settings;
use ui::{
@ -26,6 +27,7 @@ pub struct DisconnectedOverlay {
workspace: WeakView<Workspace>,
host: Host,
focus_handle: FocusHandle,
finished: bool,
}
impl EventEmitter<DismissEvent> for DisconnectedOverlay {}
@ -35,6 +37,9 @@ impl FocusableView for DisconnectedOverlay {
}
}
impl ModalView for DisconnectedOverlay {
fn on_before_dismiss(&mut self, _: &mut ViewContext<Self>) -> workspace::DismissDecision {
return workspace::DismissDecision::Dismiss(self.finished);
}
fn fade_out_background(&self) -> bool {
true
}
@ -70,6 +75,7 @@ impl DisconnectedOverlay {
};
workspace.toggle_modal(cx, |cx| DisconnectedOverlay {
finished: false,
workspace: handle,
host,
focus_handle: cx.focus_handle(),
@ -79,6 +85,7 @@ impl DisconnectedOverlay {
}
fn handle_reconnect(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
self.finished = true;
cx.emit(DismissEvent);
match &self.host {
@ -186,6 +193,7 @@ impl DisconnectedOverlay {
}
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
self.finished = true;
cx.emit(DismissEvent)
}
}
@ -202,9 +210,17 @@ impl Render for DisconnectedOverlay {
"Your connection to the remote project has been lost.".to_string()
}
Host::SshRemoteProject(options) => {
let autosave = if ProjectSettings::get_global(cx)
.session
.restore_unsaved_buffers
{
"\nUnsaved changes are stored locally."
} else {
""
};
format!(
"Your connection to {} has been lost",
options.connection_string()
"Your connection to {} has been lost.{}",
options.host, autosave
)
}
};

View file

@ -31,7 +31,7 @@ use futures::{
};
use gpui::{
action_as, actions, canvas, impl_action_as, impl_actions, point, relative, size,
transparent_black, Action, AnyElement, AnyView, AnyWeakView, AppContext, AsyncAppContext,
transparent_black, Action, AnyView, AnyWeakView, AppContext, AsyncAppContext,
AsyncWindowContext, Bounds, CursorStyle, Decorations, DragMoveEvent, Entity as _, EntityId,
EventEmitter, Flatten, FocusHandle, FocusableView, Global, Hsla, KeyContext, Keystroke,
ManagedView, Model, ModelContext, MouseButton, PathPromptOptions, Point, PromptLevel, Render,
@ -762,8 +762,6 @@ pub struct Workspace {
bounds_save_task_queued: Option<Task<()>>,
on_prompt_for_new_path: Option<PromptForNewPath>,
on_prompt_for_open_path: Option<PromptForOpenPath>,
render_disconnected_overlay:
Option<Box<dyn Fn(&mut Self, &mut ViewContext<Self>) -> AnyElement>>,
serializable_items_tx: UnboundedSender<Box<dyn SerializableItemHandle>>,
serialized_ssh_project: Option<SerializedSshProject>,
_items_serializer: Task<Result<()>>,
@ -1067,7 +1065,6 @@ impl Workspace {
bounds_save_task_queued: None,
on_prompt_for_new_path: None,
on_prompt_for_open_path: None,
render_disconnected_overlay: None,
serializable_items_tx,
_items_serializer,
session_id: Some(session_id),
@ -1472,13 +1469,6 @@ impl Workspace {
self.serialized_ssh_project = Some(serialized_ssh_project);
}
pub fn set_render_disconnected_overlay(
&mut self,
render: impl Fn(&mut Self, &mut ViewContext<Self>) -> AnyElement + 'static,
) {
self.render_disconnected_overlay = Some(Box::new(render))
}
pub fn prompt_for_open_path(
&mut self,
path_prompt_options: PathPromptOptions,
@ -4746,130 +4736,158 @@ impl Render for Workspace {
.children(self.titlebar_item.clone())
.child(
div()
.id("workspace")
.bg(colors.background)
.size_full()
.relative()
.flex_1()
.w_full()
.flex()
.flex_col()
.overflow_hidden()
.border_t_1()
.border_b_1()
.border_color(colors.border)
.child({
let this = cx.view().clone();
canvas(
move |bounds, cx| this.update(cx, |this, _cx| this.bounds = bounds),
|_, _, _| {},
)
.absolute()
.size_full()
})
.when(self.zoomed.is_none(), |this| {
this.on_drag_move(cx.listener(
|workspace, e: &DragMoveEvent<DraggedDock>, cx| match e.drag(cx).0 {
DockPosition::Left => {
let size = e.event.position.x - workspace.bounds.left();
workspace.left_dock.update(cx, |left_dock, cx| {
left_dock.resize_active_panel(Some(size), cx);
});
}
DockPosition::Right => {
let size = workspace.bounds.right() - e.event.position.x;
workspace.right_dock.update(cx, |right_dock, cx| {
right_dock.resize_active_panel(Some(size), cx);
});
}
DockPosition::Bottom => {
let size = workspace.bounds.bottom() - e.event.position.y;
workspace.bottom_dock.update(cx, |bottom_dock, cx| {
bottom_dock.resize_active_panel(Some(size), cx);
});
}
},
))
})
.child(
div()
.id("workspace")
.bg(colors.background)
.relative()
.flex_1()
.w_full()
.flex()
.flex_row()
.h_full()
// Left Dock
.children(self.render_dock(DockPosition::Left, &self.left_dock, cx))
// Panes
.flex_col()
.overflow_hidden()
.border_t_1()
.border_b_1()
.border_color(colors.border)
.child({
let this = cx.view().clone();
canvas(
move |bounds, cx| {
this.update(cx, |this, _cx| this.bounds = bounds)
},
|_, _, _| {},
)
.absolute()
.size_full()
})
.when(self.zoomed.is_none(), |this| {
this.on_drag_move(cx.listener(
|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
match e.drag(cx).0 {
DockPosition::Left => {
let size = e.event.position.x
- workspace.bounds.left();
workspace.left_dock.update(
cx,
|left_dock, cx| {
left_dock.resize_active_panel(
Some(size),
cx,
);
},
);
}
DockPosition::Right => {
let size = workspace.bounds.right()
- e.event.position.x;
workspace.right_dock.update(
cx,
|right_dock, cx| {
right_dock.resize_active_panel(
Some(size),
cx,
);
},
);
}
DockPosition::Bottom => {
let size = workspace.bounds.bottom()
- e.event.position.y;
workspace.bottom_dock.update(
cx,
|bottom_dock, cx| {
bottom_dock.resize_active_panel(
Some(size),
cx,
);
},
);
}
}
},
))
})
.child(
div()
.flex()
.flex_col()
.flex_1()
.overflow_hidden()
.child(
h_flex()
.flex_1()
.when_some(paddings.0, |this, p| {
this.child(p.border_r_1())
})
.child(self.center.render(
&self.project,
&self.follower_states,
self.active_call(),
&self.active_pane,
self.zoomed.as_ref(),
&self.app_state,
cx,
))
.when_some(paddings.1, |this, p| {
this.child(p.border_l_1())
}),
)
.flex_row()
.h_full()
// Left Dock
.children(self.render_dock(
DockPosition::Bottom,
&self.bottom_dock,
DockPosition::Left,
&self.left_dock,
cx,
))
// Panes
.child(
div()
.flex()
.flex_col()
.flex_1()
.overflow_hidden()
.child(
h_flex()
.flex_1()
.when_some(paddings.0, |this, p| {
this.child(p.border_r_1())
})
.child(self.center.render(
&self.project,
&self.follower_states,
self.active_call(),
&self.active_pane,
self.zoomed.as_ref(),
&self.app_state,
cx,
))
.when_some(paddings.1, |this, p| {
this.child(p.border_l_1())
}),
)
.children(self.render_dock(
DockPosition::Bottom,
&self.bottom_dock,
cx,
)),
)
// Right Dock
.children(self.render_dock(
DockPosition::Right,
&self.right_dock,
cx,
)),
)
// Right Dock
.children(self.render_dock(
DockPosition::Right,
&self.right_dock,
cx,
)),
)
.children(self.zoomed.as_ref().and_then(|view| {
let zoomed_view = view.upgrade()?;
let div = div()
.occlude()
.absolute()
.overflow_hidden()
.border_color(colors.border)
.bg(colors.background)
.child(zoomed_view)
.inset_0()
.shadow_lg();
.children(self.zoomed.as_ref().and_then(|view| {
let zoomed_view = view.upgrade()?;
let div = div()
.occlude()
.absolute()
.overflow_hidden()
.border_color(colors.border)
.bg(colors.background)
.child(zoomed_view)
.inset_0()
.shadow_lg();
Some(match self.zoomed_position {
Some(DockPosition::Left) => div.right_2().border_r_1(),
Some(DockPosition::Right) => div.left_2().border_l_1(),
Some(DockPosition::Bottom) => div.top_2().border_t_1(),
None => div.top_2().bottom_2().left_2().right_2().border_1(),
})
}))
.child(self.modal_layer.clone())
.children(self.render_notifications(cx)),
)
.child(self.status_bar.clone())
.children(if self.project.read(cx).is_disconnected(cx) {
if let Some(render) = self.render_disconnected_overlay.take() {
let result = render(self, cx);
self.render_disconnected_overlay = Some(render);
Some(result)
} else {
None
}
} else {
None
}),
Some(match self.zoomed_position {
Some(DockPosition::Left) => div.right_2().border_r_1(),
Some(DockPosition::Right) => div.left_2().border_l_1(),
Some(DockPosition::Bottom) => div.top_2().border_t_1(),
None => {
div.top_2().bottom_2().left_2().right_2().border_1()
}
})
}))
.children(self.render_notifications(cx)),
)
.child(self.status_bar.clone())
.child(self.modal_layer.clone()),
),
cx,
)
}