From a4c12723de09a250d665e9e4c988f41193ffb39b Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Tue, 1 Sep 2026 11:04:16 +0700 Subject: [PATCH] update --- crates/signed_ui/src/lib.rs | 3 ++ crates/signed_ui/src/user_avatar.rs | 48 +++++++++++++++++++ .../workspace/src/views/repo_detail/about.rs | 13 ++--- .../src/views/repo_detail/issue_detail.rs | 28 ++--------- .../workspace/src/views/repo_detail/issues.rs | 13 ++--- crates/workspace/src/views/repo_detail/mod.rs | 11 +---- .../views/repo_detail/pull_request_detail.rs | 28 ++--------- .../src/views/repo_detail/pull_requests.rs | 13 ++--- crates/workspace/src/views/repo_list.rs | 11 +---- crates/workspace/src/views/sidebar/mod.rs | 11 +---- 10 files changed, 76 insertions(+), 103 deletions(-) create mode 100644 crates/signed_ui/src/user_avatar.rs diff --git a/crates/signed_ui/src/lib.rs b/crates/signed_ui/src/lib.rs index 05d3f40..d574ad8 100644 --- a/crates/signed_ui/src/lib.rs +++ b/crates/signed_ui/src/lib.rs @@ -14,6 +14,7 @@ //! [`PopupMenu`], wired through `gpui_base::Popover` //! - [`SegmentButton`] / [`CountBadge`] — segmented filter/tab button with an //! optional count badge +//! - [`UserAvatar`] — user picture avatar with a name-initials fallback //! - [`status_badge`] — NIP-34 issue/PR status badge //! - [`placeholder`] — centered muted placeholder message //! - [`copy_row`] / [`menu_copy_row`] — rows with a copy-to-clipboard button @@ -31,6 +32,7 @@ mod segment_button; mod status_badge; mod title_bar; mod tree_row; +mod user_avatar; pub mod copy_row; pub mod image_cache; @@ -46,4 +48,5 @@ pub use segment_button::{CountBadge, SegmentButton}; pub use status_badge::status_badge; pub use title_bar::title_bar_drag_handlers; pub use tree_row::tree_row; +pub use user_avatar::UserAvatar; pub use util::middle_truncate; diff --git a/crates/signed_ui/src/user_avatar.rs b/crates/signed_ui/src/user_avatar.rs new file mode 100644 index 0000000..ec51c24 --- /dev/null +++ b/crates/signed_ui/src/user_avatar.rs @@ -0,0 +1,48 @@ +use gpui::prelude::*; +use gpui::{App, SharedString, StyleRefinement, Window}; +use gpui_component::avatar::Avatar; +use gpui_component::{ActiveTheme, Sizable, StyledExt}; + +/// A user avatar: the gpui-component [`Avatar`] sized small and rounded with +/// the theme radius, showing the user's picture or a name-initials fallback. +#[derive(IntoElement)] +pub struct UserAvatar { + name: SharedString, + picture: Option, + style: StyleRefinement, +} + +impl UserAvatar { + /// Create an avatar for `name`; the name seeds the initials fallback + /// shown when no picture is set. + pub fn new(name: impl Into) -> Self { + Self { + name: name.into(), + picture: None, + style: StyleRefinement::default(), + } + } + + /// The user's picture URL, if known. + pub fn picture(mut self, picture: Option>) -> Self { + self.picture = picture.map(Into::into); + self + } +} + +impl Styled for UserAvatar { + fn style(&mut self) -> &mut StyleRefinement { + &mut self.style + } +} + +impl RenderOnce for UserAvatar { + fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { + Avatar::new() + .name(self.name) + .when_some(self.picture, |this, url| this.src(url)) + .rounded(cx.theme().radius) + .refine_style(&self.style) + .small() + } +} diff --git a/crates/workspace/src/views/repo_detail/about.rs b/crates/workspace/src/views/repo_detail/about.rs index b897781..290954e 100644 --- a/crates/workspace/src/views/repo_detail/about.rs +++ b/crates/workspace/src/views/repo_detail/about.rs @@ -1,12 +1,11 @@ use gpui::prelude::*; use gpui::{AnyElement, App, SharedString, Window, div, px}; -use gpui_component::avatar::Avatar; use gpui_component::clipboard::Clipboard; -use gpui_component::{ActiveTheme, Sizable, StyledExt, WindowExt, h_flex, v_flex}; +use gpui_component::{ActiveTheme, StyledExt, WindowExt, h_flex, v_flex}; use nostr::prelude::PublicKey; use signed_core::Announcement; use signed_state::ProfileStore; -use signed_ui::middle_truncate; +use signed_ui::{UserAvatar, middle_truncate}; /// Open the "About" dialog: every field of the repository's announcement /// event (NIP-34, kind 30617), as parsed into [`Announcement`]. @@ -174,13 +173,7 @@ fn maintainers(maintainers: &[PublicKey], cx: &App) -> AnyElement { .gap_2() .items_center() .min_w_0() - .child( - Avatar::new() - .name(name.clone()) - .when_some(picture, |this, url| this.src(url)) - .rounded(cx.theme().radius) - .small(), - ) + .child(UserAvatar::new(name.clone()).picture(picture)) .child( div() .flex_1() diff --git a/crates/workspace/src/views/repo_detail/issue_detail.rs b/crates/workspace/src/views/repo_detail/issue_detail.rs index a4b80b2..43ba3fb 100644 --- a/crates/workspace/src/views/repo_detail/issue_detail.rs +++ b/crates/workspace/src/views/repo_detail/issue_detail.rs @@ -7,7 +7,6 @@ use gpui::{ AnyElement, App, Context, Entity, EventEmitter, FocusHandle, Focusable, Render, SharedString, Window, div, px, relative, }; -use gpui_component::avatar::Avatar; use gpui_component::button::{Button, ButtonVariants}; use gpui_component::input::{Textarea, TextareaState}; use gpui_component::scroll::ScrollableElement; @@ -17,7 +16,7 @@ use nostr::prelude::{Event, EventId, PublicKey}; use signed_core::activity_subject; use signed_state::{ProfileStore, RepoStore}; use signed_ui::image_cache::{MAX_IMAGES, image_cache}; -use signed_ui::{placeholder, status_badge}; +use signed_ui::{UserAvatar, placeholder, status_badge}; use utils::relative_time; /// Detail panel of a single issue. @@ -91,13 +90,7 @@ impl IssueDetailView { h_flex() .gap_1() .items_center() - .child( - Avatar::new() - .name(name.clone()) - .when_some(picture, |this, url| this.src(url)) - .rounded(cx.theme().radius) - .small(), - ) + .child(UserAvatar::new(name.clone()).picture(picture)) .child(div().text_sm().truncate().text_ellipsis().child(name)) .into_any_element() })), @@ -169,13 +162,7 @@ impl IssueDetailView { .child( h_flex() .gap_1() - .child( - Avatar::new() - .name(author.clone()) - .when_some(picture, |this, url| this.src(url)) - .rounded(cx.theme().radius) - .small(), - ) + .child(UserAvatar::new(author.clone()).picture(picture)) .child(author), ) .child( @@ -351,13 +338,8 @@ impl Render for IssueDetailView { h_flex() .gap_1() .child( - Avatar::new() - .when_some(picture, |this, url| { - this.src(url) - }) - .name(author.clone()) - .rounded(cx.theme().radius) - .small(), + UserAvatar::new(author.clone()) + .picture(picture), ) .child(author), ) diff --git a/crates/workspace/src/views/repo_detail/issues.rs b/crates/workspace/src/views/repo_detail/issues.rs index 425a55f..403f402 100644 --- a/crates/workspace/src/views/repo_detail/issues.rs +++ b/crates/workspace/src/views/repo_detail/issues.rs @@ -7,20 +7,19 @@ use gpui::{ AnyElement, App, Context, Entity, EventEmitter, FocusHandle, Focusable, Pixels, Render, SharedString, Size, WeakEntity, Window, div, px, size, }; -use gpui_component::avatar::Avatar; use gpui_component::button::{Button, ButtonVariants}; use gpui_component::dialog::{DialogDescription, DialogFooter, DialogHeader, DialogTitle}; use gpui_component::form::{field, v_form}; use gpui_component::input::{Input, InputState, Textarea, TextareaState}; use gpui_component::scroll::Scrollbar; use gpui_component::{ - ActiveTheme, Icon, Sizable, VirtualListScrollHandle, WindowExt, h_flex, v_flex, v_virtual_list, + ActiveTheme, Icon, VirtualListScrollHandle, WindowExt, h_flex, v_flex, v_virtual_list, }; use nostr::prelude::EventId; use signed_core::{RepoStatus, activity_subject}; use signed_state::{ProfileStore, RepoStore}; use signed_ui::image_cache::{MAX_IMAGES, image_cache}; -use signed_ui::{SegmentButton, placeholder, status_badge}; +use signed_ui::{SegmentButton, UserAvatar, placeholder, status_badge}; use utils::relative_time; use super::issue_detail::IssueDetailView; @@ -167,13 +166,7 @@ impl IssuesView { .child( h_flex() .gap_1() - .child( - Avatar::new() - .name(author.clone()) - .when_some(picture, |this, url| this.src(url)) - .rounded(cx.theme().radius) - .small(), - ) + .child(UserAvatar::new(author.clone()).picture(picture)) .child(div().child(author)), ) .child(SharedString::from("opened")) diff --git a/crates/workspace/src/views/repo_detail/mod.rs b/crates/workspace/src/views/repo_detail/mod.rs index c09d0cd..16038c2 100644 --- a/crates/workspace/src/views/repo_detail/mod.rs +++ b/crates/workspace/src/views/repo_detail/mod.rs @@ -14,7 +14,6 @@ use gpui::{ }; use gpui_base::{Button as BaseButton, Disableable, Popover}; use gpui_component::alert::Alert; -use gpui_component::avatar::Avatar; use gpui_component::button::{Button, ButtonVariants}; use gpui_component::combobox::{ Caret, Combobox, ComboboxEvent, ComboboxState, ComboboxTriggerContext, @@ -31,7 +30,7 @@ use signed_core::Announcement; use signed_git::{CommitList, FileCommit}; use signed_state::{Backend, GitStore, LocalReposStore, ProfileStore, RepoStore}; use signed_ui::image_cache::{MAX_IMAGES, image_cache}; -use signed_ui::{DropdownButton, PixelAvatar, copy_row}; +use signed_ui::{DropdownButton, PixelAvatar, UserAvatar, copy_row}; mod about; mod browser; @@ -1883,13 +1882,7 @@ impl RepoDetailView { .child( h_flex() .gap_1() - .child( - Avatar::new() - .name(owner_name.clone()) - .when_some(owner_picture, |this, url| this.src(url)) - .rounded(cx.theme().radius) - .small(), - ) + .child(UserAvatar::new(owner_name.clone()).picture(owner_picture)) .child(div().text_xs().whitespace_nowrap().child(owner_name)), ) .when(!rest.is_empty(), |this| { diff --git a/crates/workspace/src/views/repo_detail/pull_request_detail.rs b/crates/workspace/src/views/repo_detail/pull_request_detail.rs index a2404fe..ef52da5 100644 --- a/crates/workspace/src/views/repo_detail/pull_request_detail.rs +++ b/crates/workspace/src/views/repo_detail/pull_request_detail.rs @@ -10,7 +10,6 @@ use gpui::{ ScrollStrategy, SharedString, Size, Subscription, Task, WeakEntity, Window, div, px, relative, size, }; -use gpui_component::avatar::Avatar; use gpui_component::button::{Button, ButtonVariants}; use gpui_component::clipboard::Clipboard; use gpui_component::input::{Textarea, TextareaState}; @@ -28,7 +27,7 @@ use signed_core::{activity_subject, pull_request_patch}; use signed_git::{CommitDiff, FileCommit, FileDiff, patch_commits, patch_diffs}; use signed_state::{GitStore, ProfileStore, RepoStore}; use signed_ui::image_cache::{MAX_IMAGES, image_cache}; -use signed_ui::{placeholder, status_badge, tree_row}; +use signed_ui::{UserAvatar, placeholder, status_badge, tree_row}; use utils::{relative_time, relative_time_secs}; use super::diff::CommitDiffView; @@ -668,13 +667,8 @@ impl PullRequestDetailView { h_flex() .gap_1() .child( - Avatar::new() - .name(author.clone()) - .when_some(picture, |this, url| { - this.src(url) - }) - .rounded(cx.theme().radius) - .small(), + UserAvatar::new(author.clone()) + .picture(picture), ) .child(author), ) @@ -741,13 +735,7 @@ impl PullRequestDetailView { h_flex() .gap_1() .items_center() - .child( - Avatar::new() - .name(name.clone()) - .when_some(picture, |this, url| this.src(url)) - .rounded(cx.theme().radius) - .small(), - ) + .child(UserAvatar::new(name.clone()).picture(picture)) .child(div().text_sm().truncate().text_ellipsis().child(name)) .into_any_element() })), @@ -937,13 +925,7 @@ impl PullRequestDetailView { .child( h_flex() .gap_1() - .child( - Avatar::new() - .name(author.clone()) - .when_some(picture, |this, url| this.src(url)) - .rounded(cx.theme().radius) - .small(), - ) + .child(UserAvatar::new(author.clone()).picture(picture)) .child(author), ) .child( diff --git a/crates/workspace/src/views/repo_detail/pull_requests.rs b/crates/workspace/src/views/repo_detail/pull_requests.rs index 1da82a6..7084d11 100644 --- a/crates/workspace/src/views/repo_detail/pull_requests.rs +++ b/crates/workspace/src/views/repo_detail/pull_requests.rs @@ -7,20 +7,19 @@ use gpui::{ AnyElement, App, Context, Entity, EventEmitter, FocusHandle, Focusable, Pixels, Render, SharedString, Size, WeakEntity, Window, div, px, size, }; -use gpui_component::avatar::Avatar; use gpui_component::button::{Button, ButtonVariants}; use gpui_component::dialog::{DialogDescription, DialogFooter, DialogHeader, DialogTitle}; use gpui_component::form::{field, v_form}; use gpui_component::input::{Input, InputState, Textarea, TextareaState}; use gpui_component::scroll::Scrollbar; use gpui_component::{ - ActiveTheme, Icon, Sizable, VirtualListScrollHandle, WindowExt, h_flex, v_flex, v_virtual_list, + ActiveTheme, Icon, VirtualListScrollHandle, WindowExt, h_flex, v_flex, v_virtual_list, }; use nostr::prelude::{EventId, Kind}; use signed_core::{RepoStatus, activity_subject}; use signed_state::{ProfileStore, RepoStore}; use signed_ui::image_cache::{MAX_IMAGES, image_cache}; -use signed_ui::{SegmentButton, placeholder, status_badge}; +use signed_ui::{SegmentButton, UserAvatar, placeholder, status_badge}; use utils::relative_time; use super::pull_request_detail::PullRequestDetailView; @@ -187,13 +186,7 @@ impl PullRequestsView { .child( h_flex() .gap_1() - .child( - Avatar::new() - .name(author.clone()) - .when_some(picture, |this, url| this.src(url)) - .rounded(cx.theme().radius) - .small(), - ) + .child(UserAvatar::new(author.clone()).picture(picture)) .child(div().child(author)), ) .child(SharedString::from("opened")) diff --git a/crates/workspace/src/views/repo_list.rs b/crates/workspace/src/views/repo_list.rs index 20fb9fb..0582f59 100644 --- a/crates/workspace/src/views/repo_list.rs +++ b/crates/workspace/src/views/repo_list.rs @@ -7,7 +7,6 @@ use gpui::{ AnyElement, App, Context, Entity, EventEmitter, FocusHandle, Focusable, Pixels, Render, SharedString, Size, Subscription, WeakEntity, Window, div, px, size, }; -use gpui_component::avatar::Avatar; use gpui_component::input::{Input, InputEvent, InputState}; use gpui_component::scroll::Scrollbar; use gpui_component::{ @@ -16,8 +15,8 @@ use gpui_component::{ }; use signed_core::Announcement; use signed_state::{ProfileStore, RepoListStore, Timestamp}; -use signed_ui::SegmentButton; use signed_ui::image_cache::{MAX_IMAGES, image_cache}; +use signed_ui::{SegmentButton, UserAvatar}; use utils::relative_time; use super::RepoDetailView; @@ -255,13 +254,7 @@ impl RepoListView { h_flex() .gap_2() .items_center() - .child( - Avatar::new() - .name(owner.name()) - .when_some(owner.picture(), |this, url| this.src(url)) - .rounded(cx.theme().radius) - .small(), - ) + .child(UserAvatar::new(owner.name()).picture(owner.picture())) .child( div() .text_xs() diff --git a/crates/workspace/src/views/sidebar/mod.rs b/crates/workspace/src/views/sidebar/mod.rs index 63a5327..5c962c6 100644 --- a/crates/workspace/src/views/sidebar/mod.rs +++ b/crates/workspace/src/views/sidebar/mod.rs @@ -11,14 +11,13 @@ use gpui::{ SharedString, Subscription, WeakEntity, Window, div, img, px, uniform_list, }; use gpui_base::Button as BaseButton; -use gpui_component::avatar::Avatar; use gpui_component::button::{Button, ButtonVariants}; use gpui_component::input::InputState; use gpui_component::{ActiveTheme, Icon, IconName, Sizable, StyledExt, h_flex, v_flex}; use signed_core::{Announcement, identifier_from_name}; use signed_state::{Backend, BackendEvent, LocalReposStore, Profile, ProfileStore, RepoListStore}; use signed_ui::image_cache::{MAX_IMAGES, image_cache}; -use signed_ui::{NavItem, PixelAvatar, title_bar_drag_handlers}; +use signed_ui::{NavItem, PixelAvatar, UserAvatar, title_bar_drag_handlers}; use super::{RepoDetailView, RepoListView}; @@ -397,13 +396,7 @@ impl SidebarPanel { Button::new("user").text().dropdown_caret(true).child( h_flex() .gap_1() - .child( - Avatar::new() - .name(name.clone()) - .when_some(picture, |this, url| this.src(url)) - .rounded(cx.theme().radius) - .small(), - ) + .child(UserAvatar::new(name.clone()).picture(picture)) .child(div().text_xs().font_semibold().child(name)), ), ),