git_ui: Clean up file history view design (#43941)
Mostly cleaning up the UI code. The UI looks fairly the same just a bit more polished, with proper colors and spacing. Also added a scrollbar in there. Next step would be to make it keyboard navigable. <img width="500" height="1948" alt="Screenshot 2025-12-01 at 5 38@2x" src="https://github.com/user-attachments/assets/c266b97c-4a79-4a0e-8e78-2f1ed1ba495f" /> Release Notes: - N/A
This commit is contained in:
@@ -4,8 +4,7 @@ use git::repository::{FileHistory, FileHistoryEntry, RepoPath};
|
||||
use git::{GitHostingProviderRegistry, GitRemote, parse_git_remote_url};
|
||||
use gpui::{
|
||||
AnyElement, AnyEntity, App, Asset, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
IntoElement, ListSizingBehavior, Render, Task, UniformListScrollHandle, WeakEntity, Window,
|
||||
actions, rems, uniform_list,
|
||||
IntoElement, Render, Task, UniformListScrollHandle, WeakEntity, Window, actions, uniform_list,
|
||||
};
|
||||
use project::{
|
||||
Project, ProjectPath,
|
||||
@@ -14,11 +13,8 @@ use project::{
|
||||
use std::any::{Any, TypeId};
|
||||
|
||||
use time::OffsetDateTime;
|
||||
use ui::{
|
||||
Avatar, Button, ButtonStyle, Color, Icon, IconName, IconSize, Label, LabelCommon as _,
|
||||
LabelSize, SharedString, prelude::*,
|
||||
};
|
||||
use util::{ResultExt, truncate_and_trailoff};
|
||||
use ui::{Avatar, Chip, Divider, ListItem, WithScrollbar, prelude::*};
|
||||
use util::ResultExt;
|
||||
use workspace::{
|
||||
Item, Workspace,
|
||||
item::{ItemEvent, SaveOptions},
|
||||
@@ -195,38 +191,24 @@ impl FileHistoryView {
|
||||
task.detach();
|
||||
}
|
||||
|
||||
fn list_item_height(&self) -> Rems {
|
||||
rems(2.0)
|
||||
}
|
||||
|
||||
fn fallback_commit_avatar() -> AnyElement {
|
||||
Icon::new(IconName::Person)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::Small)
|
||||
.into_element()
|
||||
.into_any()
|
||||
}
|
||||
|
||||
fn render_commit_avatar(
|
||||
&self,
|
||||
sha: &SharedString,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyElement {
|
||||
) -> impl IntoElement {
|
||||
let remote = self.remote.as_ref().filter(|r| r.host_supports_avatars());
|
||||
let size = rems_from_px(20.);
|
||||
|
||||
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) {
|
||||
Avatar::new(url.to_string())
|
||||
.size(rems(1.25))
|
||||
.into_element()
|
||||
.into_any()
|
||||
Avatar::new(url.to_string()).size(size)
|
||||
} else {
|
||||
Self::fallback_commit_avatar()
|
||||
Avatar::new("").size(size)
|
||||
}
|
||||
} else {
|
||||
Self::fallback_commit_avatar()
|
||||
Avatar::new("").size(size)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,34 +245,52 @@ impl FileHistoryView {
|
||||
time_format::TimestampFormat::Relative,
|
||||
);
|
||||
|
||||
let selected = self.selected_entry == Some(ix);
|
||||
let sha = entry.sha.clone();
|
||||
let repo = self.repository.clone();
|
||||
let workspace = self.workspace.clone();
|
||||
let file_path = self.history.path.clone();
|
||||
|
||||
let base_bg = if selected {
|
||||
cx.theme().status().info.alpha(0.1)
|
||||
} else {
|
||||
cx.theme().colors().editor_background
|
||||
};
|
||||
|
||||
let hover_bg = if selected {
|
||||
cx.theme().status().info.alpha(0.15)
|
||||
} else {
|
||||
cx.theme().colors().element_hover
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.id(("commit", ix))
|
||||
.h(self.list_item_height())
|
||||
.w_full()
|
||||
.items_center()
|
||||
.px(rems(0.75))
|
||||
.gap_2()
|
||||
.bg(base_bg)
|
||||
.hover(|style| style.bg(hover_bg))
|
||||
.cursor_pointer()
|
||||
ListItem::new(("commit", ix))
|
||||
.child(
|
||||
h_flex()
|
||||
.h_8()
|
||||
.w_full()
|
||||
.pl_0p5()
|
||||
.pr_2p5()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.w(rems_from_px(52.))
|
||||
.flex_none()
|
||||
.child(Chip::new(pr_number)),
|
||||
)
|
||||
.child(self.render_commit_avatar(&entry.sha, window, cx))
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(entry.author_name.clone())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Default),
|
||||
)
|
||||
.child(
|
||||
Label::new(&entry.subject)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.truncate(),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Label::new(relative_timestamp)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
),
|
||||
)
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.selected_entry = Some(ix);
|
||||
cx.notify();
|
||||
@@ -308,56 +308,6 @@ impl FileHistoryView {
|
||||
);
|
||||
}
|
||||
}))
|
||||
.child(
|
||||
div().flex_none().min_w(rems(4.0)).child(
|
||||
div()
|
||||
.px(rems(0.5))
|
||||
.py(rems(0.25))
|
||||
.rounded_md()
|
||||
.bg(cx.theme().colors().element_background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
Label::new(pr_number)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.single_line(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex_none()
|
||||
.w(rems(1.75))
|
||||
.child(self.render_commit_avatar(&entry.sha, window, cx)),
|
||||
)
|
||||
.child(
|
||||
div().flex_1().overflow_hidden().child(
|
||||
h_flex()
|
||||
.gap_3()
|
||||
.items_center()
|
||||
.child(
|
||||
Label::new(entry.author_name.clone())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Default)
|
||||
.single_line(),
|
||||
)
|
||||
.child(
|
||||
Label::new(truncate_and_trailoff(&entry.subject, 100))
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.single_line(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div().flex_none().child(
|
||||
Label::new(relative_timestamp)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.single_line(),
|
||||
),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
@@ -419,31 +369,49 @@ impl Focusable for FileHistoryView {
|
||||
}
|
||||
|
||||
impl Render for FileHistoryView {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let _file_name = self.history.path.file_name().unwrap_or("File");
|
||||
let entry_count = self.history.entries.len();
|
||||
|
||||
v_flex()
|
||||
.size_full()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(
|
||||
h_flex()
|
||||
.px(rems(0.75))
|
||||
.py(rems(0.5))
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.bg(cx.theme().colors().title_bar_background)
|
||||
.items_center()
|
||||
.h(rems_from_px(41.))
|
||||
.pl_3()
|
||||
.pr_2()
|
||||
.justify_between()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(
|
||||
h_flex().gap_2().items_center().child(
|
||||
Label::new(format!("History: {}", self.history.path.as_unix_str()))
|
||||
.size(LabelSize::Default),
|
||||
),
|
||||
Label::new(self.history.path.as_unix_str().to_string())
|
||||
.color(Color::Muted)
|
||||
.buffer_font(cx),
|
||||
)
|
||||
.child(
|
||||
Label::new(format!("{} commits", entry_count))
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Label::new(format!("{} commits", entry_count))
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.when(self.has_more, |this| this.mr_1()),
|
||||
)
|
||||
.when(self.has_more, |this| {
|
||||
this.child(Divider::vertical()).child(
|
||||
Button::new("load-more", "Load More")
|
||||
.disabled(self.loading_more)
|
||||
.label_size(LabelSize::Small)
|
||||
.icon(IconName::ArrowCircle)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_position(IconPosition::Start)
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.load_more(window, cx);
|
||||
})),
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
@@ -474,24 +442,9 @@ impl Render for FileHistoryView {
|
||||
)
|
||||
.flex_1()
|
||||
.size_full()
|
||||
.with_sizing_behavior(ListSizingBehavior::Auto)
|
||||
.track_scroll(&self.scroll_handle)
|
||||
})
|
||||
.when(self.has_more, |this| {
|
||||
this.child(
|
||||
div().p(rems(0.75)).flex().justify_start().child(
|
||||
Button::new("load-more", "Load more")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.disabled(self.loading_more)
|
||||
.label_size(LabelSize::Small)
|
||||
.icon(IconName::ArrowCircle)
|
||||
.icon_position(IconPosition::Start)
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.load_more(window, cx);
|
||||
})),
|
||||
),
|
||||
)
|
||||
}),
|
||||
.vertical_scrollbar_for(&self.scroll_handle, window, cx),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -518,7 +471,7 @@ impl Item for FileHistoryView {
|
||||
}
|
||||
|
||||
fn tab_icon(&self, _window: &Window, _cx: &App) -> Option<Icon> {
|
||||
Some(Icon::new(IconName::FileGit))
|
||||
Some(Icon::new(IconName::GitBranch))
|
||||
}
|
||||
|
||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||
|
||||
@@ -91,10 +91,16 @@ impl RenderOnce for Avatar {
|
||||
self.image
|
||||
.size(image_size)
|
||||
.rounded_full()
|
||||
.bg(cx.theme().colors().ghost_element_background)
|
||||
.bg(cx.theme().colors().element_disabled)
|
||||
.with_fallback(|| {
|
||||
Icon::new(IconName::Person)
|
||||
.color(Color::Muted)
|
||||
h_flex()
|
||||
.size_full()
|
||||
.justify_center()
|
||||
.child(
|
||||
Icon::new(IconName::Person)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::Small),
|
||||
)
|
||||
.into_any_element()
|
||||
}),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user