Show dev server in the titlebar for remote projects (#11276)

Conrad Irwin and Mikayla created

Co-Authored-By: Mikayla <mikayla@zed.dev>

Release Notes:

- Show server name in the titlebar for remote projects

Co-authored-by: Mikayla <mikayla@zed.dev>

Change summary

Cargo.lock                                    |  1 
crates/collab_ui/Cargo.toml                   |  1 
crates/collab_ui/src/collab_titlebar_item.rs  | 43 +++++++++++++++++++-
crates/recent_projects/src/recent_projects.rs |  2 
crates/recent_projects/src/remote_projects.rs |  6 ++
crates/remote_projects/src/remote_projects.rs |  5 ++
6 files changed, 53 insertions(+), 5 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2359,6 +2359,7 @@ dependencies = [
  "pretty_assertions",
  "project",
  "recent_projects",
+ "remote_projects",
  "rich_text",
  "rpc",
  "schemars",

crates/collab_ui/Cargo.toml 🔗

@@ -50,6 +50,7 @@ parking_lot.workspace = true
 picker.workspace = true
 project.workspace = true
 recent_projects.workspace = true
+remote_projects.workspace = true
 rich_text.workspace = true
 rpc.workspace = true
 schemars.workspace = true

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -9,12 +9,12 @@ use gpui::{
 };
 use project::{Project, RepositoryEntry};
 use recent_projects::RecentProjects;
-use rpc::proto;
+use rpc::proto::{self, DevServerStatus};
 use std::sync::Arc;
 use theme::ActiveTheme;
 use ui::{
     h_flex, popover_menu, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike,
-    ButtonStyle, ContextMenu, Icon, IconButton, IconName, TintColor, TitleBar, Tooltip,
+    ButtonStyle, ContextMenu, Icon, IconButton, IconName, Indicator, TintColor, TitleBar, Tooltip,
 };
 use util::ResultExt;
 use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu};
@@ -375,7 +375,41 @@ impl CollabTitlebarItem {
     // resolve if you are in a room -> render_project_owner
     // render_project_owner -> resolve if you are in a room -> Option<foo>
 
-    pub fn render_project_host(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
+    pub fn render_project_host(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
+        if let Some(dev_server) =
+            self.project
+                .read(cx)
+                .remote_project_id()
+                .and_then(|remote_project_id| {
+                    remote_projects::Store::global(cx)
+                        .read(cx)
+                        .dev_server_for_project(remote_project_id)
+                })
+        {
+            return Some(
+                ButtonLike::new("dev_server_trigger")
+                    .child(Indicator::dot().color(
+                        if dev_server.status == DevServerStatus::Online {
+                            Color::Created
+                        } else {
+                            Color::Disabled
+                        },
+                    ))
+                    .child(
+                        Label::new(dev_server.name.clone())
+                            .size(LabelSize::Small)
+                            .line_height_style(LineHeightStyle::UiLabel),
+                    )
+                    .tooltip(move |cx| Tooltip::text("Project is hosted on a dev server", cx))
+                    .on_click(cx.listener(|this, _, cx| {
+                        if let Some(workspace) = this.workspace.upgrade() {
+                            recent_projects::RemoteProjects::open(workspace, cx)
+                        }
+                    }))
+                    .into_any_element(),
+            );
+        }
+
         let host = self.project.read(cx).host()?;
         let host_user = self.user_store.read(cx).get_cached_user(host.user_id)?;
         let participant_index = self
@@ -406,7 +440,8 @@ impl CollabTitlebarItem {
                             })
                             .log_err();
                     })
-                }),
+                })
+                .into_any_element(),
         )
     }
 

crates/recent_projects/src/recent_projects.rs 🔗

@@ -11,7 +11,7 @@ use picker::{
     highlighted_match_with_paths::{HighlightedMatchWithPaths, HighlightedText},
     Picker, PickerDelegate,
 };
-use remote_projects::RemoteProjects;
+pub use remote_projects::RemoteProjects;
 use rpc::proto::DevServerStatus;
 use serde::Deserialize;
 use std::{

crates/recent_projects/src/remote_projects.rs 🔗

@@ -60,6 +60,12 @@ impl RemoteProjects {
         .detach();
     }
 
+    pub fn open(workspace: View<Workspace>, cx: &mut WindowContext) {
+        workspace.update(cx, |workspace, cx| {
+            workspace.toggle_modal(cx, |cx| Self::new(cx))
+        })
+    }
+
     pub fn new(cx: &mut ViewContext<Self>) -> Self {
         let remote_project_path_input = cx.new_view(|cx| TextField::new(cx, "", "Project path"));
         let dev_server_name_input =

crates/remote_projects/src/remote_projects.rs 🔗

@@ -114,6 +114,11 @@ impl Store {
         self.remote_projects.get(&id)
     }
 
+    pub fn dev_server_for_project(&self, id: RemoteProjectId) -> Option<&DevServer> {
+        self.remote_project(id)
+            .and_then(|project| self.dev_server(project.dev_server_id))
+    }
+
     async fn handle_remote_projects_update(
         this: Model<Self>,
         envelope: TypedEnvelope<proto::RemoteProjectsUpdate>,