commit view: Reuse avatar asset (#44554)

This commit is contained in:
Agus Zubiaga
2025-12-12 07:42:05 -03:00
committed by GitHub
parent 12073e10f8
commit a66854e435
3 changed files with 51 additions and 87 deletions
+7 -5
View File
@@ -47,11 +47,13 @@ impl BlameRenderer for GitBlameRenderer {
let name = util::truncate_and_trailoff(author_name, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED);
let avatar = if ProjectSettings::get_global(cx).git.blame.show_avatar {
CommitAvatar::new(
&blame_entry.sha.to_string().into(),
details.as_ref().and_then(|it| it.remote.as_ref()),
Some(
CommitAvatar::new(
&blame_entry.sha.to_string().into(),
details.as_ref().and_then(|it| it.remote.as_ref()),
)
.render(window, cx),
)
.render(window, cx)
} else {
None
};
@@ -264,7 +266,7 @@ impl BlameRenderer for GitBlameRenderer {
.flex_wrap()
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.children(avatar)
.child(avatar)
.child(author)
.when(!author_email.is_empty(), |this| {
this.child(
+30 -16
View File
@@ -29,11 +29,16 @@ pub struct CommitDetails {
pub struct CommitAvatar<'a> {
sha: &'a SharedString,
remote: Option<&'a GitRemote>,
size: Option<IconSize>,
}
impl<'a> CommitAvatar<'a> {
pub fn new(sha: &'a SharedString, remote: Option<&'a GitRemote>) -> Self {
Self { sha, remote }
Self {
sha,
remote,
size: None,
}
}
pub fn from_commit_details(details: &'a CommitDetails) -> Self {
@@ -43,28 +48,37 @@ impl<'a> CommitAvatar<'a> {
.message
.as_ref()
.and_then(|details| details.remote.as_ref()),
size: None,
}
}
}
impl<'a> CommitAvatar<'a> {
pub fn render(&'a self, window: &mut Window, cx: &mut App) -> Option<impl IntoElement + use<>> {
pub fn size(mut self, size: IconSize) -> Self {
self.size = Some(size);
self
}
pub fn render(&'a self, window: &mut Window, cx: &mut App) -> AnyElement {
match self.avatar(window, cx) {
// Loading or no avatar found
None => Icon::new(IconName::Person)
.color(Color::Muted)
.when_some(self.size, |this, size| this.size(size))
.into_any_element(),
// Found
Some(avatar) => avatar
.when_some(self.size, |this, size| this.size(size.rems()))
.into_any_element(),
}
}
pub fn avatar(&'a self, window: &mut Window, cx: &mut App) -> Option<Avatar> {
let remote = self
.remote
.filter(|remote| remote.host_supports_avatars())?;
let avatar_url = CommitAvatarAsset::new(remote.clone(), self.sha.clone());
let element = match window.use_asset::<CommitAvatarAsset>(&avatar_url, cx) {
// Loading or no avatar found
None | Some(None) => Icon::new(IconName::Person)
.color(Color::Muted)
.into_element()
.into_any(),
// Found
Some(Some(url)) => Avatar::new(url.to_string()).into_element().into_any(),
};
Some(element)
let url = window.use_asset::<CommitAvatarAsset>(&avatar_url, cx)??;
Some(Avatar::new(url.to_string()))
}
}
@@ -253,7 +267,7 @@ impl Render for CommitTooltip {
.gap_x_2()
.overflow_x_hidden()
.flex_wrap()
.children(avatar)
.child(avatar)
.child(author)
.when(!author_email.is_empty(), |this| {
this.child(
+14 -66
View File
@@ -5,8 +5,8 @@ use editor::{Editor, EditorEvent, ExcerptRange, MultiBuffer, multibuffer_context
use git::repository::{CommitDetails, CommitDiff, RepoPath};
use git::{GitHostingProviderRegistry, GitRemote, parse_git_remote_url};
use gpui::{
AnyElement, App, AppContext as _, Asset, AsyncApp, AsyncWindowContext, Context, Element,
Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement,
AnyElement, App, AppContext as _, AsyncApp, AsyncWindowContext, Context, Element, Entity,
EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement,
PromptLevel, Render, Styled, Task, WeakEntity, Window, actions,
};
use language::{
@@ -21,7 +21,7 @@ use std::{
sync::Arc,
};
use theme::ActiveTheme;
use ui::{Avatar, DiffStat, Tooltip, prelude::*};
use ui::{DiffStat, Tooltip, prelude::*};
use util::{ResultExt, paths::PathStyle, rel_path::RelPath, truncate_and_trailoff};
use workspace::item::TabTooltipContent;
use workspace::{
@@ -33,6 +33,7 @@ use workspace::{
searchable::SearchableItemHandle,
};
use crate::commit_tooltip::CommitAvatar;
use crate::git_panel::GitPanel;
actions!(git, [ApplyCurrentStash, PopCurrentStash, DropCurrentStash,]);
@@ -318,17 +319,7 @@ impl CommitView {
cx: &mut App,
) -> AnyElement {
let size = size.into();
let remote = self.remote.as_ref().filter(|r| r.host_supports_avatars());
if let Some(remote) = remote {
let avatar_asset = CommitAvatarAsset::new(remote.clone(), sha.clone());
if let Some(Some(url)) = window.use_asset::<CommitAvatarAsset>(&avatar_asset, cx) {
return Avatar::new(url.to_string())
.size(size)
.into_element()
.into_any();
}
}
let avatar = CommitAvatar::new(sha, self.remote.as_ref());
v_flex()
.w(size)
@@ -339,10 +330,15 @@ impl CommitView {
.justify_center()
.items_center()
.child(
Icon::new(IconName::Person)
.color(Color::Muted)
.size(IconSize::Medium)
.into_element(),
avatar
.avatar(window, cx)
.map(|a| a.size(size).into_any_element())
.unwrap_or_else(|| {
Icon::new(IconName::Person)
.color(Color::Muted)
.size(IconSize::Medium)
.into_any_element()
}),
)
.into_any()
}
@@ -647,54 +643,6 @@ impl CommitView {
}
}
#[derive(Clone, Debug)]
struct CommitAvatarAsset {
sha: SharedString,
remote: GitRemote,
}
impl std::hash::Hash for CommitAvatarAsset {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.sha.hash(state);
self.remote.host.name().hash(state);
}
}
impl CommitAvatarAsset {
fn new(remote: GitRemote, sha: SharedString) -> Self {
Self { remote, sha }
}
}
impl Asset for CommitAvatarAsset {
type Source = Self;
type Output = Option<SharedString>;
fn load(
source: Self::Source,
cx: &mut App,
) -> impl Future<Output = Self::Output> + Send + 'static {
let client = cx.http_client();
async move {
match source
.remote
.host
.commit_author_avatar_url(
&source.remote.owner,
&source.remote.repo,
source.sha.clone(),
client,
)
.await
{
Ok(Some(url)) => Some(SharedString::from(url.to_string())),
Ok(None) => None,
Err(_) => None,
}
}
}
}
impl language::File for GitBlob {
fn as_local(&self) -> Option<&dyn language::LocalFile> {
None