Reconnect button for remote projects (#12669)
Release Notes: - N/A --------- Co-authored-by: Max <max@zed.dev>
This commit is contained in:
parent
1914a42b1c
commit
4e98c23463
15 changed files with 437 additions and 136 deletions
|
@ -23,6 +23,7 @@ markdown.workspace = true
|
|||
menu.workspace = true
|
||||
ordered-float.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
dev_server_projects.workspace = true
|
||||
rpc.workspace = true
|
||||
serde.workspace = true
|
||||
|
|
|
@ -35,6 +35,7 @@ use ui_text_field::{FieldLabelLayout, TextField};
|
|||
use util::ResultExt;
|
||||
use workspace::{notifications::DetachAndPromptErr, AppState, ModalView, Workspace, WORKSPACE_DB};
|
||||
|
||||
use crate::open_dev_server_project;
|
||||
use crate::OpenRemote;
|
||||
|
||||
pub struct DevServerProjects {
|
||||
|
@ -211,7 +212,11 @@ impl DevServerProjects {
|
|||
this.mode = Mode::Default(None);
|
||||
if let Some(app_state) = AppState::global(cx).upgrade() {
|
||||
workspace::join_dev_server_project(
|
||||
project_id, app_state, None, cx,
|
||||
DevServerProjectId(dev_server_project_id),
|
||||
project_id,
|
||||
app_state,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
.detach_and_prompt_err(
|
||||
"Could not join project",
|
||||
|
@ -558,7 +563,27 @@ impl DevServerProjects {
|
|||
h_flex()
|
||||
.visible_on_hover("dev-server")
|
||||
.gap_1()
|
||||
.child(
|
||||
.child(if dev_server.ssh_connection_string.is_some() {
|
||||
let dev_server = dev_server.clone();
|
||||
IconButton::new("reconnect-dev-server", IconName::ArrowCircle)
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
let Some(workspace) = this.workspace.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
reconnect_to_dev_server(
|
||||
workspace,
|
||||
dev_server.clone(),
|
||||
cx,
|
||||
)
|
||||
.detach_and_prompt_err(
|
||||
"Failed to reconnect",
|
||||
cx,
|
||||
|_, _| None,
|
||||
);
|
||||
}))
|
||||
.tooltip(|cx| Tooltip::text("Reconnect", cx))
|
||||
} else {
|
||||
IconButton::new("edit-dev-server", IconName::Pencil)
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
this.mode = Mode::CreateDevServer(CreateDevServer {
|
||||
|
@ -577,8 +602,8 @@ impl DevServerProjects {
|
|||
},
|
||||
)
|
||||
}))
|
||||
.tooltip(|cx| Tooltip::text("Edit dev server", cx)),
|
||||
)
|
||||
.tooltip(|cx| Tooltip::text("Edit dev server", cx))
|
||||
})
|
||||
.child({
|
||||
let dev_server_id = dev_server.id;
|
||||
IconButton::new("remove-dev-server", IconName::Trash)
|
||||
|
@ -681,7 +706,7 @@ impl DevServerProjects {
|
|||
.on_click(cx.listener(move |_, _, cx| {
|
||||
if let Some(project_id) = project_id {
|
||||
if let Some(app_state) = AppState::global(cx).upgrade() {
|
||||
workspace::join_dev_server_project(project_id, app_state, None, cx)
|
||||
workspace::join_dev_server_project(dev_server_project_id, project_id, app_state, None, cx)
|
||||
.detach_and_prompt_err("Could not join project", cx, |_, _| None)
|
||||
}
|
||||
} else {
|
||||
|
@ -1044,6 +1069,43 @@ impl Render for DevServerProjects {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn reconnect_to_dev_server_project(
|
||||
workspace: View<Workspace>,
|
||||
dev_server: DevServer,
|
||||
dev_server_project_id: DevServerProjectId,
|
||||
replace_current_window: bool,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
let store = dev_server_projects::Store::global(cx);
|
||||
let reconnect = reconnect_to_dev_server(workspace.clone(), dev_server, cx);
|
||||
cx.spawn(|mut cx| async move {
|
||||
reconnect.await?;
|
||||
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(1000))
|
||||
.await;
|
||||
|
||||
if let Some(project_id) = store.update(&mut cx, |store, _| {
|
||||
store
|
||||
.dev_server_project(dev_server_project_id)
|
||||
.and_then(|p| p.project_id)
|
||||
})? {
|
||||
workspace
|
||||
.update(&mut cx, move |_, cx| {
|
||||
open_dev_server_project(
|
||||
replace_current_window,
|
||||
dev_server_project_id,
|
||||
project_id,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn reconnect_to_dev_server(
|
||||
workspace: View<Workspace>,
|
||||
dev_server: DevServer,
|
||||
|
|
155
crates/recent_projects/src/disconnected_overlay.rs
Normal file
155
crates/recent_projects/src/disconnected_overlay.rs
Normal file
|
@ -0,0 +1,155 @@
|
|||
use dev_server_projects::DevServer;
|
||||
use gpui::{ClickEvent, DismissEvent, EventEmitter, FocusHandle, FocusableView, Render, WeakView};
|
||||
use ui::{
|
||||
div, h_flex, rems, Button, ButtonCommon, ButtonStyle, Clickable, ElevationIndex, FluentBuilder,
|
||||
Headline, HeadlineSize, IconName, IconPosition, InteractiveElement, IntoElement, Label, Modal,
|
||||
ModalFooter, ModalHeader, ParentElement, Section, Styled, StyledExt, ViewContext,
|
||||
};
|
||||
use workspace::{notifications::DetachAndPromptErr, ModalView, Workspace};
|
||||
|
||||
use crate::{
|
||||
dev_servers::reconnect_to_dev_server_project, open_dev_server_project, DevServerProjects,
|
||||
};
|
||||
|
||||
pub struct DisconnectedOverlay {
|
||||
workspace: WeakView<Workspace>,
|
||||
dev_server: Option<DevServer>,
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for DisconnectedOverlay {}
|
||||
impl FocusableView for DisconnectedOverlay {
|
||||
fn focus_handle(&self, _cx: &gpui::AppContext) -> gpui::FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
impl ModalView for DisconnectedOverlay {
|
||||
fn fade_out_background(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl DisconnectedOverlay {
|
||||
pub fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
||||
cx.subscribe(workspace.project(), |workspace, project, event, cx| {
|
||||
if !matches!(event, project::Event::DisconnectedFromHost) {
|
||||
return;
|
||||
}
|
||||
let handle = cx.view().downgrade();
|
||||
let dev_server = project
|
||||
.read(cx)
|
||||
.dev_server_project_id()
|
||||
.and_then(|id| {
|
||||
dev_server_projects::Store::global(cx)
|
||||
.read(cx)
|
||||
.dev_server_for_project(id)
|
||||
})
|
||||
.cloned();
|
||||
workspace.toggle_modal(cx, |cx| DisconnectedOverlay {
|
||||
workspace: handle,
|
||||
dev_server,
|
||||
focus_handle: cx.focus_handle(),
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn handle_reconnect(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(DismissEvent);
|
||||
let Some(workspace) = self.workspace.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let Some(dev_server) = self.dev_server.clone() else {
|
||||
return;
|
||||
};
|
||||
let Some(dev_server_project_id) = workspace
|
||||
.read(cx)
|
||||
.project()
|
||||
.read(cx)
|
||||
.dev_server_project_id()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(project_id) = dev_server_projects::Store::global(cx)
|
||||
.read(cx)
|
||||
.dev_server_project(dev_server_project_id)
|
||||
.and_then(|project| project.project_id)
|
||||
{
|
||||
return workspace.update(cx, move |_, cx| {
|
||||
open_dev_server_project(true, dev_server_project_id, project_id, cx)
|
||||
.detach_and_prompt_err("Failed to reconnect", cx, |_, _| None)
|
||||
});
|
||||
}
|
||||
|
||||
if dev_server.ssh_connection_string.is_some() {
|
||||
let task = workspace.update(cx, |_, cx| {
|
||||
reconnect_to_dev_server_project(
|
||||
cx.view().clone(),
|
||||
dev_server,
|
||||
dev_server_project_id,
|
||||
true,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
task.detach_and_prompt_err("Failed to reconnect", cx, |_, _| None);
|
||||
} else {
|
||||
return workspace.update(cx, |workspace, cx| {
|
||||
let handle = cx.view().downgrade();
|
||||
workspace.toggle_modal(cx, |cx| DevServerProjects::new(cx, handle))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(DismissEvent)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for DisconnectedOverlay {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.track_focus(&self.focus_handle)
|
||||
.elevation_3(cx)
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.occlude()
|
||||
.w(rems(24.))
|
||||
.max_h(rems(40.))
|
||||
.child(
|
||||
Modal::new("disconnected", None)
|
||||
.header(
|
||||
ModalHeader::new()
|
||||
.show_dismiss_button(true)
|
||||
.child(Headline::new("Disconnected").size(HeadlineSize::Small)),
|
||||
)
|
||||
.section(Section::new().child(Label::new(
|
||||
"Your connection to the remote project has been lost.",
|
||||
)))
|
||||
.footer(
|
||||
ModalFooter::new().end_slot(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Button::new("close-window", "Close Window")
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.on_click(cx.listener(move |_, _, cx| {
|
||||
cx.remove_window();
|
||||
})),
|
||||
)
|
||||
.when_some(self.dev_server.clone(), |el, _| {
|
||||
el.child(
|
||||
Button::new("reconnect", "Reconnect")
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.icon(IconName::ArrowCircle)
|
||||
.icon_position(IconPosition::Start)
|
||||
.on_click(cx.listener(Self::handle_reconnect)),
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
mod dev_servers;
|
||||
pub mod disconnected_overlay;
|
||||
|
||||
use client::ProjectId;
|
||||
use dev_servers::reconnect_to_dev_server;
|
||||
use client::{DevServerProjectId, ProjectId};
|
||||
use dev_servers::reconnect_to_dev_server_project;
|
||||
pub use dev_servers::DevServerProjects;
|
||||
use disconnected_overlay::DisconnectedOverlay;
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
|
@ -19,7 +21,6 @@ use serde::Deserialize;
|
|||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use ui::{
|
||||
prelude::*, tooltip_container, ButtonLike, IconWithIndicator, Indicator, KeyBinding, ListItem,
|
||||
|
@ -46,6 +47,7 @@ gpui::actions!(projects, [OpenRemote]);
|
|||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(RecentProjects::register).detach();
|
||||
cx.observe_new_views(DevServerProjects::register).detach();
|
||||
cx.observe_new_views(DisconnectedOverlay::register).detach();
|
||||
}
|
||||
|
||||
pub struct RecentProjects {
|
||||
|
@ -314,23 +316,7 @@ impl PickerDelegate for RecentProjectsDelegate {
|
|||
else {
|
||||
let server = store.read(cx).dev_server_for_project(dev_server_project.id);
|
||||
if server.is_some_and(|server| server.ssh_connection_string.is_some()) {
|
||||
let reconnect = reconnect_to_dev_server(cx.view().clone(), server.unwrap().clone(), cx);
|
||||
let id = dev_server_project.id;
|
||||
return cx.spawn(|workspace, mut cx| async move {
|
||||
reconnect.await?;
|
||||
|
||||
cx.background_executor().timer(Duration::from_millis(1000)).await;
|
||||
|
||||
if let Some(project_id) = store.update(&mut cx, |store, _| {
|
||||
store.dev_server_project(id)
|
||||
.and_then(|p| p.project_id)
|
||||
})? {
|
||||
workspace.update(&mut cx, move |_, cx| {
|
||||
open_dev_server_project(replace_current_window, project_id, cx)
|
||||
})?.await?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
return reconnect_to_dev_server_project(cx.view().clone(), server.unwrap().clone(), dev_server_project.id, replace_current_window, cx);
|
||||
} else {
|
||||
let dev_server_name = dev_server_project.dev_server_name.clone();
|
||||
return cx.spawn(|workspace, mut cx| async move {
|
||||
|
@ -354,7 +340,7 @@ impl PickerDelegate for RecentProjectsDelegate {
|
|||
})
|
||||
}
|
||||
};
|
||||
open_dev_server_project(replace_current_window, project_id, cx)
|
||||
open_dev_server_project(replace_current_window, dev_server_project.id, project_id, cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -544,6 +530,7 @@ impl PickerDelegate for RecentProjectsDelegate {
|
|||
|
||||
fn open_dev_server_project(
|
||||
replace_current_window: bool,
|
||||
dev_server_project_id: DevServerProjectId,
|
||||
project_id: ProjectId,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
|
@ -565,6 +552,7 @@ fn open_dev_server_project(
|
|||
workspace
|
||||
.update(&mut cx, |_workspace, cx| {
|
||||
workspace::join_dev_server_project(
|
||||
dev_server_project_id,
|
||||
project_id,
|
||||
app_state,
|
||||
Some(handle),
|
||||
|
@ -576,7 +564,13 @@ fn open_dev_server_project(
|
|||
Ok(())
|
||||
})
|
||||
} else {
|
||||
let task = workspace::join_dev_server_project(project_id, app_state, None, cx);
|
||||
let task = workspace::join_dev_server_project(
|
||||
dev_server_project_id,
|
||||
project_id,
|
||||
app_state,
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
cx.spawn(|_, _| async move {
|
||||
task.await?;
|
||||
Ok(())
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue