update pull request
This commit is contained in:
@@ -27,9 +27,12 @@ use gpui_component::{
|
||||
VirtualListScrollHandle, h_flex, v_flex,
|
||||
};
|
||||
use nostr::prelude::{EventId, RelayUrl, ToBech32};
|
||||
use signed_core::{Announcement, RepoAddr, filters};
|
||||
use signed_core::{Announcement, RepoAddr, RepoStatus, filters};
|
||||
use signed_git::{CommitList, FileCommit};
|
||||
use signed_state::{Backend, GitStore, LocalReposStore, ProfileStore, RepoListStore, RepoStore};
|
||||
use signed_state::{
|
||||
Backend, CheckoutStatus, CheckoutsStore, GitStore, LocalReposStore, ProfileStore,
|
||||
RepoListStore, RepoStore, pr_proposes_checkout,
|
||||
};
|
||||
use signed_ui::image_cache::{MAX_IMAGES, image_cache};
|
||||
use signed_ui::{DropdownButton, PixelAvatar, UserAvatar, copy_row};
|
||||
|
||||
@@ -198,6 +201,17 @@ pub struct RepoDetailView {
|
||||
tasks: Vec<Task<Result<(), Error>>>,
|
||||
/// Subscriptions keeping the selectors' confirm events alive.
|
||||
_subscriptions: Vec<Subscription>,
|
||||
/// Observes the checkouts store, whose statuses feed the "ready to
|
||||
/// contribute" banner of the repository panel.
|
||||
_checkouts_subscription: Subscription,
|
||||
/// `(path, branch)` ready-suggestions dismissed by the user, per panel.
|
||||
banner_dismissed: HashSet<(PathBuf, String)>,
|
||||
/// The announced HEAD the ready-statuses were last requested with, and
|
||||
/// whether they were requested at all (re-requested only when the HEAD
|
||||
/// — the base default — changes, e.g. when the store's first refresh
|
||||
/// lands).
|
||||
ready_requested: bool,
|
||||
ready_head: Option<String>,
|
||||
/// Upstream repository (from this fork's `u` tag) the user asked to
|
||||
/// open, while its announcement is still being fetched.
|
||||
pending_upstream: Option<RepoAddr>,
|
||||
@@ -219,7 +233,16 @@ impl RepoDetailView {
|
||||
let relays = initial.relays.clone();
|
||||
let store = cx.new(|cx| RepoStore::new(addr, relays, cx));
|
||||
|
||||
Self::new_common(dock_area, Some(initial), Some(store), None, window, cx)
|
||||
let mut view = Self::new_common(
|
||||
dock_area,
|
||||
Some(initial),
|
||||
Some(store.clone()),
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
view.attach_store(&store, cx);
|
||||
view
|
||||
}
|
||||
|
||||
/// Open a local repository discovered by the scan. There is no
|
||||
@@ -328,6 +351,11 @@ impl RepoDetailView {
|
||||
focus_handle: cx.focus_handle(),
|
||||
tasks: Vec::new(),
|
||||
_subscriptions: subscriptions,
|
||||
_checkouts_subscription: cx
|
||||
.observe(&CheckoutsStore::global(cx), |_this, _store, cx| cx.notify()),
|
||||
banner_dismissed: HashSet::new(),
|
||||
ready_requested: false,
|
||||
ready_head: None,
|
||||
pending_upstream: None,
|
||||
}
|
||||
}
|
||||
@@ -548,7 +576,7 @@ impl RepoDetailView {
|
||||
return;
|
||||
}
|
||||
|
||||
let (clone_urls, name) = {
|
||||
let (clone_urls, name, addr) = {
|
||||
let Some(announcement) = self.announcement(cx) else {
|
||||
return;
|
||||
};
|
||||
@@ -569,7 +597,7 @@ impl RepoDetailView {
|
||||
} else {
|
||||
name
|
||||
};
|
||||
(clone_urls, name)
|
||||
(clone_urls, name, addr)
|
||||
};
|
||||
|
||||
self.cloning = true;
|
||||
@@ -599,14 +627,23 @@ impl RepoDetailView {
|
||||
|
||||
let destination = folder.join(&name);
|
||||
let destination_for_open = destination.clone();
|
||||
let clone_target = destination_for_open.clone();
|
||||
let result = cx
|
||||
.background_spawn(async move { signed_git::clone_repo(&clone_urls, &destination) })
|
||||
.background_spawn(async move { signed_git::clone_repo(&clone_urls, &clone_target) })
|
||||
.await;
|
||||
|
||||
this.update_in(cx, |this, _window, cx| {
|
||||
this.cloning = false;
|
||||
match result {
|
||||
Ok(_) => cx.open_with_system(&destination_for_open),
|
||||
Ok(_) => {
|
||||
cx.open_with_system(&destination_for_open);
|
||||
// Remember the clone as a checkout of this
|
||||
// repository, so the New PR panel pre-fills it.
|
||||
let checkouts = CheckoutsStore::global(cx);
|
||||
checkouts.update(cx, |store, cx| {
|
||||
store.record(destination, addr, cx);
|
||||
});
|
||||
}
|
||||
Err(error) => {
|
||||
this.error = Some(format!("Failed to clone: {error}").into());
|
||||
}
|
||||
@@ -934,15 +971,7 @@ impl RepoDetailView {
|
||||
return;
|
||||
};
|
||||
|
||||
let panel = cx.new(|cx| {
|
||||
IssuesView::new(
|
||||
self.dock_area.clone(),
|
||||
store,
|
||||
self.display_name(cx),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let panel = cx.new(|cx| IssuesView::new(self.dock_area.clone(), store, window, cx));
|
||||
|
||||
dock_area.update(cx, |dock_area, cx| {
|
||||
dock_area.add_panel_view(panel_handle(panel), DockPlacement::Center, None, window, cx);
|
||||
@@ -958,15 +987,7 @@ impl RepoDetailView {
|
||||
return;
|
||||
};
|
||||
|
||||
let panel = cx.new(|cx| {
|
||||
PullRequestsView::new(
|
||||
self.dock_area.clone(),
|
||||
store,
|
||||
self.display_name(cx),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let panel = cx.new(|cx| PullRequestsView::new(self.dock_area.clone(), store, window, cx));
|
||||
|
||||
dock_area.update(cx, |dock_area, cx| {
|
||||
dock_area.add_panel_view(panel_handle(panel), DockPlacement::Center, None, window, cx);
|
||||
@@ -1798,14 +1819,127 @@ impl RepoDetailView {
|
||||
}
|
||||
let store =
|
||||
cx.new(|cx| RepoStore::new(announcement.addr(), announcement.relays.clone(), cx));
|
||||
// Re-render when the store refreshes (issues, PRs, statuses).
|
||||
self._subscriptions
|
||||
.push(cx.observe(&store, |_this, _store, cx| cx.notify()));
|
||||
// Re-render on store refreshes (issues, PRs, statuses) and keep the
|
||||
// "ready to contribute" statuses of this repository requested.
|
||||
self.attach_store(&store, cx);
|
||||
self.store = Some(store);
|
||||
self.initial = Some(announcement);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Observe the repository's store (re-render on refreshes) and request
|
||||
/// the "ready to contribute" statuses for it.
|
||||
fn attach_store(&mut self, store: &Entity<RepoStore>, cx: &mut Context<Self>) {
|
||||
self._subscriptions
|
||||
.push(cx.observe(store, |this, _store, cx| {
|
||||
cx.notify();
|
||||
// The first refresh fills the announced HEAD, which defaults
|
||||
// the banner's base branch; re-request when it changes.
|
||||
this.refresh_ready_statuses(cx);
|
||||
}));
|
||||
self.refresh_ready_statuses(cx);
|
||||
}
|
||||
|
||||
/// (Re)request the ready statuses of this repository when the announced
|
||||
/// HEAD — the base the checkouts are compared against — changed since
|
||||
/// the last request.
|
||||
fn refresh_ready_statuses(&mut self, cx: &mut Context<Self>) {
|
||||
let Some(store) = self.store.clone() else {
|
||||
return;
|
||||
};
|
||||
let head = store.read(cx).head.clone();
|
||||
if self.ready_requested && self.ready_head == head {
|
||||
return;
|
||||
}
|
||||
self.ready_requested = true;
|
||||
self.ready_head = head.clone();
|
||||
let addr = store.read(cx).addr().clone();
|
||||
CheckoutsStore::global(cx).update(cx, |store, cx| {
|
||||
store.request_statuses(&addr, head, cx);
|
||||
});
|
||||
}
|
||||
|
||||
/// The first checkout ready for a pull request on this repository,
|
||||
/// not covered by an open PR of the signed-in user and not dismissed in this panel.
|
||||
fn ready_suggestion(&self, cx: &App) -> Option<CheckoutStatus> {
|
||||
let store = self.store.as_ref()?;
|
||||
let addr = store.read(cx).addr().clone();
|
||||
|
||||
let statuses = CheckoutsStore::global(cx).read(cx).statuses_of(&addr);
|
||||
let user = Backend::global(cx).read(cx).current_user()?;
|
||||
|
||||
'status: for status in statuses {
|
||||
if self
|
||||
.banner_dismissed
|
||||
.contains(&(status.path.clone(), status.branch.clone()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let store = store.read(cx);
|
||||
for pr in &store.pull_requests {
|
||||
if pr_proposes_checkout(pr, store.status_of(pr) == RepoStatus::Open, user, &status)
|
||||
{
|
||||
continue 'status;
|
||||
}
|
||||
}
|
||||
return Some(status);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// The "ready to contribute" banner of the repository panel: message,
|
||||
/// a Create action opening the prefilled New PR panel, and a dismiss
|
||||
/// control.
|
||||
fn render_ready_banner(&self, cx: &Context<Self>) -> Option<AnyElement> {
|
||||
let status = self.ready_suggestion(cx)?;
|
||||
let commits = if status.ahead == 1 {
|
||||
"1 commit".to_owned()
|
||||
} else {
|
||||
format!("{} commits", status.ahead)
|
||||
};
|
||||
let message = SharedString::from(format!(
|
||||
"{} is {} ahead of {} in {}",
|
||||
status.branch,
|
||||
commits,
|
||||
status.base,
|
||||
status.path.display()
|
||||
));
|
||||
let key = (status.path.clone(), status.branch.clone());
|
||||
let view = cx.entity().clone();
|
||||
|
||||
Some(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.px_4()
|
||||
.pt_1()
|
||||
.w_full()
|
||||
.items_center()
|
||||
.child(
|
||||
Alert::info("repo-ready-to-contribute", message)
|
||||
.banner()
|
||||
.flex_1()
|
||||
.on_close(move |_event, _window, cx| {
|
||||
view.update(cx, |this, _| {
|
||||
this.banner_dismissed.insert(key.clone());
|
||||
});
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Button::new("create-pr-from-banner")
|
||||
.small()
|
||||
.icon(IconName::Plus)
|
||||
.label("Create pull request")
|
||||
.on_click(cx.listener(|this, _event, window, cx| {
|
||||
if let Some(store) = this.store.clone() {
|
||||
open_new_pull_panel(this.dock_area.clone(), store, window, cx);
|
||||
}
|
||||
})),
|
||||
)
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
|
||||
/// The tab row shared by both header variants: Files/Commits tabs, the
|
||||
/// HEAD commit button and the branch/tag selectors.
|
||||
fn render_header_tabs(&self, cx: &mut Context<Self>) -> AnyElement {
|
||||
@@ -2036,6 +2170,9 @@ impl Render for RepoDetailView {
|
||||
.id("repo")
|
||||
.size_full()
|
||||
.child(self.render_header(cx))
|
||||
.when_some(self.render_ready_banner(cx), |this, banner| {
|
||||
this.child(banner)
|
||||
})
|
||||
.when_some(self.error.clone(), |this, error| {
|
||||
this.child(
|
||||
Alert::error("repo-error", error)
|
||||
|
||||
Reference in New Issue
Block a user