This commit is contained in:
2026-09-14 09:05:57 +07:00
parent c6ada10afa
commit 9fbd1c9dfa
4 changed files with 166 additions and 59 deletions
+73 -26
View File
@@ -26,8 +26,9 @@ use nostr::prelude::{RelayUrl, ToBech32, Url};
use signed_core::{Announcement, RepoAddr, RepoStatus};
use signed_git::FileCommit;
use signed_state::{
Backend, CheckoutStatus, CheckoutsStore, LocalReposStore, ProfileStore, RepoListStore,
RepoStore, ensure_repo_mirror, open_repo_mirror, pr_proposes_checkout,
Backend, CheckoutStatus, CheckoutsStore, LocalReposStore, Nip34Binding, Nip34Kind,
ProfileStore, RepoListStore, RepoStore, ensure_repo_mirror, open_repo_mirror,
pr_proposes_checkout,
};
use signed_ui::{
CountBadge, DropdownButton, PixelAvatar, UserAvatar, copy_row, menu_copy_row, middle_truncate,
@@ -123,10 +124,26 @@ impl RepoDetailView {
pub fn new_local(
dock_area: WeakEntity<DockArea>,
local_path: PathBuf,
nip34: Option<Nip34Binding>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let store = cx.new(move |_cx| RepoStore::new_local(local_path));
let store = cx.new(move |_cx| RepoStore::new_local(local_path, nip34));
Self::new_common(dock_area, store, window, cx)
}
/// A local repository whose detected binding matches an announcement.
///
/// Opens as the announced repository with the local worktree attached.
pub fn new_local_announced(
dock_area: WeakEntity<DockArea>,
announcement: Announcement,
local_path: PathBuf,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let addr = announcement.addr();
let store = cx.new(move |cx| RepoStore::from_worktree(addr, announcement, local_path, cx));
Self::new_common(dock_area, store, window, cx)
}
@@ -294,24 +311,17 @@ impl RepoDetailView {
self.error = None;
cx.notify();
let (addr, announcement, local_path) = {
let (announcement, local_path) = {
let store = self.store.read(cx);
(
store.addr().cloned(),
store.announcement.clone(),
store.path.clone(),
)
(store.announcement.clone(), store.path.clone())
};
// Local repositories live on disk at their scan path.
// No clone step or network refresh applies here.
if addr.is_none() {
// A repository with a local worktree shows it directly. An announced one
// still loads its announcement and activity from the store, which is
// subscribed to the relays independently.
if let Some(local_path) = local_path {
self.repo_started = true;
let Some(local_path) = local_path else {
return;
};
let task: gpui::Task<Result<(), Error>> = cx.spawn_in(window, async move |this, cx| {
let data = cx
.background_spawn(async move {
@@ -1254,6 +1264,24 @@ impl RepoDetailView {
.unwrap_or_default();
let avatar = PixelAvatar::new(path.clone());
// A repository already bound to a coordinate is not offered for publishing again.
let bound = self.store.read(cx).nip34.clone();
let action = match bound
.as_ref()
.filter(|binding| binding.kind == Nip34Kind::Initialized)
{
Some(binding) => bound_repo_label(binding, cx),
None => Button::new("init")
.icon(CustomIconName::Init)
.label("Initialize on Nostr")
.primary()
.tooltip("Publish this repository to Nostr")
.on_click(cx.listener(|this, _event, window, cx| {
this.open_init_dialog(window, cx);
}))
.into_any_element(),
};
v_flex()
.px_4()
.pb_4()
@@ -1291,16 +1319,7 @@ impl RepoDetailView {
.child(path),
),
)
.child(
Button::new("init")
.icon(CustomIconName::Init)
.label("Initialize on Nostr")
.primary()
.tooltip("Publish this repository to Nostr")
.on_click(cx.listener(|this, _event, window, cx| {
this.open_init_dialog(window, cx);
})),
),
.child(action),
)
.child(self.render_header_tabs(cx))
.into_any_element()
@@ -1996,6 +2015,34 @@ pub(super) fn repo_display_name(store: &RepoStore) -> SharedString {
.unwrap_or_default()
}
/// The owner and identifier a local repository is already bound to.
fn bound_repo_label(binding: &Nip34Binding, cx: &App) -> AnyElement {
let owner = binding
.owner
.and_then(|owner| owner.to_bech32().ok())
.map(|npub| middle_truncate(&npub, 12, 8))
.unwrap_or_else(|| "a NIP-34 coordinate".to_owned());
let mut label = v_flex().flex_shrink_0().items_end().gap_1().child(
div()
.text_xs()
.text_color(cx.theme().muted_foreground)
.child(SharedString::from(format!("Bound to {owner}"))),
);
if let Some(identifier) = binding.identifier.as_deref() {
label = label.child(
div()
.text_xs()
.font_semibold()
.text_color(cx.theme().muted_foreground)
.child(SharedString::from(identifier.to_owned())),
);
}
label.into_any_element()
}
impl BasePanel for RepoDetailView {
fn panel_name(&self) -> &'static str {
"repo"
+56 -15
View File
@@ -10,8 +10,9 @@ use dock::{
};
use gpui::prelude::*;
use gpui::{
AnyElement, App, Context, Div, EventEmitter, FocusHandle, Focusable, Hsla, ObjectFit, Render,
SharedString, Subscription, WeakEntity, Window, div, img, px, relative, uniform_list, white,
AnyElement, App, Context, Div, Entity, EventEmitter, FocusHandle, Focusable, Hsla, ObjectFit,
Render, SharedString, Subscription, WeakEntity, Window, div, img, px, relative, uniform_list,
white,
};
use gpui_base::Button as BaseButton;
use gpui_component::button::{Button, ButtonVariants};
@@ -264,31 +265,71 @@ impl SidebarPanel {
}
/// The detail view offers to publish it to NIP-34.
fn open_local_repo(&mut self, path: PathBuf, window: &mut Window, cx: &mut Context<Self>) {
fn open_local_repo(
&mut self,
path: PathBuf,
nip34: Option<Nip34Binding>,
window: &mut Window,
cx: &mut Context<Self>,
) {
let detail =
cx.new(|cx| RepoDetailView::new_local(self.dock_area.clone(), path, window, cx));
self.dock_area
.update(cx, |dock_area, cx| {
add_center_panel(dock_area, panel_handle(detail), window, cx);
})
.ok();
cx.new(|cx| RepoDetailView::new_local(self.dock_area.clone(), path, nip34, window, cx));
self.add_detail_panel(detail, window, cx);
}
/// A local repository opens as the announced repository when its binding matches one,
/// and as a local-only repository otherwise.
/// A local repository whose binding matches an announcement opens as the announced repository.
fn open_local_announced(
&mut self,
announcement: Announcement,
path: PathBuf,
window: &mut Window,
cx: &mut Context<Self>,
) {
let detail = cx.new(|cx| {
RepoDetailView::new_local_announced(
self.dock_area.clone(),
announcement,
path,
window,
cx,
)
});
self.add_detail_panel(detail, window, cx);
}
/// A local repository opens as the announced repository
/// when its binding matches one, and as a local-only repository otherwise.
fn open_local_entry(
&mut self,
entry: ResolvedLocalRepo,
window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(announcement) = entry.announcement {
self.open_repo(&announcement, window, cx);
let ResolvedLocalRepo {
path,
nip34,
announcement,
} = entry;
if let Some(announcement) = announcement {
self.open_local_announced(announcement, path, window, cx);
return;
}
self.open_local_repo(entry.path, window, cx);
self.open_local_repo(path, nip34, window, cx);
}
fn add_detail_panel(
&mut self,
detail: Entity<RepoDetailView>,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.dock_area
.update(cx, |dock_area, cx| {
add_center_panel(dock_area, panel_handle(detail), window, cx);
})
.ok();
}
fn render_repos(&self, cx: &mut Context<Self>) -> impl IntoElement {