git_ui: Update Project Diff empty state design (#26554)
Title Release Notes: - N/A --------- Co-authored-by: Cole Miller <m@cole-miller.net>
This commit is contained in:
parent
010c5a2c4e
commit
8d259a9dbe
1 changed files with 288 additions and 3 deletions
|
@ -1,4 +1,7 @@
|
||||||
use crate::git_panel::{GitPanel, GitPanelAddon, GitStatusEntry};
|
use crate::{
|
||||||
|
git_panel::{GitPanel, GitPanelAddon, GitStatusEntry},
|
||||||
|
remote_button::{render_publish_button, render_push_button},
|
||||||
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus};
|
use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus};
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
|
@ -9,8 +12,9 @@ use editor::{
|
||||||
};
|
};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use git::{
|
use git::{
|
||||||
repository::Branch, status::FileStatus, Commit, StageAll, StageAndNext, ToggleStaged,
|
repository::{Branch, Upstream, UpstreamTracking, UpstreamTrackingStatus},
|
||||||
UnstageAll, UnstageAndNext,
|
status::FileStatus,
|
||||||
|
Commit, StageAll, StageAndNext, ToggleStaged, UnstageAll, UnstageAndNext,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, Action, AnyElement, AnyView, App, AppContext as _, AsyncWindowContext, Entity,
|
actions, Action, AnyElement, AnyView, App, AppContext as _, AsyncWindowContext, Entity,
|
||||||
|
@ -1022,6 +1026,287 @@ impl Render for ProjectDiffToolbar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(IntoElement, IntoComponent)]
|
||||||
|
#[component(scope = "Version Control")]
|
||||||
|
pub struct ProjectDiffEmptyState {
|
||||||
|
pub no_repo: bool,
|
||||||
|
pub can_push_and_pull: bool,
|
||||||
|
pub focus_handle: Option<FocusHandle>,
|
||||||
|
pub current_branch: Option<Branch>,
|
||||||
|
// has_pending_commits: bool,
|
||||||
|
// ahead_of_remote: bool,
|
||||||
|
// no_git_repository: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for ProjectDiffEmptyState {
|
||||||
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
let status_against_remote = |ahead_by: usize, behind_by: usize| -> bool {
|
||||||
|
match self.current_branch {
|
||||||
|
Some(Branch {
|
||||||
|
upstream:
|
||||||
|
Some(Upstream {
|
||||||
|
tracking:
|
||||||
|
UpstreamTracking::Tracked(UpstreamTrackingStatus {
|
||||||
|
ahead, behind, ..
|
||||||
|
}),
|
||||||
|
..
|
||||||
|
}),
|
||||||
|
..
|
||||||
|
}) if (ahead > 0) == (ahead_by > 0) && (behind > 0) == (behind_by > 0) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let change_count = |current_branch: &Branch| -> (usize, usize) {
|
||||||
|
match current_branch {
|
||||||
|
Branch {
|
||||||
|
upstream:
|
||||||
|
Some(Upstream {
|
||||||
|
tracking:
|
||||||
|
UpstreamTracking::Tracked(UpstreamTrackingStatus {
|
||||||
|
ahead, behind, ..
|
||||||
|
}),
|
||||||
|
..
|
||||||
|
}),
|
||||||
|
..
|
||||||
|
} => (*ahead as usize, *behind as usize),
|
||||||
|
_ => (0, 0),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let not_ahead_or_behind = status_against_remote(0, 0);
|
||||||
|
let ahead_of_remote = status_against_remote(1, 0);
|
||||||
|
let branch_not_on_remote = if let Some(branch) = self.current_branch.as_ref() {
|
||||||
|
branch.upstream.is_none()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
let has_branch_container = |branch: &Branch| {
|
||||||
|
h_flex()
|
||||||
|
.max_w(px(420.))
|
||||||
|
.bg(cx.theme().colors().text.opacity(0.05))
|
||||||
|
.border_1()
|
||||||
|
.border_color(cx.theme().colors().border)
|
||||||
|
.rounded_sm()
|
||||||
|
.gap_8()
|
||||||
|
.px_6()
|
||||||
|
.py_4()
|
||||||
|
.map(|this| {
|
||||||
|
if ahead_of_remote {
|
||||||
|
let ahead_count = change_count(branch).0;
|
||||||
|
let ahead_string = format!("{} Commits Ahead", ahead_count);
|
||||||
|
this.child(
|
||||||
|
v_flex()
|
||||||
|
.child(Headline::new(ahead_string).size(HeadlineSize::Small))
|
||||||
|
.child(
|
||||||
|
Label::new(format!("Push your changes to {}", branch.name))
|
||||||
|
.color(Color::Muted),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(div().child(render_push_button(
|
||||||
|
self.focus_handle,
|
||||||
|
"push".into(),
|
||||||
|
ahead_count as u32,
|
||||||
|
)))
|
||||||
|
} else if branch_not_on_remote {
|
||||||
|
this.child(
|
||||||
|
v_flex()
|
||||||
|
.child(Headline::new("Publish Branch").size(HeadlineSize::Small))
|
||||||
|
.child(
|
||||||
|
Label::new(format!("Create {} on remote", branch.name))
|
||||||
|
.color(Color::Muted),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div().child(render_publish_button(self.focus_handle, "publish".into())),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
this.child(Label::new("Remote status unknown").color(Color::Muted))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
v_flex().size_full().items_center().justify_center().child(
|
||||||
|
v_flex()
|
||||||
|
.gap_1()
|
||||||
|
.when(self.no_repo, |this| {
|
||||||
|
// TODO: add git init
|
||||||
|
this.text_center()
|
||||||
|
.child(Label::new("No Repository").color(Color::Muted))
|
||||||
|
})
|
||||||
|
.map(|this| {
|
||||||
|
if not_ahead_or_behind && self.current_branch.is_some() {
|
||||||
|
this.text_center()
|
||||||
|
.child(Label::new("No Changes").color(Color::Muted))
|
||||||
|
} else {
|
||||||
|
this.when_some(self.current_branch.as_ref(), |this, branch| {
|
||||||
|
this.child(has_branch_container(&branch))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// .when(self.can_push_and_pull, |this| {
|
||||||
|
// let remote_button = crate::render_remote_button(
|
||||||
|
// "project-diff-remote-button",
|
||||||
|
// &branch,
|
||||||
|
// self.focus_handle.clone(),
|
||||||
|
// false,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// match remote_button {
|
||||||
|
// Some(button) => {
|
||||||
|
// this.child(h_flex().justify_around().child(button))
|
||||||
|
// }
|
||||||
|
// None => this.child(
|
||||||
|
// h_flex()
|
||||||
|
// .justify_around()
|
||||||
|
// .child(Label::new("Remote up to date")),
|
||||||
|
// ),
|
||||||
|
// }
|
||||||
|
// }),
|
||||||
|
//
|
||||||
|
// // .map(|this| {
|
||||||
|
// this.child(h_flex().justify_around().mt_1().child(
|
||||||
|
// Button::new("project-diff-close-button", "Close").when_some(
|
||||||
|
// self.focus_handle.clone(),
|
||||||
|
// |this, focus_handle| {
|
||||||
|
// this.key_binding(KeyBinding::for_action_in(
|
||||||
|
// &CloseActiveItem::default(),
|
||||||
|
// &focus_handle,
|
||||||
|
// window,
|
||||||
|
// cx,
|
||||||
|
// ))
|
||||||
|
// .on_click(move |_, window, cx| {
|
||||||
|
// window.focus(&focus_handle);
|
||||||
|
// window
|
||||||
|
// .dispatch_action(Box::new(CloseActiveItem::default()), cx);
|
||||||
|
// })
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// ))
|
||||||
|
// }),
|
||||||
|
|
||||||
|
mod preview {
|
||||||
|
use git::repository::{
|
||||||
|
Branch, CommitSummary, Upstream, UpstreamTracking, UpstreamTrackingStatus,
|
||||||
|
};
|
||||||
|
use ui::prelude::*;
|
||||||
|
|
||||||
|
use super::ProjectDiffEmptyState;
|
||||||
|
|
||||||
|
// View this component preview using `workspace: open component-preview`
|
||||||
|
impl ComponentPreview for ProjectDiffEmptyState {
|
||||||
|
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
|
||||||
|
let unknown_upstream: Option<UpstreamTracking> = None;
|
||||||
|
let ahead_of_upstream: Option<UpstreamTracking> = Some(
|
||||||
|
UpstreamTrackingStatus {
|
||||||
|
ahead: 2,
|
||||||
|
behind: 0,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let not_ahead_or_behind_upstream: Option<UpstreamTracking> = Some(
|
||||||
|
UpstreamTrackingStatus {
|
||||||
|
ahead: 0,
|
||||||
|
behind: 0,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
fn branch(upstream: Option<UpstreamTracking>) -> Branch {
|
||||||
|
Branch {
|
||||||
|
is_head: true,
|
||||||
|
name: "some-branch".into(),
|
||||||
|
upstream: upstream.map(|tracking| Upstream {
|
||||||
|
ref_name: "origin/some-branch".into(),
|
||||||
|
tracking,
|
||||||
|
}),
|
||||||
|
most_recent_commit: Some(CommitSummary {
|
||||||
|
sha: "abc123".into(),
|
||||||
|
subject: "Modify stuff".into(),
|
||||||
|
commit_timestamp: 1710932954,
|
||||||
|
has_parent: true,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let no_repo_state = ProjectDiffEmptyState {
|
||||||
|
no_repo: true,
|
||||||
|
can_push_and_pull: false,
|
||||||
|
focus_handle: None,
|
||||||
|
current_branch: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let no_changes_state = ProjectDiffEmptyState {
|
||||||
|
no_repo: false,
|
||||||
|
can_push_and_pull: true,
|
||||||
|
focus_handle: None,
|
||||||
|
current_branch: Some(branch(not_ahead_or_behind_upstream)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let ahead_of_upstream_state = ProjectDiffEmptyState {
|
||||||
|
no_repo: false,
|
||||||
|
can_push_and_pull: true,
|
||||||
|
focus_handle: None,
|
||||||
|
current_branch: Some(branch(ahead_of_upstream)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let unknown_upstream_state = ProjectDiffEmptyState {
|
||||||
|
no_repo: false,
|
||||||
|
can_push_and_pull: true,
|
||||||
|
focus_handle: None,
|
||||||
|
current_branch: Some(branch(unknown_upstream)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (width, height) = (px(480.), px(320.));
|
||||||
|
|
||||||
|
v_flex()
|
||||||
|
.gap_6()
|
||||||
|
.children(vec![example_group(vec![
|
||||||
|
single_example(
|
||||||
|
"No Repo",
|
||||||
|
div()
|
||||||
|
.w(width)
|
||||||
|
.h(height)
|
||||||
|
.child(no_repo_state)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"No Changes",
|
||||||
|
div()
|
||||||
|
.w(width)
|
||||||
|
.h(height)
|
||||||
|
.child(no_changes_state)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Unknown Upstream",
|
||||||
|
div()
|
||||||
|
.w(width)
|
||||||
|
.h(height)
|
||||||
|
.child(unknown_upstream_state)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Ahead of Remote",
|
||||||
|
div()
|
||||||
|
.w(width)
|
||||||
|
.h(height)
|
||||||
|
.child(ahead_of_upstream_state)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.vertical()])
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue