Improve disconnected modal for SSH (#19567)
Closes #ISSUE Release Notes: - SSH Remoting: made reconnect modal more robust
This commit is contained in:
parent
5dbf68ddc4
commit
e93d62680d
2 changed files with 156 additions and 122 deletions
|
@ -2,6 +2,7 @@ use std::path::PathBuf;
|
||||||
|
|
||||||
use dev_server_projects::DevServer;
|
use dev_server_projects::DevServer;
|
||||||
use gpui::{ClickEvent, DismissEvent, EventEmitter, FocusHandle, FocusableView, Render, WeakView};
|
use gpui::{ClickEvent, DismissEvent, EventEmitter, FocusHandle, FocusableView, Render, WeakView};
|
||||||
|
use project::project_settings::ProjectSettings;
|
||||||
use remote::SshConnectionOptions;
|
use remote::SshConnectionOptions;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use ui::{
|
use ui::{
|
||||||
|
@ -26,6 +27,7 @@ pub struct DisconnectedOverlay {
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
host: Host,
|
host: Host,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
|
finished: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<DismissEvent> for DisconnectedOverlay {}
|
impl EventEmitter<DismissEvent> for DisconnectedOverlay {}
|
||||||
|
@ -35,6 +37,9 @@ impl FocusableView for DisconnectedOverlay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl ModalView 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 {
|
fn fade_out_background(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -70,6 +75,7 @@ impl DisconnectedOverlay {
|
||||||
};
|
};
|
||||||
|
|
||||||
workspace.toggle_modal(cx, |cx| DisconnectedOverlay {
|
workspace.toggle_modal(cx, |cx| DisconnectedOverlay {
|
||||||
|
finished: false,
|
||||||
workspace: handle,
|
workspace: handle,
|
||||||
host,
|
host,
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
|
@ -79,6 +85,7 @@ impl DisconnectedOverlay {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_reconnect(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
|
fn handle_reconnect(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
|
||||||
|
self.finished = true;
|
||||||
cx.emit(DismissEvent);
|
cx.emit(DismissEvent);
|
||||||
|
|
||||||
match &self.host {
|
match &self.host {
|
||||||
|
@ -186,6 +193,7 @@ impl DisconnectedOverlay {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
||||||
|
self.finished = true;
|
||||||
cx.emit(DismissEvent)
|
cx.emit(DismissEvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,9 +210,17 @@ impl Render for DisconnectedOverlay {
|
||||||
"Your connection to the remote project has been lost.".to_string()
|
"Your connection to the remote project has been lost.".to_string()
|
||||||
}
|
}
|
||||||
Host::SshRemoteProject(options) => {
|
Host::SshRemoteProject(options) => {
|
||||||
|
let autosave = if ProjectSettings::get_global(cx)
|
||||||
|
.session
|
||||||
|
.restore_unsaved_buffers
|
||||||
|
{
|
||||||
|
"\nUnsaved changes are stored locally."
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
format!(
|
format!(
|
||||||
"Your connection to {} has been lost",
|
"Your connection to {} has been lost.{}",
|
||||||
options.connection_string()
|
options.host, autosave
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -31,7 +31,7 @@ use futures::{
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
action_as, actions, canvas, impl_action_as, impl_actions, point, relative, size,
|
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,
|
AsyncWindowContext, Bounds, CursorStyle, Decorations, DragMoveEvent, Entity as _, EntityId,
|
||||||
EventEmitter, Flatten, FocusHandle, FocusableView, Global, Hsla, KeyContext, Keystroke,
|
EventEmitter, Flatten, FocusHandle, FocusableView, Global, Hsla, KeyContext, Keystroke,
|
||||||
ManagedView, Model, ModelContext, MouseButton, PathPromptOptions, Point, PromptLevel, Render,
|
ManagedView, Model, ModelContext, MouseButton, PathPromptOptions, Point, PromptLevel, Render,
|
||||||
|
@ -762,8 +762,6 @@ pub struct Workspace {
|
||||||
bounds_save_task_queued: Option<Task<()>>,
|
bounds_save_task_queued: Option<Task<()>>,
|
||||||
on_prompt_for_new_path: Option<PromptForNewPath>,
|
on_prompt_for_new_path: Option<PromptForNewPath>,
|
||||||
on_prompt_for_open_path: Option<PromptForOpenPath>,
|
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>>,
|
serializable_items_tx: UnboundedSender<Box<dyn SerializableItemHandle>>,
|
||||||
serialized_ssh_project: Option<SerializedSshProject>,
|
serialized_ssh_project: Option<SerializedSshProject>,
|
||||||
_items_serializer: Task<Result<()>>,
|
_items_serializer: Task<Result<()>>,
|
||||||
|
@ -1067,7 +1065,6 @@ impl Workspace {
|
||||||
bounds_save_task_queued: None,
|
bounds_save_task_queued: None,
|
||||||
on_prompt_for_new_path: None,
|
on_prompt_for_new_path: None,
|
||||||
on_prompt_for_open_path: None,
|
on_prompt_for_open_path: None,
|
||||||
render_disconnected_overlay: None,
|
|
||||||
serializable_items_tx,
|
serializable_items_tx,
|
||||||
_items_serializer,
|
_items_serializer,
|
||||||
session_id: Some(session_id),
|
session_id: Some(session_id),
|
||||||
|
@ -1472,13 +1469,6 @@ impl Workspace {
|
||||||
self.serialized_ssh_project = Some(serialized_ssh_project);
|
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(
|
pub fn prompt_for_open_path(
|
||||||
&mut self,
|
&mut self,
|
||||||
path_prompt_options: PathPromptOptions,
|
path_prompt_options: PathPromptOptions,
|
||||||
|
@ -4746,130 +4736,158 @@ impl Render for Workspace {
|
||||||
.children(self.titlebar_item.clone())
|
.children(self.titlebar_item.clone())
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.id("workspace")
|
.size_full()
|
||||||
.bg(colors.background)
|
|
||||||
.relative()
|
.relative()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.w_full()
|
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.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(
|
.child(
|
||||||
div()
|
div()
|
||||||
|
.id("workspace")
|
||||||
|
.bg(colors.background)
|
||||||
|
.relative()
|
||||||
|
.flex_1()
|
||||||
|
.w_full()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_row()
|
.flex_col()
|
||||||
.h_full()
|
.overflow_hidden()
|
||||||
// Left Dock
|
.border_t_1()
|
||||||
.children(self.render_dock(DockPosition::Left, &self.left_dock, cx))
|
.border_b_1()
|
||||||
// Panes
|
.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(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_row()
|
||||||
.flex_1()
|
.h_full()
|
||||||
.overflow_hidden()
|
// Left Dock
|
||||||
.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(
|
.children(self.render_dock(
|
||||||
DockPosition::Bottom,
|
DockPosition::Left,
|
||||||
&self.bottom_dock,
|
&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,
|
cx,
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
// Right Dock
|
.children(self.zoomed.as_ref().and_then(|view| {
|
||||||
.children(self.render_dock(
|
let zoomed_view = view.upgrade()?;
|
||||||
DockPosition::Right,
|
let div = div()
|
||||||
&self.right_dock,
|
.occlude()
|
||||||
cx,
|
.absolute()
|
||||||
)),
|
.overflow_hidden()
|
||||||
)
|
.border_color(colors.border)
|
||||||
.children(self.zoomed.as_ref().and_then(|view| {
|
.bg(colors.background)
|
||||||
let zoomed_view = view.upgrade()?;
|
.child(zoomed_view)
|
||||||
let div = div()
|
.inset_0()
|
||||||
.occlude()
|
.shadow_lg();
|
||||||
.absolute()
|
|
||||||
.overflow_hidden()
|
|
||||||
.border_color(colors.border)
|
|
||||||
.bg(colors.background)
|
|
||||||
.child(zoomed_view)
|
|
||||||
.inset_0()
|
|
||||||
.shadow_lg();
|
|
||||||
|
|
||||||
Some(match self.zoomed_position {
|
Some(match self.zoomed_position {
|
||||||
Some(DockPosition::Left) => div.right_2().border_r_1(),
|
Some(DockPosition::Left) => div.right_2().border_r_1(),
|
||||||
Some(DockPosition::Right) => div.left_2().border_l_1(),
|
Some(DockPosition::Right) => div.left_2().border_l_1(),
|
||||||
Some(DockPosition::Bottom) => div.top_2().border_t_1(),
|
Some(DockPosition::Bottom) => div.top_2().border_t_1(),
|
||||||
None => div.top_2().bottom_2().left_2().right_2().border_1(),
|
None => {
|
||||||
})
|
div.top_2().bottom_2().left_2().right_2().border_1()
|
||||||
}))
|
}
|
||||||
.child(self.modal_layer.clone())
|
})
|
||||||
.children(self.render_notifications(cx)),
|
}))
|
||||||
)
|
.children(self.render_notifications(cx)),
|
||||||
.child(self.status_bar.clone())
|
)
|
||||||
.children(if self.project.read(cx).is_disconnected(cx) {
|
.child(self.status_bar.clone())
|
||||||
if let Some(render) = self.render_disconnected_overlay.take() {
|
.child(self.modal_layer.clone()),
|
||||||
let result = render(self, cx);
|
),
|
||||||
self.render_disconnected_overlay = Some(render);
|
|
||||||
Some(result)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue