update
This commit is contained in:
@@ -204,21 +204,22 @@ fn list(id: &'static str, items: impl IntoIterator<Item = String>, cx: &App) ->
|
||||
.min_w_0()
|
||||
.children(items.into_iter().enumerate().map(|(ix, item)| {
|
||||
h_flex()
|
||||
.id(ix)
|
||||
.h_8()
|
||||
.px_2()
|
||||
.gap_2()
|
||||
.items_center()
|
||||
.min_w_0()
|
||||
.bg(cx.theme().secondary)
|
||||
.hover(|this| this.bg(cx.theme().secondary_hover))
|
||||
.rounded(cx.theme().radius)
|
||||
.text_color(cx.theme().secondary_foreground)
|
||||
.child(
|
||||
h_flex()
|
||||
.h_5()
|
||||
.px_1()
|
||||
div()
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.overflow_hidden()
|
||||
.whitespace_nowrap()
|
||||
.text_ellipsis()
|
||||
.text_sm()
|
||||
.bg(cx.theme().muted)
|
||||
.rounded(cx.theme().radius)
|
||||
.child(SharedString::from(middle_truncate(&item, 28, 16))),
|
||||
)
|
||||
.child(
|
||||
|
||||
@@ -13,12 +13,14 @@ use gpui::{
|
||||
Task, WeakEntity, Window, div, px, relative, size,
|
||||
};
|
||||
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::clipboard::Clipboard;
|
||||
use gpui_component::combobox::{
|
||||
Caret, Combobox, ComboboxEvent, ComboboxState, ComboboxTriggerContext,
|
||||
};
|
||||
use gpui_component::menu::DropdownMenu;
|
||||
use gpui_component::searchable_list::SearchableVec;
|
||||
use gpui_component::tree::TreeState;
|
||||
use gpui_component::{
|
||||
@@ -28,7 +30,7 @@ use gpui_component::{
|
||||
use nostr::prelude::{RelayUrl, ToBech32};
|
||||
use signed_core::Announcement;
|
||||
use signed_git::{CommitList, FileCommit};
|
||||
use signed_state::{GitStore, LocalReposStore, ProfileStore, RepoStore};
|
||||
use signed_state::{Backend, GitStore, LocalReposStore, ProfileStore, RepoStore};
|
||||
|
||||
use crate::image_cache::{MAX_IMAGES, image_cache};
|
||||
use crate::pixel_avatar::PixelAvatar;
|
||||
@@ -74,6 +76,12 @@ enum RepoAction {
|
||||
NewIssue,
|
||||
/// Open the "new pull request" dialog.
|
||||
NewPR,
|
||||
/// Open the about dialog.
|
||||
About,
|
||||
/// Re-push the repository to its grasp servers.
|
||||
Push,
|
||||
/// Delete the repository from nostr (owner only).
|
||||
Delete,
|
||||
}
|
||||
|
||||
/// Everything loaded from the local clone for the explorer: the tree seeds,
|
||||
@@ -148,6 +156,8 @@ pub struct RepoDetailView {
|
||||
loading: bool,
|
||||
/// The header clone button is cloning into a user-chosen folder.
|
||||
cloning: bool,
|
||||
/// A push to the grasp servers is in flight.
|
||||
pushing: bool,
|
||||
error: Option<SharedString>,
|
||||
/// Commit HEAD currently points to, shown in the header button.
|
||||
head_commit: Option<FileCommit>,
|
||||
@@ -281,6 +291,7 @@ impl RepoDetailView {
|
||||
item_sizes: Rc::new(Vec::new()),
|
||||
loading: true,
|
||||
cloning: false,
|
||||
pushing: false,
|
||||
error: None,
|
||||
head_commit: None,
|
||||
branch_select,
|
||||
@@ -828,6 +839,60 @@ impl RepoDetailView {
|
||||
});
|
||||
}
|
||||
|
||||
/// Re-push the repository's refs to its announced grasp servers; the
|
||||
/// menu trigger shows a spinner while the push is in flight, failures
|
||||
/// appear in the panel's error banner.
|
||||
fn push_repository(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.pushing {
|
||||
return;
|
||||
}
|
||||
let Some(announcement) = self.announcement(cx).cloned() else {
|
||||
return;
|
||||
};
|
||||
self.pushing = true;
|
||||
self.error = None;
|
||||
cx.notify();
|
||||
|
||||
let backend = Backend::global(cx);
|
||||
let task = backend.update(cx, |backend, cx| backend.push_repository(announcement, cx));
|
||||
|
||||
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
|
||||
let result = task.await;
|
||||
this.update_in(cx, |this, _window, cx| {
|
||||
if let Err(error) = result {
|
||||
this.error = Some(format!("Push failed: {error}").into());
|
||||
}
|
||||
this.pushing = false;
|
||||
cx.notify();
|
||||
})?;
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
/// Delete the repository from nostr (announcement, state and activity);
|
||||
/// only offered to the repository owner. The sidebar list updates when
|
||||
/// the deletion events arrive.
|
||||
fn delete_repository(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(announcement) = self.announcement(cx).cloned() else {
|
||||
return;
|
||||
};
|
||||
let backend = Backend::global(cx);
|
||||
let task = backend.update(cx, |backend, cx| {
|
||||
backend.delete_repository(announcement.addr(), cx)
|
||||
});
|
||||
|
||||
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
|
||||
let result = task.await;
|
||||
this.update_in(cx, |this, _window, cx| {
|
||||
if let Err(error) = result {
|
||||
this.error = Some(format!("Delete failed: {error}").into());
|
||||
}
|
||||
cx.notify();
|
||||
})?;
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
/// Open the issues panel at the bottom of the dock area.
|
||||
fn open_issue_detail(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(store) = self.store.clone() else {
|
||||
@@ -1145,7 +1210,12 @@ impl RepoDetailView {
|
||||
return div().into_any_element();
|
||||
};
|
||||
let store = store_entity.read(cx);
|
||||
let Some(announcement) = store.announcement.as_ref().or(self.initial.as_ref()) else {
|
||||
let Some(announcement) = store
|
||||
.announcement
|
||||
.as_ref()
|
||||
.or(self.initial.as_ref())
|
||||
.cloned()
|
||||
else {
|
||||
return div().into_any_element();
|
||||
};
|
||||
let issue_count = SharedString::from(store.issue_count().to_string());
|
||||
@@ -1154,9 +1224,9 @@ impl RepoDetailView {
|
||||
let name = self.display_name(cx);
|
||||
let description = announcement.description();
|
||||
let avatar = PixelAvatar::new(format!("{}:{}", announcement.owner, announcement.id));
|
||||
let share = ShareTargets::from_announcement(announcement);
|
||||
let share = ShareTargets::from_announcement(&announcement);
|
||||
|
||||
let nostr_url = nostr_clone_url(announcement, cx);
|
||||
let nostr_url = nostr_clone_url(&announcement, cx);
|
||||
let ngit_command = SharedString::from(format!("git clone {nostr_url}"));
|
||||
let nak_command = SharedString::from(format!("nak git clone {nostr_url}"));
|
||||
let git_commands = announcement.clone_urls();
|
||||
@@ -1174,6 +1244,13 @@ impl RepoDetailView {
|
||||
open_new_pull_request_dialog(store, window, cx);
|
||||
}
|
||||
}
|
||||
RepoAction::About => {
|
||||
if let Some(announcement) = this.announcement(cx) {
|
||||
open_about_dialog(announcement.clone(), window, cx);
|
||||
}
|
||||
}
|
||||
RepoAction::Push => this.push_repository(window, cx),
|
||||
RepoAction::Delete => this.delete_repository(window, cx),
|
||||
}),
|
||||
)
|
||||
.px_4()
|
||||
@@ -1263,11 +1340,13 @@ impl RepoDetailView {
|
||||
})),
|
||||
)
|
||||
.dropdown_menu(|menu, _, _| {
|
||||
menu.menu_element_with_icon(
|
||||
IconName::Plus,
|
||||
Box::new(RepoAction::NewIssue),
|
||||
|_, _| div().text_xs().child("New issue"),
|
||||
)
|
||||
menu.menu_element(Box::new(RepoAction::NewIssue), |_, _| {
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.child(Icon::new(IconName::Plus))
|
||||
.child("New issue")
|
||||
})
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
@@ -1304,11 +1383,13 @@ impl RepoDetailView {
|
||||
})),
|
||||
)
|
||||
.dropdown_menu(|menu, _, _| {
|
||||
menu.menu_element_with_icon(
|
||||
IconName::Plus,
|
||||
Box::new(RepoAction::NewPR),
|
||||
|_, _| div().text_xs().child("New PR"),
|
||||
)
|
||||
menu.menu_element(Box::new(RepoAction::NewPR), |_, _| {
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.child(Icon::new(IconName::Plus))
|
||||
.child("New PR")
|
||||
})
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
@@ -1330,15 +1411,50 @@ impl RepoDetailView {
|
||||
.dropdown_menu(move |menu, _, _| share.menu(menu)),
|
||||
)
|
||||
.child(
|
||||
Button::new("info")
|
||||
.icon(IconName::Info)
|
||||
.tooltip("About")
|
||||
Button::new("repo-menu-open")
|
||||
.icon(IconName::EllipsisVertical)
|
||||
.tooltip("Repository management")
|
||||
.compact()
|
||||
.secondary()
|
||||
.on_click(cx.listener(|this, _event, window, cx| {
|
||||
if let Some(announcement) = this.announcement(cx) {
|
||||
open_about_dialog(announcement.clone(), window, cx);
|
||||
.loading(self.pushing)
|
||||
.disabled(self.pushing)
|
||||
.dropdown_menu(move |menu, _, cx| {
|
||||
let backend = Backend::global(cx);
|
||||
let current_user = backend.read(cx).current_user();
|
||||
let owner = current_user == Some(announcement.owner);
|
||||
|
||||
let menu = menu.menu_element(
|
||||
Box::new(RepoAction::About),
|
||||
|_, _| {
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.child(Icon::new(IconName::Info))
|
||||
.child("About")
|
||||
},
|
||||
);
|
||||
|
||||
if owner {
|
||||
menu.menu_element(Box::new(RepoAction::Push), |_, _| {
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.child(Icon::new(CustomIconName::Init))
|
||||
.child("Republish")
|
||||
})
|
||||
.separator()
|
||||
.menu_element(Box::new(RepoAction::Delete), |_, cx| {
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().danger)
|
||||
.child(Icon::new(IconName::Delete))
|
||||
.child("Delete")
|
||||
})
|
||||
} else {
|
||||
menu
|
||||
}
|
||||
})),
|
||||
}),
|
||||
)
|
||||
.child({
|
||||
let view = cx.entity();
|
||||
@@ -1781,6 +1897,16 @@ impl Render for RepoDetailView {
|
||||
.id("repo")
|
||||
.size_full()
|
||||
.child(self.render_header(cx))
|
||||
.when_some(self.error.clone(), |this, error| {
|
||||
this.child(
|
||||
Alert::error("repo-error", error)
|
||||
.banner()
|
||||
.on_close(cx.listener(|this, _event, _window, cx| {
|
||||
this.error = None;
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
})
|
||||
.map(|this| match self.active_tab {
|
||||
0 => this.child(
|
||||
h_flex()
|
||||
|
||||
Reference in New Issue
Block a user