@@ -315,6 +315,139 @@ async fn test_dev_server_delete(
})
}
+#[gpui::test]
+async fn test_dev_server_rename(
+ 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.rename_dev_server(
+ store.dev_servers().first().unwrap().id,
+ "name-edited".to_string(),
+ cx,
+ )
+ })
+ })
+ .await
+ .unwrap();
+
+ cx1.executor().run_until_parked();
+
+ cx1.update(|cx| {
+ dev_server_projects::Store::global(cx).update(cx, |store, _| {
+ assert_eq!(store.dev_servers().first().unwrap().name, "name-edited");
+ })
+ })
+}
+
+#[gpui::test]
+async fn test_dev_server_refresh_access_token(
+ cx1: &mut gpui::TestAppContext,
+ cx2: &mut gpui::TestAppContext,
+ cx3: &mut gpui::TestAppContext,
+ cx4: &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();
+
+ // Regenerate the access token
+ let new_token_response = cx1
+ .update(|cx| {
+ dev_server_projects::Store::global(cx).update(cx, |store, cx| {
+ store.regenerate_dev_server_token(store.dev_servers().first().unwrap().id, cx)
+ })
+ })
+ .await
+ .unwrap();
+
+ cx1.executor().run_until_parked();
+
+ // Assert that the other client was disconnected
+ let (workspace, cx2) = client2.active_workspace(cx2);
+ cx2.update(|cx| assert!(workspace.read(cx).project().read(cx).is_disconnected()));
+
+ // Assert that the owner of the dev server does not see the dev server as online anymore
+ let (workspace, cx1) = client1.active_workspace(cx1);
+ cx1.update(|cx| {
+ assert!(workspace.read(cx).project().read(cx).is_disconnected());
+ dev_server_projects::Store::global(cx).update(cx, |store, _| {
+ assert_eq!(
+ store.dev_servers().first().unwrap().status,
+ DevServerStatus::Offline
+ );
+ })
+ });
+
+ // Reconnect the dev server with the new token
+ let _dev_server = server
+ .create_dev_server(new_token_response.access_token, cx4)
+ .await;
+
+ cx1.executor().run_until_parked();
+
+ // Assert that the dev server is online again
+ cx1.update(|cx| {
+ dev_server_projects::Store::global(cx).update(cx, |store, _| {
+ assert_eq!(store.dev_servers().len(), 1);
+ assert_eq!(
+ store.dev_servers().first().unwrap().status,
+ DevServerStatus::Online
+ );
+ })
+ });
+}
+
#[gpui::test]
async fn test_dev_server_reconnect(
cx1: &mut gpui::TestAppContext,
@@ -10,7 +10,7 @@ use gpui::{
View, ViewContext,
};
use rpc::{
- proto::{CreateDevServerResponse, DevServerStatus},
+ proto::{CreateDevServerResponse, DevServerStatus, RegenerateDevServerTokenResponse},
ErrorCode, ErrorExt,
};
use settings::Settings;
@@ -29,15 +29,30 @@ pub struct DevServerProjects {
dev_server_store: Model<dev_server_projects::Store>,
project_path_input: View<Editor>,
dev_server_name_input: View<TextField>,
+ rename_dev_server_input: View<TextField>,
_subscription: gpui::Subscription,
}
-#[derive(Default)]
+#[derive(Default, Clone)]
struct CreateDevServer {
creating: bool,
dev_server: Option<CreateDevServerResponse>,
}
+#[derive(Clone)]
+struct EditDevServer {
+ dev_server_id: DevServerId,
+ state: EditDevServerState,
+}
+
+#[derive(Clone, PartialEq)]
+enum EditDevServerState {
+ Default,
+ RenamingDevServer,
+ RegeneratingToken,
+ RegeneratedToken(RegenerateDevServerTokenResponse),
+}
+
#[derive(Clone)]
struct CreateDevServerProject {
dev_server_id: DevServerId,
@@ -47,6 +62,7 @@ struct CreateDevServerProject {
enum Mode {
Default(Option<CreateDevServerProject>),
CreateDevServer(CreateDevServer),
+ EditDevServer(EditDevServer),
}
impl DevServerProjects {
@@ -83,6 +99,8 @@ impl DevServerProjects {
});
let dev_server_name_input =
cx.new_view(|cx| TextField::new(cx, "Name", "").with_label(FieldLabelLayout::Stacked));
+ let rename_dev_server_input =
+ cx.new_view(|cx| TextField::new(cx, "Name", "").with_label(FieldLabelLayout::Stacked));
let focus_handle = cx.focus_handle();
let dev_server_store = dev_server_projects::Store::global(cx);
@@ -98,6 +116,7 @@ impl DevServerProjects {
dev_server_store,
project_path_input,
dev_server_name_input,
+ rename_dev_server_input,
_subscription: subscription,
}
}
@@ -225,6 +244,88 @@ impl DevServerProjects {
cx.notify()
}
+ fn rename_dev_server(&mut self, id: DevServerId, cx: &mut ViewContext<Self>) {
+ let name = self
+ .rename_dev_server_input
+ .read(cx)
+ .editor()
+ .read(cx)
+ .text(cx)
+ .trim()
+ .to_string();
+
+ let Some(dev_server) = self.dev_server_store.read(cx).dev_server(id) else {
+ return;
+ };
+
+ if name.is_empty() || dev_server.name == name {
+ return;
+ }
+
+ let request = self
+ .dev_server_store
+ .update(cx, |store, cx| store.rename_dev_server(id, name, cx));
+
+ self.mode = Mode::EditDevServer(EditDevServer {
+ dev_server_id: id,
+ state: EditDevServerState::RenamingDevServer,
+ });
+
+ cx.spawn(|this, mut cx| async move {
+ request.await?;
+ this.update(&mut cx, move |this, cx| {
+ this.mode = Mode::EditDevServer(EditDevServer {
+ dev_server_id: id,
+ state: EditDevServerState::Default,
+ });
+ cx.notify();
+ })
+ })
+ .detach_and_prompt_err("Failed to rename dev server", cx, |_, _| None);
+ }
+
+ fn refresh_dev_server_token(&mut self, id: DevServerId, cx: &mut ViewContext<Self>) {
+ let answer = cx.prompt(
+ gpui::PromptLevel::Warning,
+ "Are you sure?",
+ Some("This will invalidate the existing dev server token."),
+ &["Generate", "Cancel"],
+ );
+ cx.spawn(|this, mut cx| async move {
+ let answer = answer.await?;
+
+ if answer != 0 {
+ return Ok(());
+ }
+
+ let response = this
+ .update(&mut cx, move |this, cx| {
+ let request = this
+ .dev_server_store
+ .update(cx, |store, cx| store.regenerate_dev_server_token(id, cx));
+ this.mode = Mode::EditDevServer(EditDevServer {
+ dev_server_id: id,
+ state: EditDevServerState::RegeneratingToken,
+ });
+ cx.notify();
+ request
+ })?
+ .await?;
+
+ this.update(&mut cx, move |this, cx| {
+ this.mode = Mode::EditDevServer(EditDevServer {
+ dev_server_id: id,
+ state: EditDevServerState::RegeneratedToken(response),
+ });
+ cx.notify();
+ })
+ .log_err();
+
+ Ok(())
+ })
+ .detach_and_prompt_err("Failed to delete dev server", cx, |_, _| None);
+ }
+
fn delete_dev_server(&mut self, id: DevServerId, cx: &mut ViewContext<Self>) {
let answer = cx.prompt(
gpui::PromptLevel::Destructive,
@@ -314,6 +415,17 @@ impl DevServerProjects {
self.create_dev_server(cx);
}
}
+ Mode::EditDevServer(edit_dev_server) => {
+ if self
+ .rename_dev_server_input
+ .read(cx)
+ .editor()
+ .read(cx)
+ .is_focused(cx)
+ {
+ self.rename_dev_server(edit_dev_server.dev_server_id, cx);
+ }
+ }
}
}
@@ -336,6 +448,7 @@ impl DevServerProjects {
) -> impl IntoElement {
let dev_server_id = dev_server.id;
let status = dev_server.status;
+ let dev_server_name = dev_server.name.clone();
if create_project
.as_ref()
.is_some_and(|cp| cp.dev_server_id != dev_server.id)
@@ -375,17 +488,32 @@ impl DevServerProjects {
)
}),
)
- .child(dev_server.name.clone())
+ .child(dev_server_name.clone())
.child(
h_flex()
.visible_on_hover("dev-server")
.gap_1()
.child(
IconButton::new("edit-dev-server", IconName::Pencil)
- .disabled(true) //TODO implement this on the collab side
- .tooltip(|cx| {
- Tooltip::text("Coming Soon - Edit dev server", cx)
- }),
+ .on_click(cx.listener(move |this, _, cx| {
+ this.mode = Mode::EditDevServer(EditDevServer {
+ dev_server_id,
+ state: EditDevServerState::Default,
+ });
+ let dev_server_name = dev_server_name.clone();
+ this.rename_dev_server_input.update(
+ cx,
+ move |input, cx| {
+ input.editor().update(
+ cx,
+ move |editor, cx| {
+ editor.set_text(dev_server_name, cx)
+ },
+ )
+ },
+ )
+ }))
+ .tooltip(|cx| Tooltip::text("Edit dev server", cx)),
)
.child({
let dev_server_id = dev_server.id;
@@ -507,17 +635,18 @@ impl DevServerProjects {
.tooltip(|cx| Tooltip::text("Delete remote project", cx)).into_any_element()))
}
- fn render_create_dev_server(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
- let Mode::CreateDevServer(CreateDevServer {
+ fn render_create_dev_server(
+ &mut self,
+ state: CreateDevServer,
+ cx: &mut ViewContext<Self>,
+ ) -> impl IntoElement {
+ let CreateDevServer {
creating,
dev_server,
- }) = &self.mode
- else {
- unreachable!()
- };
+ } = state;
self.dev_server_name_input.update(cx, |input, cx| {
- input.set_disabled(*creating || dev_server.is_some(), cx);
+ input.set_disabled(creating || dev_server.is_some(), cx);
});
v_flex()
@@ -529,7 +658,7 @@ impl DevServerProjects {
.pt_0p5()
.gap_px()
.child(
- ModalHeader::new("remote-projects")
+ ModalHeader::new("create-dev-server")
.show_back_button(true)
.child(Headline::new("New dev server").size(HeadlineSize::Small)),
)
@@ -555,14 +684,14 @@ impl DevServerProjects {
div()
.pl_1()
.pb(px(3.))
- .when(!*creating && dev_server.is_none(), |div| {
+ .when(!creating && dev_server.is_none(), |div| {
div.child(Button::new("create-dev-server", "Create").on_click(
cx.listener(move |this, _, cx| {
this.create_dev_server(cx);
}),
))
})
- .when(*creating && dev_server.is_none(), |div| {
+ .when(creating && dev_server.is_none(), |div| {
div.child(
Button::new("create-dev-server", "Creating...")
.disabled(true),
@@ -579,86 +708,212 @@ impl DevServerProjects {
.read(cx)
.dev_server_status(DevServerId(dev_server.dev_server_id));
- let instructions = SharedString::from(format!(
- "zed --dev-server-token {}",
- dev_server.access_token
- ));
div.child(
- v_flex()
- .pl_2()
- .pt_2()
- .gap_2()
- .child(
- h_flex().justify_between().w_full()
- .child(Label::new(format!(
- "Please log into `{}` and run:",
- dev_server.name
- )))
- .child(
- Button::new("copy-access-token", "Copy Instructions")
- .icon(Some(IconName::Copy))
- .icon_size(IconSize::Small)
- .on_click({
- let instructions = instructions.clone();
- cx.listener(move |_, _, cx| {
- cx.write_to_clipboard(ClipboardItem::new(
- instructions.to_string(),
- ))
- })})
- )
- )
- .child(
- v_flex()
- .w_full()
- .bg(cx.theme().colors().title_bar_background) // todo: this should be distinct
- .border()
- .border_color(cx.theme().colors().border_variant)
- .rounded_md()
- .my_1()
- .py_0p5()
- .px_3()
- .font_family(ThemeSettings::get_global(cx).buffer_font.family.clone())
- .child(Label::new(instructions))
- )
- .when(status == DevServerStatus::Offline, |this| {
- this.child(
-
- h_flex()
- .gap_2()
- .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)))
- },
- ),
- )
- .child(
- Label::new("Waiting for connectionβ¦"),
- )
- )
- })
- .when(status == DevServerStatus::Online, |this| {
- this.child(Label::new("π Connection established!"))
- .child(
- h_flex().justify_end().child(
- Button::new("done", "Done").on_click(cx.listener(
- |_, _, cx| {
- cx.dispatch_action(menu::Cancel.boxed_clone())
- },
- ))
- ),
- )
- }),
+ Self::render_dev_server_token_instructions(&dev_server.access_token, &dev_server.name, status, cx)
)
}),
)
)
}
+ fn render_dev_server_token_instructions(
+ access_token: &str,
+ dev_server_name: &str,
+ status: DevServerStatus,
+ cx: &mut ViewContext<Self>,
+ ) -> Div {
+ let instructions = SharedString::from(format!("zed --dev-server-token {}", access_token));
+
+ v_flex()
+ .pl_2()
+ .pt_2()
+ .gap_2()
+ .child(
+ h_flex()
+ .justify_between()
+ .w_full()
+ .child(Label::new(format!(
+ "Please log into `{}` and run:",
+ dev_server_name
+ )))
+ .child(
+ Button::new("copy-access-token", "Copy Instructions")
+ .icon(Some(IconName::Copy))
+ .icon_size(IconSize::Small)
+ .on_click({
+ let instructions = instructions.clone();
+ cx.listener(move |_, _, cx| {
+ cx.write_to_clipboard(ClipboardItem::new(
+ instructions.to_string(),
+ ))
+ })
+ }),
+ ),
+ )
+ .child(
+ v_flex()
+ .w_full()
+ .bg(cx.theme().colors().title_bar_background) // todo: this should be distinct
+ .border()
+ .border_color(cx.theme().colors().border_variant)
+ .rounded_md()
+ .my_1()
+ .py_0p5()
+ .px_3()
+ .font_family(ThemeSettings::get_global(cx).buffer_font.family.clone())
+ .child(Label::new(instructions)),
+ )
+ .when(status == DevServerStatus::Offline, |this| {
+ this.child(Self::render_loading_spinner("Waiting for connectionβ¦"))
+ })
+ .when(status == DevServerStatus::Online, |this| {
+ this.child(Label::new("π Connection established!")).child(
+ h_flex()
+ .justify_end()
+ .child(Button::new("done", "Done").on_click(
+ cx.listener(|_, _, cx| cx.dispatch_action(menu::Cancel.boxed_clone())),
+ )),
+ )
+ })
+ }
+
+ fn render_loading_spinner(label: impl Into<SharedString>) -> Div {
+ h_flex()
+ .gap_2()
+ .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))),
+ ),
+ )
+ .child(Label::new(label))
+ }
+
+ fn render_edit_dev_server(
+ &mut self,
+ edit_dev_server: EditDevServer,
+ cx: &mut ViewContext<Self>,
+ ) -> impl IntoElement {
+ let dev_server_id = edit_dev_server.dev_server_id;
+ let dev_server = self
+ .dev_server_store
+ .read(cx)
+ .dev_server(dev_server_id)
+ .cloned();
+
+ let dev_server_name = dev_server
+ .as_ref()
+ .map(|dev_server| dev_server.name.clone())
+ .unwrap_or_default();
+
+ let dev_server_status = dev_server
+ .map(|dev_server| dev_server.status)
+ .unwrap_or(DevServerStatus::Offline);
+
+ let disabled = matches!(
+ edit_dev_server.state,
+ EditDevServerState::RenamingDevServer | EditDevServerState::RegeneratingToken
+ );
+ self.rename_dev_server_input.update(cx, |input, cx| {
+ input.set_disabled(disabled, cx);
+ });
+
+ let rename_dev_server_input_text = self
+ .rename_dev_server_input
+ .read(cx)
+ .editor()
+ .read(cx)
+ .text(cx);
+
+ let content = v_flex().w_full().gap_2().child(
+ h_flex()
+ .pb_2()
+ .border_b_1()
+ .border_color(cx.theme().colors().border)
+ .items_end()
+ .w_full()
+ .px_2()
+ .child(
+ div()
+ .pl_2()
+ .max_w(rems(16.))
+ .child(self.rename_dev_server_input.clone()),
+ )
+ .child(
+ div()
+ .pl_1()
+ .pb(px(3.))
+ .when(
+ edit_dev_server.state != EditDevServerState::RenamingDevServer,
+ |div| {
+ div.child(
+ Button::new("rename-dev-server", "Rename")
+ .disabled(
+ rename_dev_server_input_text.trim().is_empty()
+ || rename_dev_server_input_text == dev_server_name,
+ )
+ .on_click(cx.listener(move |this, _, cx| {
+ this.rename_dev_server(dev_server_id, cx);
+ cx.notify();
+ })),
+ )
+ },
+ )
+ .when(
+ edit_dev_server.state == EditDevServerState::RenamingDevServer,
+ |div| {
+ div.child(
+ Button::new("rename-dev-server", "Renaming...").disabled(true),
+ )
+ },
+ ),
+ ),
+ );
+
+ let content = content.child(match edit_dev_server.state {
+ EditDevServerState::RegeneratingToken => {
+ Self::render_loading_spinner("Generating token...")
+ }
+ EditDevServerState::RegeneratedToken(response) => {
+ Self::render_dev_server_token_instructions(
+ &response.access_token,
+ &dev_server_name,
+ dev_server_status,
+ cx,
+ )
+ }
+ _ => h_flex().items_end().w_full().child(
+ Button::new("regenerate-dev-server-token", "Generate new access token")
+ .icon(IconName::Update)
+ .on_click(cx.listener(move |this, _, cx| {
+ this.refresh_dev_server_token(dev_server_id, cx);
+ cx.notify();
+ })),
+ ),
+ });
+
+ v_flex()
+ .id("scroll-container")
+ .h_full()
+ .overflow_y_scroll()
+ .track_scroll(&self.scroll_handle)
+ .px_1()
+ .pt_0p5()
+ .gap_px()
+ .child(
+ ModalHeader::new("edit-dev-server")
+ .show_back_button(true)
+ .child(
+ Headline::new(format!("Edit {}", &dev_server_name))
+ .size(HeadlineSize::Small),
+ ),
+ )
+ .child(ModalContent::new().child(v_flex().w_full().child(content)))
+ }
+
fn render_default(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let dev_servers = self.dev_server_store.read(cx).dev_servers();
@@ -715,121 +970,6 @@ impl DevServerProjects {
),
)
}
-
- // fn render_create_dev_server_project(&self, cx: &mut ViewContext<Self>) -> 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_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())
- // })),
- // )
- // }),
- // )
- // })
- // }
}
impl ModalView for DevServerProjects {}
@@ -860,7 +1000,12 @@ impl Render for DevServerProjects {
.max_h(rems(40.))
.child(match &self.mode {
Mode::Default(_) => self.render_default(cx).into_any_element(),
- Mode::CreateDevServer(_) => self.render_create_dev_server(cx).into_any_element(),
+ Mode::CreateDevServer(state) => self
+ .render_create_dev_server(state.clone(), cx)
+ .into_any_element(),
+ Mode::EditDevServer(state) => self
+ .render_edit_dev_server(state.clone(), cx)
+ .into_any_element(),
})
}
}