Filter out empty projects in contacts panel

Nathan Sobo created

Change summary

crates/client/src/user.rs                   |   6 
crates/contacts_panel/src/contacts_panel.rs | 239 +++++++++++-----------
2 files changed, 129 insertions(+), 116 deletions(-)

Detailed changes

crates/client/src/user.rs 🔗

@@ -501,6 +501,12 @@ impl Contact {
         }
         Ok(Self { user, projects })
     }
+
+    pub fn non_empty_projects(&self) -> impl Iterator<Item = &ProjectMetadata> {
+        self.projects
+            .iter()
+            .filter(|project| !project.worktree_root_names.is_empty())
+    }
 }
 
 async fn fetch_avatar(http: &dyn HttpClient, url: &str) -> Result<Arc<ImageData>> {

crates/contacts_panel/src/contacts_panel.rs 🔗

@@ -149,7 +149,7 @@ impl ContactsPanel {
         theme: &theme::ContactsPanel,
         cx: &mut LayoutContext,
     ) -> ElementBox {
-        let project_count = contact.projects.len();
+        let project_count = contact.non_empty_projects().count();
         let font_cache = cx.font_cache();
         let line_height = theme.unshared_project.name.text.line_height(font_cache);
         let cap_height = theme.unshared_project.name.text.cap_height(font_cache);
@@ -188,121 +188,128 @@ impl ContactsPanel {
                     .with_height(theme.row_height)
                     .boxed(),
             )
-            .with_children(contact.projects.iter().enumerate().map(|(ix, project)| {
-                let project_id = project.id;
-
-                Flex::row()
-                    .with_child(
-                        Canvas::new(move |bounds, _, cx| {
-                            let start_x =
-                                bounds.min_x() + (bounds.width() / 2.) - (tree_branch_width / 2.);
-                            let end_x = bounds.max_x();
-                            let start_y = bounds.min_y();
-                            let end_y = bounds.min_y() + baseline_offset - (cap_height / 2.);
-
-                            cx.scene.push_quad(gpui::Quad {
-                                bounds: RectF::from_points(
-                                    vec2f(start_x, start_y),
-                                    vec2f(
-                                        start_x + tree_branch_width,
-                                        if ix + 1 == project_count {
-                                            end_y
-                                        } else {
-                                            bounds.max_y()
-                                        },
-                                    ),
-                                ),
-                                background: Some(tree_branch_color),
-                                border: gpui::Border::default(),
-                                corner_radius: 0.,
-                            });
-                            cx.scene.push_quad(gpui::Quad {
-                                bounds: RectF::from_points(
-                                    vec2f(start_x, end_y),
-                                    vec2f(end_x, end_y + tree_branch_width),
-                                ),
-                                background: Some(tree_branch_color),
-                                border: gpui::Border::default(),
-                                corner_radius: 0.,
-                            });
-                        })
-                        .constrained()
-                        .with_width(host_avatar_height)
-                        .boxed(),
-                    )
-                    .with_child({
-                        let is_host = Some(contact.user.id) == current_user_id;
-                        let is_guest = !is_host
-                            && project
-                                .guests
-                                .iter()
-                                .any(|guest| Some(guest.id) == current_user_id);
-                        let is_shared = project.is_shared;
-                        let app_state = app_state.clone();
-
-                        MouseEventHandler::new::<ContactsPanel, _, _>(
-                            project_id as usize,
-                            cx,
-                            |mouse_state, _| {
-                                let style = match (project.is_shared, mouse_state.hovered) {
-                                    (false, false) => &theme.unshared_project,
-                                    (false, true) => &theme.hovered_unshared_project,
-                                    (true, false) => &theme.shared_project,
-                                    (true, true) => &theme.hovered_shared_project,
-                                };
-
-                                Flex::row()
-                                    .with_child(
-                                        Label::new(
-                                            project.worktree_root_names.join(", "),
-                                            style.name.text.clone(),
-                                        )
-                                        .aligned()
-                                        .left()
-                                        .contained()
-                                        .with_style(style.name.container)
-                                        .boxed(),
-                                    )
-                                    .with_children(project.guests.iter().filter_map(
-                                        |participant| {
-                                            participant.avatar.clone().map(|avatar| {
-                                                Image::new(avatar)
-                                                    .with_style(style.guest_avatar)
-                                                    .aligned()
-                                                    .left()
-                                                    .contained()
-                                                    .with_margin_right(style.guest_avatar_spacing)
-                                                    .boxed()
-                                            })
-                                        },
-                                    ))
-                                    .contained()
-                                    .with_style(style.container)
-                                    .constrained()
-                                    .with_height(style.height)
-                                    .boxed()
-                            },
-                        )
-                        .with_cursor_style(if is_host || is_shared {
-                            CursorStyle::PointingHand
-                        } else {
-                            CursorStyle::Arrow
-                        })
-                        .on_click(move |_, cx| {
-                            if !is_host && !is_guest {
-                                cx.dispatch_global_action(JoinProject {
-                                    project_id,
-                                    app_state: app_state.clone(),
-                                });
-                            }
-                        })
-                        .flex(1., true)
-                        .boxed()
-                    })
-                    .constrained()
-                    .with_height(theme.unshared_project.height)
-                    .boxed()
-            }))
+            .with_children(
+                contact
+                    .non_empty_projects()
+                    .enumerate()
+                    .map(|(ix, project)| {
+                        let project_id = project.id;
+                        Flex::row()
+                            .with_child(
+                                Canvas::new(move |bounds, _, cx| {
+                                    let start_x = bounds.min_x() + (bounds.width() / 2.)
+                                        - (tree_branch_width / 2.);
+                                    let end_x = bounds.max_x();
+                                    let start_y = bounds.min_y();
+                                    let end_y =
+                                        bounds.min_y() + baseline_offset - (cap_height / 2.);
+
+                                    cx.scene.push_quad(gpui::Quad {
+                                        bounds: RectF::from_points(
+                                            vec2f(start_x, start_y),
+                                            vec2f(
+                                                start_x + tree_branch_width,
+                                                if ix + 1 == project_count {
+                                                    end_y
+                                                } else {
+                                                    bounds.max_y()
+                                                },
+                                            ),
+                                        ),
+                                        background: Some(tree_branch_color),
+                                        border: gpui::Border::default(),
+                                        corner_radius: 0.,
+                                    });
+                                    cx.scene.push_quad(gpui::Quad {
+                                        bounds: RectF::from_points(
+                                            vec2f(start_x, end_y),
+                                            vec2f(end_x, end_y + tree_branch_width),
+                                        ),
+                                        background: Some(tree_branch_color),
+                                        border: gpui::Border::default(),
+                                        corner_radius: 0.,
+                                    });
+                                })
+                                .constrained()
+                                .with_width(host_avatar_height)
+                                .boxed(),
+                            )
+                            .with_child({
+                                let is_host = Some(contact.user.id) == current_user_id;
+                                let is_guest = !is_host
+                                    && project
+                                        .guests
+                                        .iter()
+                                        .any(|guest| Some(guest.id) == current_user_id);
+                                let is_shared = project.is_shared;
+                                let app_state = app_state.clone();
+
+                                MouseEventHandler::new::<ContactsPanel, _, _>(
+                                    project_id as usize,
+                                    cx,
+                                    |mouse_state, _| {
+                                        let style = match (project.is_shared, mouse_state.hovered) {
+                                            (false, false) => &theme.unshared_project,
+                                            (false, true) => &theme.hovered_unshared_project,
+                                            (true, false) => &theme.shared_project,
+                                            (true, true) => &theme.hovered_shared_project,
+                                        };
+
+                                        Flex::row()
+                                            .with_child(
+                                                Label::new(
+                                                    project.worktree_root_names.join(", "),
+                                                    style.name.text.clone(),
+                                                )
+                                                .aligned()
+                                                .left()
+                                                .contained()
+                                                .with_style(style.name.container)
+                                                .boxed(),
+                                            )
+                                            .with_children(project.guests.iter().filter_map(
+                                                |participant| {
+                                                    participant.avatar.clone().map(|avatar| {
+                                                        Image::new(avatar)
+                                                            .with_style(style.guest_avatar)
+                                                            .aligned()
+                                                            .left()
+                                                            .contained()
+                                                            .with_margin_right(
+                                                                style.guest_avatar_spacing,
+                                                            )
+                                                            .boxed()
+                                                    })
+                                                },
+                                            ))
+                                            .contained()
+                                            .with_style(style.container)
+                                            .constrained()
+                                            .with_height(style.height)
+                                            .boxed()
+                                    },
+                                )
+                                .with_cursor_style(if is_host || is_shared {
+                                    CursorStyle::PointingHand
+                                } else {
+                                    CursorStyle::Arrow
+                                })
+                                .on_click(move |_, cx| {
+                                    if !is_host && !is_guest {
+                                        cx.dispatch_global_action(JoinProject {
+                                            project_id,
+                                            app_state: app_state.clone(),
+                                        });
+                                    }
+                                })
+                                .flex(1., true)
+                                .boxed()
+                            })
+                            .constrained()
+                            .with_height(theme.unshared_project.height)
+                            .boxed()
+                    }),
+            )
             .boxed()
     }