diff --git a/crates/collab/migrations/20240502180204_remove_old_remote_projects.sql b/crates/collab/migrations/20240502180204_remove_old_remote_projects.sql new file mode 100644 index 0000000000..01ace43fab --- /dev/null +++ b/crates/collab/migrations/20240502180204_remove_old_remote_projects.sql @@ -0,0 +1,2 @@ +ALTER TABLE projects DROP COLUMN remote_project_id; +DROP TABLE remote_projects; diff --git a/crates/collab/src/db/queries/dev_server_projects.rs b/crates/collab/src/db/queries/dev_server_projects.rs index 6a622fd14a..3c71693a53 100644 --- a/crates/collab/src/db/queries/dev_server_projects.rs +++ b/crates/collab/src/db/queries/dev_server_projects.rs @@ -1,5 +1,8 @@ use anyhow::anyhow; -use rpc::{proto, ConnectionId}; +use rpc::{ + proto::{self}, + ConnectionId, +}; use sea_orm::{ ActiveModelTrait, ActiveValue, ColumnTrait, Condition, DatabaseTransaction, EntityTrait, ModelTrait, QueryFilter, @@ -35,24 +38,33 @@ impl Database { dev_server_id: DevServerId, ) -> crate::Result> { self.transaction(|tx| async move { - let servers = dev_server_project::Entity::find() - .filter(dev_server_project::Column::DevServerId.eq(dev_server_id)) - .find_also_related(project::Entity) - .all(&*tx) - .await?; - Ok(servers - .into_iter() - .map(|(dev_server_project, project)| proto::DevServerProject { - id: dev_server_project.id.to_proto(), - project_id: project.map(|p| p.id.to_proto()), - dev_server_id: dev_server_project.dev_server_id.to_proto(), - path: dev_server_project.path, - }) - .collect()) + self.get_projects_for_dev_server_internal(dev_server_id, &tx) + .await }) .await } + pub async fn get_projects_for_dev_server_internal( + &self, + dev_server_id: DevServerId, + tx: &DatabaseTransaction, + ) -> crate::Result> { + let servers = dev_server_project::Entity::find() + .filter(dev_server_project::Column::DevServerId.eq(dev_server_id)) + .find_also_related(project::Entity) + .all(tx) + .await?; + Ok(servers + .into_iter() + .map(|(dev_server_project, project)| proto::DevServerProject { + id: dev_server_project.id.to_proto(), + project_id: project.map(|p| p.id.to_proto()), + dev_server_id: dev_server_project.dev_server_id.to_proto(), + path: dev_server_project.path, + }) + .collect()) + } + pub async fn dev_server_project_ids_for_user( &self, user_id: UserId, @@ -136,6 +148,39 @@ impl Database { .await } + pub async fn delete_dev_server_project( + &self, + dev_server_project_id: DevServerProjectId, + dev_server_id: DevServerId, + user_id: UserId, + ) -> crate::Result<(Vec, proto::DevServerProjectsUpdate)> { + self.transaction(|tx| async move { + project::Entity::delete_many() + .filter(project::Column::DevServerProjectId.eq(dev_server_project_id)) + .exec(&*tx) + .await?; + let result = dev_server_project::Entity::delete_by_id(dev_server_project_id) + .exec(&*tx) + .await?; + if result.rows_affected != 1 { + return Err(anyhow!( + "no dev server project with id {}", + dev_server_project_id + ))?; + } + + let status = self + .dev_server_projects_update_internal(user_id, &tx) + .await?; + + let projects = self + .get_projects_for_dev_server_internal(dev_server_id, &tx) + .await?; + Ok((projects, status)) + }) + .await + } + pub async fn share_dev_server_project( &self, dev_server_project_id: DevServerProjectId, diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index 5742fdd56b..34033593a7 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -598,6 +598,17 @@ impl Database { .await } + pub async fn find_dev_server_project(&self, id: DevServerProjectId) -> Result { + self.transaction(|tx| async move { + Ok(project::Entity::find() + .filter(project::Column::DevServerProjectId.eq(id)) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("no such project"))?) + }) + .await + } + /// Adds the given connection to the specified project /// in the current room. pub async fn join_project( diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 9aa5d9bdbc..6c82309eb0 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -413,6 +413,7 @@ impl Server { .add_request_handler(user_handler(join_hosted_project)) .add_request_handler(user_handler(rejoin_dev_server_projects)) .add_request_handler(user_handler(create_dev_server_project)) + .add_request_handler(user_handler(delete_dev_server_project)) .add_request_handler(user_handler(create_dev_server)) .add_request_handler(user_handler(delete_dev_server)) .add_request_handler(dev_server_handler(share_dev_server_project)) @@ -2363,6 +2364,68 @@ async fn delete_dev_server( Ok(()) } +async fn delete_dev_server_project( + request: proto::DeleteDevServerProject, + response: Response, + session: UserSession, +) -> Result<()> { + let dev_server_project_id = DevServerProjectId(request.dev_server_project_id as i32); + let dev_server_project = session + .db() + .await + .get_dev_server_project(dev_server_project_id) + .await?; + + let dev_server = session + .db() + .await + .get_dev_server(dev_server_project.dev_server_id) + .await?; + if dev_server.user_id != session.user_id() { + return Err(anyhow!(ErrorCode::Forbidden))?; + } + + let dev_server_connection_id = session + .connection_pool() + .await + .dev_server_connection_id(dev_server.id); + + if let Some(dev_server_connection_id) = dev_server_connection_id { + let project = session + .db() + .await + .find_dev_server_project(dev_server_project_id) + .await; + if let Ok(project) = project { + unshare_project_internal( + project.id, + dev_server_connection_id, + Some(session.user_id()), + &session, + ) + .await?; + } + } + + let (projects, status) = session + .db() + .await + .delete_dev_server_project(dev_server_project_id, dev_server.id, session.user_id()) + .await?; + + if let Some(dev_server_connection_id) = dev_server_connection_id { + session.peer.send( + dev_server_connection_id, + proto::DevServerInstructions { projects }, + )?; + } + + send_dev_server_projects_update(session.user_id(), status, &session).await; + + response.send(proto::Ack {})?; + Ok(()) +} + async fn rejoin_dev_server_projects( request: proto::RejoinRemoteProjects, response: Response, diff --git a/crates/collab/src/tests/dev_server_tests.rs b/crates/collab/src/tests/dev_server_tests.rs index d388850d6e..95becadada 100644 --- a/crates/collab/src/tests/dev_server_tests.rs +++ b/crates/collab/src/tests/dev_server_tests.rs @@ -263,6 +263,58 @@ async fn test_dev_server_leave_room( cx2.update(|cx| assert!(workspace.read(cx).project().read(cx).is_disconnected())); } +#[gpui::test] +async fn test_dev_server_delete( + cx1: &mut gpui::TestAppContext, + cx2: &mut gpui::TestAppContext, + cx3: &mut gpui::TestAppContext, +) { + let (server, client1, client2, channel_id) = TestServer::start2(cx1, cx2).await; + + let (_dev_server, remote_workspace) = + create_dev_server_project(&server, client1.app_state.clone(), cx1, cx3).await; + + cx1.update(|cx| { + workspace::join_channel( + channel_id, + client1.app_state.clone(), + Some(remote_workspace), + cx, + ) + }) + .await + .unwrap(); + cx1.executor().run_until_parked(); + + remote_workspace + .update(cx1, |ws, cx| { + assert!(ws.project().read(cx).is_shared()); + }) + .unwrap(); + + join_channel(channel_id, &client2, cx2).await.unwrap(); + cx2.executor().run_until_parked(); + + cx1.update(|cx| { + dev_server_projects::Store::global(cx).update(cx, |store, cx| { + store.delete_dev_server_project(store.dev_server_projects().first().unwrap().id, cx) + }) + }) + .await + .unwrap(); + + cx1.executor().run_until_parked(); + + let (workspace, cx2) = client2.active_workspace(cx2); + cx2.update(|cx| assert!(workspace.read(cx).project().read(cx).is_disconnected())); + + cx1.update(|cx| { + dev_server_projects::Store::global(cx).update(cx, |store, _| { + assert_eq!(store.dev_server_projects().len(), 0); + }) + }) +} + #[gpui::test] async fn test_dev_server_reconnect( cx1: &mut gpui::TestAppContext, diff --git a/crates/dev_server_projects/src/dev_server_projects.rs b/crates/dev_server_projects/src/dev_server_projects.rs index 499eaa479d..aa27f3c8ca 100644 --- a/crates/dev_server_projects/src/dev_server_projects.rs +++ b/crates/dev_server_projects/src/dev_server_projects.rs @@ -188,4 +188,20 @@ impl Store { Ok(()) }) } + + pub fn delete_dev_server_project( + &mut self, + id: DevServerProjectId, + cx: &mut ModelContext, + ) -> Task> { + let client = self.client.clone(); + cx.background_executor().spawn(async move { + client + .request(proto::DeleteDevServerProject { + dev_server_project_id: id.0, + }) + .await?; + Ok(()) + }) + } } diff --git a/crates/recent_projects/Cargo.toml b/crates/recent_projects/Cargo.toml index ded5ba59fa..057aa6d368 100644 --- a/crates/recent_projects/Cargo.toml +++ b/crates/recent_projects/Cargo.toml @@ -14,6 +14,7 @@ doctest = false [dependencies] anyhow.workspace = true +editor.workspace = true feature_flags.workspace = true fuzzy.workspace = true gpui.workspace = true diff --git a/crates/recent_projects/src/dev_servers.rs b/crates/recent_projects/src/dev_servers.rs index ed07480ac2..4f427550a5 100644 --- a/crates/recent_projects/src/dev_servers.rs +++ b/crates/recent_projects/src/dev_servers.rs @@ -1,14 +1,15 @@ use std::time::Duration; use dev_server_projects::{DevServer, DevServerId, DevServerProject, DevServerProjectId}; +use editor::Editor; use feature_flags::FeatureFlagViewExt; use gpui::{ - percentage, Action, Animation, AnimationExt, AppContext, ClipboardItem, DismissEvent, - EventEmitter, FocusHandle, FocusableView, Model, ScrollHandle, Transformation, View, - ViewContext, + percentage, Action, Animation, AnimationExt, AnyElement, AppContext, ClipboardItem, + DismissEvent, EventEmitter, FocusHandle, FocusableView, Model, ScrollHandle, Transformation, + View, ViewContext, }; use rpc::{ - proto::{self, CreateDevServerResponse, DevServerStatus}, + proto::{CreateDevServerResponse, DevServerStatus}, ErrorCode, ErrorExt, }; use settings::Settings; @@ -16,7 +17,7 @@ use theme::ThemeSettings; use ui::{prelude::*, Indicator, List, ListHeader, ListItem, ModalContent, ModalHeader, Tooltip}; use ui_text_field::{FieldLabelLayout, TextField}; use util::ResultExt; -use workspace::{notifications::DetachAndPromptErr, AppState, ModalView, Workspace}; +use workspace::{notifications::DetachAndPromptErr, AppState, ModalView, Workspace, WORKSPACE_DB}; use crate::OpenRemote; @@ -25,7 +26,7 @@ pub struct DevServerProjects { focus_handle: FocusHandle, scroll_handle: ScrollHandle, dev_server_store: Model, - project_path_input: View, + project_path_input: View, dev_server_name_input: View, _subscription: gpui::Subscription, } @@ -36,15 +37,14 @@ struct CreateDevServer { dev_server: Option, } +#[derive(Clone)] struct CreateDevServerProject { dev_server_id: DevServerId, creating: bool, - dev_server_project: Option, } enum Mode { - Default, - CreateDevServerProject(CreateDevServerProject), + Default(Option), CreateDevServer(CreateDevServer), } @@ -67,7 +67,11 @@ impl DevServerProjects { } pub fn new(cx: &mut ViewContext) -> Self { - let project_path_input = cx.new_view(|cx| TextField::new(cx, "", "Project path")); + let project_path_input = cx.new_view(|cx| { + let mut editor = Editor::single_line(cx); + editor.set_placeholder_text("Project path", cx); + editor + }); let dev_server_name_input = cx.new_view(|cx| TextField::new(cx, "Name", "").with_label(FieldLabelLayout::Stacked)); @@ -79,7 +83,7 @@ impl DevServerProjects { }); Self { - mode: Mode::Default, + mode: Mode::Default(None), focus_handle, scroll_handle: ScrollHandle::new(), dev_server_store, @@ -94,14 +98,7 @@ impl DevServerProjects { dev_server_id: DevServerId, cx: &mut ViewContext, ) { - let path = self - .project_path_input - .read(cx) - .editor() - .read(cx) - .text(cx) - .trim() - .to_string(); + let path = self.project_path_input.read(cx).text(cx).trim().to_string(); if path == "" { return; @@ -139,16 +136,18 @@ impl DevServerProjects { cx.spawn(|this, mut cx| async move { let result = create.await; - let dev_server_project = result - .as_ref() - .ok() - .and_then(|r| r.dev_server_project.clone()); - this.update(&mut cx, |this, _| { - this.mode = Mode::CreateDevServerProject(CreateDevServerProject { - dev_server_id, - creating: false, - dev_server_project, - }); + this.update(&mut cx, |this, cx| { + if result.is_ok() { + this.project_path_input.update(cx, |editor, cx| { + editor.set_text("", cx); + }); + this.mode = Mode::Default(None); + } else { + this.mode = Mode::Default(Some(CreateDevServerProject { + dev_server_id, + creating: false, + })); + } }) .log_err(); result @@ -166,17 +165,10 @@ impl DevServerProjects { } }); - self.project_path_input.update(cx, |input, cx| { - input.editor().update(cx, |editor, cx| { - editor.set_text("", cx); - }); - }); - - self.mode = Mode::CreateDevServerProject(CreateDevServerProject { + self.mode = Mode::Default(Some(CreateDevServerProject { dev_server_id, creating: true, - dev_server_project: None, - }); + })); } pub fn create_dev_server(&mut self, cx: &mut ViewContext) { @@ -238,20 +230,74 @@ impl DevServerProjects { return Ok(()); } + let project_ids: Vec = this.update(&mut cx, |this, cx| { + this.dev_server_store.update(cx, |store, _| { + store + .projects_for_server(id) + .into_iter() + .map(|project| project.id) + .collect() + }) + })?; + this.update(&mut cx, |this, cx| { this.dev_server_store .update(cx, |store, cx| store.delete_dev_server(id, cx)) })? - .await + .await?; + + for id in project_ids { + WORKSPACE_DB + .delete_workspace_by_dev_server_project_id(id) + .await + .log_err(); + } + Ok(()) }) .detach_and_prompt_err("Failed to delete dev server", cx, |_, _| None); } + fn delete_dev_server_project( + &mut self, + id: DevServerProjectId, + path: &str, + cx: &mut ViewContext, + ) { + let answer = cx.prompt( + gpui::PromptLevel::Destructive, + format!("Delete \"{}\"?", path).as_str(), + Some("This will delete the remote project. You can always re-add it later."), + &["Delete", "Cancel"], + ); + + cx.spawn(|this, mut cx| async move { + let answer = answer.await?; + + if answer != 0 { + return Ok(()); + } + + this.update(&mut cx, |this, cx| { + this.dev_server_store + .update(cx, |store, cx| store.delete_dev_server_project(id, cx)) + })? + .await?; + + WORKSPACE_DB + .delete_workspace_by_dev_server_project_id(id) + .await + .log_err(); + + Ok(()) + }) + .detach_and_prompt_err("Failed to delete dev server project", cx, |_, _| None); + } + fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext) { - match self.mode { - Mode::Default => {} - Mode::CreateDevServerProject(CreateDevServerProject { dev_server_id, .. }) => { - self.create_dev_server_project(dev_server_id, cx); + match &self.mode { + Mode::Default(None) => {} + Mode::Default(Some(create_project)) => { + self.create_dev_server_project(create_project.dev_server_id, cx); } Mode::CreateDevServer(_) => { self.create_dev_server(cx); @@ -261,9 +307,9 @@ impl DevServerProjects { fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { match self.mode { - Mode::Default => cx.emit(DismissEvent), - Mode::CreateDevServerProject(_) | Mode::CreateDevServer(_) => { - self.mode = Mode::Default; + Mode::Default(None) => cx.emit(DismissEvent), + _ => { + self.mode = Mode::Default(None); self.focus_handle(cx).focus(cx); cx.notify(); } @@ -273,10 +319,17 @@ impl DevServerProjects { fn render_dev_server( &mut self, dev_server: &DevServer, + mut create_project: Option, cx: &mut ViewContext, ) -> impl IntoElement { let dev_server_id = dev_server.id; let status = dev_server.status; + if create_project + .as_ref() + .is_some_and(|cp| cp.dev_server_id != dev_server.id) + { + create_project = None; + } v_flex() .w_full() @@ -341,12 +394,12 @@ impl DevServerProjects { .tooltip(|cx| Tooltip::text("Add a remote project", cx)) .on_click(cx.listener( move |this, _, cx| { - this.mode = - Mode::CreateDevServerProject(CreateDevServerProject { + if let Mode::Default(project) = &mut this.mode { + *project = Some(CreateDevServerProject { dev_server_id, creating: false, - dev_server_project: None, }); + } this.project_path_input.read(cx).focus_handle(cx).focus(cx); cx.notify(); }, @@ -365,17 +418,49 @@ impl DevServerProjects { .py_0p5() .px_3() .child( - List::new().empty_message("No projects.").children( - self.dev_server_store - .read(cx) - .projects_for_server(dev_server.id) - .iter() - .map(|p| self.render_dev_server_project(p, cx)), - ), + List::new() + .empty_message("No projects.") + .children( + self.dev_server_store + .read(cx) + .projects_for_server(dev_server.id) + .iter() + .map(|p| self.render_dev_server_project(p, cx)), + ) + .when_some(create_project, |el, create_project| { + el.child(self.render_create_new_project(&create_project, cx)) + }), ), ) } + fn render_create_new_project( + &mut self, + create_project: &CreateDevServerProject, + _: &mut ViewContext, + ) -> impl IntoElement { + ListItem::new("create-remote-project") + .start_slot(Icon::new(IconName::FileTree).color(Color::Muted)) + .child(self.project_path_input.clone()) + .child( + div() + .w(IconSize::Medium.rems()) + .when(create_project.creating, |el| { + el.child( + Icon::new(IconName::ArrowCircle) + .size(IconSize::Medium) + .with_animation( + "arrow-circle", + Animation::new(Duration::from_secs(2)).repeat(), + |icon, delta| { + icon.transform(Transformation::rotate(percentage(delta))) + }, + ), + ) + }), + ) + } + fn render_dev_server_project( &mut self, project: &DevServerProject, @@ -384,6 +469,7 @@ impl DevServerProjects { let dev_server_project_id = project.id; let project_id = project.project_id; let is_online = project_id.is_some(); + let project_path = project.path.clone(); ListItem::new(("remote-project", dev_server_project_id.0)) .start_slot(Icon::new(IconName::FileTree).when(!is_online, |icon| icon.color(Color::Muted))) @@ -402,6 +488,11 @@ impl DevServerProjects { }).detach(); } })) + .end_hover_slot::(Some(IconButton::new("remove-remote-project", IconName::Trash) + .on_click(cx.listener(move |this, _, cx| { + this.delete_dev_server_project(dev_server_project_id, &project_path, cx) + })) + .tooltip(|cx| Tooltip::text("Delete remote project", cx)).into_any_element())) } fn render_create_dev_server(&mut self, cx: &mut ViewContext) -> impl IntoElement { @@ -559,6 +650,11 @@ impl DevServerProjects { fn render_default(&mut self, cx: &mut ViewContext) -> impl IntoElement { let dev_servers = self.dev_server_store.read(cx).dev_servers(); + let Mode::Default(create_dev_server_project) = &self.mode else { + unreachable!() + }; + let create_dev_server_project = create_dev_server_project.clone(); + v_flex() .id("scroll-container") .h_full() @@ -597,126 +693,131 @@ impl DevServerProjects { ), )) .children(dev_servers.iter().map(|dev_server| { - self.render_dev_server(dev_server, cx).into_any_element() + self.render_dev_server( + dev_server, + create_dev_server_project.clone(), + cx, + ) + .into_any_element() })), ), ) } - fn render_create_dev_server_project(&self, cx: &mut ViewContext) -> impl IntoElement { - let Mode::CreateDevServerProject(CreateDevServerProject { - dev_server_id, - creating, - dev_server_project, - }) = &self.mode - else { - unreachable!() - }; + // fn render_create_dev_server_project(&self, cx: &mut ViewContext) -> impl IntoElement { + // let Mode::CreateDevServerProject(CreateDevServerProject { + // dev_server_id, + // creating, + // dev_server_project, + // }) = &self.mode + // else { + // unreachable!() + // }; - let dev_server = self - .dev_server_store - .read(cx) - .dev_server(*dev_server_id) - .cloned(); + // let dev_server = self + // .dev_server_store + // .read(cx) + // .dev_server(*dev_server_id) + // .cloned(); - let (dev_server_name, dev_server_status) = dev_server - .map(|server| (server.name, server.status)) - .unwrap_or((SharedString::from(""), DevServerStatus::Offline)); + // let (dev_server_name, dev_server_status) = dev_server + // .map(|server| (server.name, server.status)) + // .unwrap_or((SharedString::from(""), DevServerStatus::Offline)); - v_flex() - .px_1() - .pt_0p5() - .gap_px() - .child( - v_flex().py_0p5().px_1().child( - h_flex() - .px_1() - .py_0p5() - .child( - IconButton::new("back", IconName::ArrowLeft) - .style(ButtonStyle::Transparent) - .on_click(cx.listener(|_, _: &gpui::ClickEvent, cx| { - cx.dispatch_action(menu::Cancel.boxed_clone()) - })), - ) - .child(Headline::new("Add remote project").size(HeadlineSize::Small)), - ), - ) - .child( - h_flex() - .ml_5() - .gap_2() - .child( - div() - .id(("status", dev_server_id.0)) - .relative() - .child(Icon::new(IconName::Server)) - .child(div().absolute().bottom_0().left(rems_from_px(12.0)).child( - Indicator::dot().color(match dev_server_status { - DevServerStatus::Online => Color::Created, - DevServerStatus::Offline => Color::Hidden, - }), - )) - .tooltip(move |cx| { - Tooltip::text( - match dev_server_status { - DevServerStatus::Online => "Online", - DevServerStatus::Offline => "Offline", - }, - cx, - ) - }), - ) - .child(dev_server_name.clone()), - ) - .child( - h_flex() - .ml_5() - .gap_2() - .child(self.project_path_input.clone()) - .when(!*creating && dev_server_project.is_none(), |div| { - div.child(Button::new("create-remote-server", "Create").on_click({ - let dev_server_id = *dev_server_id; - cx.listener(move |this, _, cx| { - this.create_dev_server_project(dev_server_id, cx) - }) - })) - }) - .when(*creating, |div| { - div.child(Button::new("create-dev-server", "Creating...").disabled(true)) - }), - ) - .when_some(dev_server_project.clone(), |div, dev_server_project| { - let status = self - .dev_server_store - .read(cx) - .dev_server_project(DevServerProjectId(dev_server_project.id)) - .map(|project| { - if project.project_id.is_some() { - DevServerStatus::Online - } else { - DevServerStatus::Offline - } - }) - .unwrap_or(DevServerStatus::Offline); - div.child( - v_flex() - .ml_5() - .ml_8() - .gap_2() - .when(status == DevServerStatus::Offline, |this| { - this.child(Label::new("Waiting for project...")) - }) - .when(status == DevServerStatus::Online, |this| { - this.child(Label::new("Project online! 🎊")).child( - Button::new("done", "Done").on_click(cx.listener(|_, _, cx| { - cx.dispatch_action(menu::Cancel.boxed_clone()) - })), - ) - }), - ) - }) - } + // v_flex() + // .px_1() + // .pt_0p5() + // .gap_px() + // .child( + // v_flex().py_0p5().px_1().child( + // h_flex() + // .px_1() + // .py_0p5() + // .child( + // IconButton::new("back", IconName::ArrowLeft) + // .style(ButtonStyle::Transparent) + // .on_click(cx.listener(|_, _: &gpui::ClickEvent, cx| { + // cx.dispatch_action(menu::Cancel.boxed_clone()) + // })), + // ) + // .child(Headline::new("Add remote project").size(HeadlineSize::Small)), + // ), + // ) + // .child( + // h_flex() + // .ml_5() + // .gap_2() + // .child( + // div() + // .id(("status", dev_server_id.0)) + // .relative() + // .child(Icon::new(IconName::Server)) + // .child(div().absolute().bottom_0().left(rems_from_px(12.0)).child( + // Indicator::dot().color(match dev_server_status { + // DevServerStatus::Online => Color::Created, + // DevServerStatus::Offline => Color::Hidden, + // }), + // )) + // .tooltip(move |cx| { + // Tooltip::text( + // match dev_server_status { + // DevServerStatus::Online => "Online", + // DevServerStatus::Offline => "Offline", + // }, + // cx, + // ) + // }), + // ) + // .child(dev_server_name.clone()), + // ) + // .child( + // h_flex() + // .ml_5() + // .gap_2() + // .child(self.project_path_input.clone()) + // .when(!*creating && dev_server_project.is_none(), |div| { + // div.child(Button::new("create-remote-server", "Create").on_click({ + // let dev_server_id = *dev_server_id; + // cx.listener(move |this, _, cx| { + // this.create_dev_server_project(dev_server_id, cx) + // }) + // })) + // }) + // .when(*creating, |div| { + // div.child(Button::new("create-dev-server", "Creating...").disabled(true)) + // }), + // ) + // .when_some(dev_server_project.clone(), |div, dev_server_project| { + // let status = self + // .dev_server_store + // .read(cx) + // .dev_server_project(DevServerProjectId(dev_server_project.id)) + // .map(|project| { + // if project.project_id.is_some() { + // DevServerStatus::Online + // } else { + // DevServerStatus::Offline + // } + // }) + // .unwrap_or(DevServerStatus::Offline); + // div.child( + // v_flex() + // .ml_5() + // .ml_8() + // .gap_2() + // .when(status == DevServerStatus::Offline, |this| { + // this.child(Label::new("Waiting for project...")) + // }) + // .when(status == DevServerStatus::Online, |this| { + // this.child(Label::new("Project online! 🎊")).child( + // Button::new("done", "Done").on_click(cx.listener(|_, _, cx| { + // cx.dispatch_action(menu::Cancel.boxed_clone()) + // })), + // ) + // }), + // ) + // }) + // } } impl ModalView for DevServerProjects {} @@ -737,7 +838,7 @@ impl Render for DevServerProjects { .on_action(cx.listener(Self::cancel)) .on_action(cx.listener(Self::confirm)) .on_mouse_down_out(cx.listener(|this, _, cx| { - if matches!(this.mode, Mode::Default) { + if matches!(this.mode, Mode::Default(None)) { cx.emit(DismissEvent) } })) @@ -746,10 +847,7 @@ impl Render for DevServerProjects { .min_h(rems(20.)) .max_h(rems(40.)) .child(match &self.mode { - Mode::Default => self.render_default(cx).into_any_element(), - Mode::CreateDevServerProject(_) => { - self.render_create_dev_server_project(cx).into_any_element() - } + Mode::Default(_) => self.render_default(cx).into_any_element(), Mode::CreateDevServer(_) => self.render_create_dev_server(cx).into_any_element(), }) } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index cde0908ba0..3dfa9508dc 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -237,7 +237,8 @@ message Envelope { DevServerProjectsUpdate dev_server_projects_update = 193; ValidateDevServerProjectRequest validate_dev_server_project_request = 194; DeleteDevServer delete_dev_server = 195; - OpenNewBuffer open_new_buffer = 196; // Current max + OpenNewBuffer open_new_buffer = 196; + DeleteDevServerProject delete_dev_server_project = 197; // Current max } reserved 158 to 161; @@ -498,6 +499,10 @@ message DeleteDevServer { uint64 dev_server_id = 1; } +message DeleteDevServerProject { + uint64 dev_server_project_id = 1; +} + message ReconnectDevServer { repeated UpdateProject reshared_projects = 1; } diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 0394ba33ba..966a24ead9 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -320,6 +320,7 @@ messages!( (DevServerProjectsUpdate, Foreground), (ValidateDevServerProjectRequest, Background), (DeleteDevServer, Foreground), + (DeleteDevServerProject, Foreground), (OpenNewBuffer, Foreground) ); @@ -425,6 +426,7 @@ request_messages!( (ValidateDevServerProjectRequest, Ack), (MultiLspQuery, MultiLspQueryResponse), (DeleteDevServer, Ack), + (DeleteDevServerProject, Ack), ); entity_messages!( diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 10a5eae004..04639b5011 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -576,6 +576,22 @@ impl WorkspaceDb { } } + pub async fn delete_workspace_by_dev_server_project_id( + &self, + id: DevServerProjectId, + ) -> Result<()> { + self.write(move |conn| { + conn.exec_bound(sql!( + DELETE FROM dev_server_projects WHERE id = ? + ))?(id.0)?; + conn.exec_bound(sql!( + DELETE FROM workspaces + WHERE dev_server_project_id IS ? + ))?(id.0) + }) + .await + } + // Returns the recent locations which are still valid on disk and deletes ones which no longer // exist. pub async fn recent_workspaces_on_disk(