add avatar to tab

This commit is contained in:
2026-09-25 11:06:37 +07:00
parent 54b2102e05
commit 192a0585b6
13 changed files with 109 additions and 31 deletions
+3 -1
View File
@@ -6,10 +6,12 @@
### Added
- Show an avatar in each panel's tab, using the repository owner's profile picture when set and a pixel avatar otherwise
### Changed
- Migrate the GPUI foundation to the published `gpui-pre` crates and GPUI Kit 0.6, off the zed and gpui-component git pins
- Redesign the dock tab bar: the active tab is a muted grey, each tab holds a close button that shows on hover or when active, double-clicking a tab zooms its panel, and the trailing panel toolbar is gone
- Redesign the dock tab bar, using muted grey active tab, added close buttons, double-click to zoom, and removed panel toolbar
### Fixed
+10 -5
View File
@@ -18,10 +18,12 @@ use gpui_component::{
ActiveTheme, Sizable, StyledExt, VirtualListScrollHandle, h_flex, v_flex, v_virtual_list,
};
use signed_git::{CommitDiff, DiffHunk, DiffLine, DiffLineKind, DiffStatus, FileCommit, FileDiff};
use signed_state::RepoStore;
use signed_ui::{placeholder, tree_row};
use utils::relative_time_secs;
use crate::views::tree::{build_tree_items, tree_items};
use crate::views::{repo_tab_avatar, tab_title};
const TREE_WIDTH: f32 = 260.;
@@ -293,6 +295,7 @@ impl Render for DiffPane {
pub struct CommitDiffView {
focus_handle: FocusHandle,
store: Entity<RepoStore>,
worktree: PathBuf,
repo_name: SharedString,
commit: FileCommit,
@@ -304,6 +307,7 @@ pub struct CommitDiffView {
impl CommitDiffView {
pub fn new(
store: Entity<RepoStore>,
worktree: PathBuf,
repo_name: SharedString,
commit_id: String,
@@ -319,6 +323,7 @@ impl CommitDiffView {
Self {
focus_handle: cx.focus_handle(),
store,
worktree,
repo_name,
commit: FileCommit {
@@ -465,11 +470,11 @@ impl BasePanel for CommitDiffView {
}
impl Panel for CommitDiffView {
fn title(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div().text_sm().child(SharedString::from(format!(
"{}/{}",
self.repo_name, self.commit.id
)))
fn title(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let avatar = repo_tab_avatar(self.store.read(cx), cx);
let label = SharedString::from(format!("{}/{}", self.repo_name, self.commit.id));
tab_title(avatar, label)
}
}
+2 -2
View File
@@ -18,7 +18,7 @@ use signed_state::{
use signed_ui::{CountBadge, UserAvatar};
use utils::relative_time;
use super::{RepoItem, open_repo_item};
use super::{RepoItem, open_repo_item, panel_avatar, tab_title};
const LIST_OVERDRAW: Pixels = px(400.);
const MAX_SUB_ACTIVITIES: usize = 5;
@@ -619,7 +619,7 @@ impl BasePanel for InboxView {
impl Panel for InboxView {
fn title(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div().text_sm().child(SharedString::from("Inbox"))
tab_title(panel_avatar("inbox"), SharedString::from("Inbox"))
}
}
+4 -1
View File
@@ -14,6 +14,7 @@ use signed_ui::{UserAvatar, placeholder, status_badge};
use utils::relative_time;
use crate::views::discussion::{comment_form, comments_section, issue_roots, sidebar_section};
use crate::views::{repo_tab_avatar, tab_title};
pub struct IssueDetailView {
focus_handle: FocusHandle,
@@ -65,7 +66,9 @@ impl Panel for IssueDetailView {
})
.unwrap_or_else(|| SharedString::from("Issue"));
div().text_sm().child(short_id)
let avatar = repo_tab_avatar(self.store.read(cx), cx);
tab_title(avatar, short_id)
}
}
+6 -2
View File
@@ -25,6 +25,7 @@ pub(super) mod detail;
use self::detail::IssueDetailView;
use super::status_list::{StatusCounts, filter_by_status};
use crate::views::{repo_tab_avatar, tab_title};
const ISSUE_ROW_HEIGHT: f32 = 73.;
@@ -333,8 +334,11 @@ impl BasePanel for IssuesView {
}
impl Panel for IssuesView {
fn title(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div().child(SharedString::from(format!("{}/issues", self.repo_name)))
fn title(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let avatar = repo_tab_avatar(self.store.read(cx), cx);
let label = SharedString::from(format!("{}/issues", self.repo_name));
tab_title(avatar, label)
}
}
+45
View File
@@ -11,8 +11,53 @@ pub(crate) mod sidebar;
mod status_list;
pub(crate) mod tree;
use gpui::prelude::*;
use gpui::{AnyElement, App, div};
use gpui_component::{Sizable as _, h_flex};
pub use inbox::InboxView;
pub use repo::RepoDetailView;
pub(crate) use repo::{RepoItem, open_repo_item, open_repo_panel};
pub use repo_list::RepoListView;
pub use sidebar::SidebarPanel;
use signed_state::{ProfileStore, RepoStore};
use signed_ui::{PixelAvatar, UserAvatar};
pub(crate) fn tab_title(avatar: AnyElement, label: impl IntoElement) -> impl IntoElement {
h_flex()
.gap_1()
.items_center()
.child(div().flex_shrink_0().child(avatar))
.child(label)
}
pub(crate) fn panel_avatar(seed: impl AsRef<str>) -> AnyElement {
PixelAvatar::new(seed).xsmall().into_any_element()
}
pub(crate) fn repo_tab_avatar(store: &RepoStore, cx: &App) -> AnyElement {
let seed = store
.announcement
.as_ref()
.map(|announcement| format!("{}:{}", announcement.owner, announcement.id))
.or_else(|| store.addr().map(|addr| addr.to_string()))
.or_else(|| {
store
.path
.as_ref()
.map(|path| path.to_string_lossy().into_owned())
})
.unwrap_or_default();
let owner = store
.announcement
.as_ref()
.map(|announcement| ProfileStore::global(cx).read(cx).get(&announcement.owner));
match owner.and_then(|profile| profile.picture().map(|picture| (profile.name(), picture))) {
Some((name, picture)) => UserAvatar::new(name)
.picture(Some(picture))
.xsmall()
.into_any_element(),
None => PixelAvatar::new(seed).xsmall().into_any_element(),
}
}
@@ -32,6 +32,7 @@ use utils::{relative_time, relative_time_secs};
use crate::views::commit_diff::{CommitDiffView, DiffPane};
use crate::views::discussion::{comment_form, comments_section, pr_roots, sidebar_section};
use crate::views::{repo_tab_avatar, tab_title};
const ROW_HEIGHT: f32 = 37.;
@@ -343,6 +344,7 @@ impl PullRequestDetailView {
let panel = cx.new(|cx| {
CommitDiffView::new(
self.store.clone(),
worktree,
self.repo_name.clone(),
commit_id.into(),
@@ -788,15 +790,17 @@ impl BasePanel for PullRequestDetailView {
}
impl Panel for PullRequestDetailView {
fn title(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
fn title(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let avatar = repo_tab_avatar(self.store.read(cx), cx);
let hex = self.pr_id.to_hex();
let id = SharedString::from(&hex[..8]);
let title = if self.repo_name.is_empty() {
let label = if self.repo_name.is_empty() {
id
} else {
SharedString::from(format!("{}/{}", self.repo_name, id))
};
div().text_sm().child(title)
tab_title(avatar, label)
}
}
@@ -27,6 +27,7 @@ use self::new::open_new_pull_panel;
use super::send_patch::open_send_patch_panel;
use super::status_list::{StatusCounts, filter_by_status};
use crate::views::repo::RepoAction;
use crate::views::{repo_tab_avatar, tab_title};
const ROW_HEIGHT: f32 = 73.;
@@ -333,11 +334,11 @@ impl BasePanel for PullRequestsView {
}
impl Panel for PullRequestsView {
fn title(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div().child(SharedString::from(format!(
"{}/pull-requests",
self.repo_name
)))
fn title(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let avatar = repo_tab_avatar(self.store.read(cx), cx);
let label = SharedString::from(format!("{}/pulls", self.repo_name));
tab_title(avatar, label)
}
}
@@ -32,6 +32,7 @@ use signed_state::{
use signed_ui::{CountBadge, placeholder, ref_selector_trigger};
use crate::views::commit_diff::{COMMIT_ROW_HEIGHT, CommitDiffView, DiffPane, commit_row};
use crate::views::{repo_tab_avatar, tab_title};
pub struct NewPullRequestView {
focus_handle: FocusHandle,
@@ -924,6 +925,7 @@ impl NewPullRequestView {
let panel = cx.new(|cx| {
CommitDiffView::new(
self.store.clone(),
repo_path,
self.repo_name.clone(),
commit_id.into(),
@@ -1323,11 +1325,11 @@ impl BasePanel for NewPullRequestView {
}
impl Panel for NewPullRequestView {
fn title(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div().child(SharedString::from(format!(
"{}/new-pull-request",
self.repo_name
)))
fn title(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let avatar = repo_tab_avatar(self.store.read(cx), cx);
let label = SharedString::from(format!("{}/new-pull-request", self.repo_name));
tab_title(avatar, label)
}
}
+4 -2
View File
@@ -106,8 +106,10 @@ impl RepoHistoryView {
// Same display name as the repo detail panel's title.
let repo_name = repo_display_name(self.store.read(cx));
let panel =
cx.new(|cx| CommitDiffView::new(worktree, repo_name, commit_id.into(), window, cx));
let store = self.store.clone();
let panel = cx.new(|cx| {
CommitDiffView::new(store, worktree, repo_name, commit_id.into(), window, cx)
});
dock_area.update(cx, |dock_area, cx| {
add_center_panel(dock_area, panel_handle(panel), window, cx);
+6 -1
View File
@@ -55,6 +55,7 @@ use crate::views::pull_requests::new::open_new_pull_panel;
use crate::views::repo::about::open_about_dialog;
use crate::views::send_patch::open_send_patch_panel;
use crate::views::tree::{TreeItemSeed, build_tree_items, sorted_worktree_paths};
use crate::views::{repo_tab_avatar, tab_title};
#[derive(Clone, Copy, PartialEq, Eq)]
enum RefKind {
@@ -2054,7 +2055,11 @@ impl BasePanel for RepoDetailView {
impl Panel for RepoDetailView {
fn title(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
repo_display_name(self.store.read(cx))
let store = self.store.read(cx);
let name = repo_display_name(store);
let avatar = repo_tab_avatar(store, cx);
tab_title(avatar, name)
}
}
+2 -2
View File
@@ -19,7 +19,7 @@ use signed_state::{ProfileStore, RepoListStore, Timestamp};
use signed_ui::{SegmentButton, UserAvatar};
use utils::relative_time;
use super::open_repo_panel;
use super::{open_repo_panel, panel_avatar, tab_title};
const COLUMNS: usize = 2;
const CARD_HEIGHT: f32 = 40. + 64. + 48. + 2. + 6.;
@@ -389,7 +389,7 @@ impl BasePanel for RepoListView {
impl Panel for RepoListView {
fn title(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div().text_sm().child(SharedString::from("Explore"))
tab_title(panel_avatar("explore"), SharedString::from("Explore"))
}
}
+7 -2
View File
@@ -11,6 +11,8 @@ use gpui_component::spinner::Spinner;
use gpui_component::{ActiveTheme, Icon, IconName, Sizable, h_flex, v_flex};
use signed_state::RepoStore;
use crate::views::{repo_tab_avatar, tab_title};
pub struct SendPatchView {
focus_handle: FocusHandle,
dock_area: WeakEntity<DockArea>,
@@ -205,8 +207,11 @@ impl BasePanel for SendPatchView {
}
impl Panel for SendPatchView {
fn title(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div().child(SharedString::from(format!("{}/send-patch", self.repo_name)))
fn title(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let avatar = repo_tab_avatar(self.store.read(cx), cx);
let label = SharedString::from(format!("{}/send-patch", self.repo_name));
tab_title(avatar, label)
}
}