update
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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<SharedString>,
|
||||
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<SharedString>) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
picture: None,
|
||||
style: StyleRefinement::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The user's picture URL, if known.
|
||||
pub fn picture(mut self, picture: Option<impl Into<SharedString>>) -> 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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user