From 9f484ea98ded1e6e9f302a16334e2f4f2cdee2ac Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Mon, 17 Aug 2026 09:13:04 +0700 Subject: [PATCH] add issues and prs button --- Cargo.lock | 126 +++-- crates/signed_core/src/model.rs | 7 + .../src/views/repo_detail/browser.rs | 9 +- crates/workspace/src/views/repo_detail/mod.rs | 452 ++++++++---------- 4 files changed, 275 insertions(+), 319 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0d4dba5..33f55ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1244,7 +1244,7 @@ dependencies = [ [[package]] name = "collections" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "gpui_util", "indexmap", @@ -1701,7 +1701,7 @@ dependencies = [ [[package]] name = "derive_refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "proc-macro2", "quote", @@ -2094,9 +2094,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "fixedbitset" @@ -3436,7 +3436,7 @@ dependencies = [ [[package]] name = "gpui" version = "0.2.2" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "accesskit", "anyhow", @@ -3520,7 +3520,7 @@ dependencies = [ [[package]] name = "gpui-base" version = "0.5.2" -source = "git+https://github.com/longbridge/gpui-component#b83e4a3a950cfe28a30e4b5a73ad0dd06a821593" +source = "git+https://github.com/longbridge/gpui-component#da4f93696dc2b2b4d91bcc42412b9053a3d24de8" dependencies = [ "aho-corasick", "anyhow", @@ -3550,7 +3550,7 @@ dependencies = [ [[package]] name = "gpui-component" version = "0.5.2" -source = "git+https://github.com/longbridge/gpui-component#b83e4a3a950cfe28a30e4b5a73ad0dd06a821593" +source = "git+https://github.com/longbridge/gpui-component#da4f93696dc2b2b4d91bcc42412b9053a3d24de8" dependencies = [ "anyhow", "chrono", @@ -3630,7 +3630,7 @@ dependencies = [ [[package]] name = "gpui-component-assets" version = "0.5.1" -source = "git+https://github.com/longbridge/gpui-component#b83e4a3a950cfe28a30e4b5a73ad0dd06a821593" +source = "git+https://github.com/longbridge/gpui-component#da4f93696dc2b2b4d91bcc42412b9053a3d24de8" dependencies = [ "anyhow", "gpui", @@ -3644,17 +3644,40 @@ dependencies = [ [[package]] name = "gpui-component-macros" version = "0.5.1" -source = "git+https://github.com/longbridge/gpui-component#b83e4a3a950cfe28a30e4b5a73ad0dd06a821593" +source = "git+https://github.com/longbridge/gpui-component#da4f93696dc2b2b4d91bcc42412b9053a3d24de8" dependencies = [ "proc-macro2", "quote", "syn 2.0.119", ] +[[package]] +name = "gpui_apple" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" +dependencies = [ + "anyhow", + "block", + "cbindgen", + "cocoa 0.26.0", + "collections", + "core-foundation 0.10.1", + "core-video", + "derive_more", + "etagere", + "foreign-types", + "gpui", + "image", + "log", + "metal", + "objc", + "parking_lot", +] + [[package]] name = "gpui_linux" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "accesskit", "accesskit_unix", @@ -3706,7 +3729,7 @@ dependencies = [ [[package]] name = "gpui_macos" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "accesskit", "accesskit_macos", @@ -3714,21 +3737,18 @@ dependencies = [ "async-task", "block", "block2 0.6.2", - "cbindgen", "cocoa 0.26.0", "collections", "core-foundation 0.10.1", "core-foundation-sys", "core-graphics 0.24.0", "core-text", - "core-video", "ctor", - "derive_more", "dispatch2", - "etagere", "foreign-types", "futures", "gpui", + "gpui_apple", "gpui_util", "image", "itertools 0.14.0", @@ -3755,7 +3775,7 @@ dependencies = [ [[package]] name = "gpui_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -3766,7 +3786,7 @@ dependencies = [ [[package]] name = "gpui_platform" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "console_error_panic_hook", "gpui", @@ -3779,7 +3799,7 @@ dependencies = [ [[package]] name = "gpui_shared_string" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "schemars", "serde", @@ -3789,7 +3809,7 @@ dependencies = [ [[package]] name = "gpui_util" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "anyhow", "log", @@ -3799,7 +3819,7 @@ dependencies = [ [[package]] name = "gpui_web" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "anyhow", "console_error_panic_hook", @@ -3822,7 +3842,7 @@ dependencies = [ [[package]] name = "gpui_wgpu" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "anyhow", "bytemuck", @@ -3852,7 +3872,7 @@ dependencies = [ [[package]] name = "gpui_windows" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "accesskit", "accesskit_windows", @@ -4165,7 +4185,7 @@ dependencies = [ [[package]] name = "http_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "anyhow", "async-compression", @@ -4185,7 +4205,7 @@ dependencies = [ [[package]] name = "http_client_tls" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "rustls", "rustls-platform-verifier 0.5.3", @@ -4899,9 +4919,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" +checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" dependencies = [ "libc", ] @@ -5161,7 +5181,7 @@ dependencies = [ [[package]] name = "media" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "anyhow", "bindgen", @@ -5309,9 +5329,9 @@ dependencies = [ [[package]] name = "negentropy" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0efe882e02d206d8d279c20eb40e03baf7cb5136a1476dc084a324fbc3ec42d" +checksum = "81c353b400a5503efdcf398f11a83fb7aa84f59f5d76fc4bf5bbc1e4f5366caa" [[package]] name = "new_debug_unreachable" @@ -5376,8 +5396,8 @@ dependencies = [ [[package]] name = "nostr" -version = "0.45.1" -source = "git+https://github.com/rust-nostr/nostr#f0f52cf82e04824b7d3c5ac2f0bfc7bbeef37b19" +version = "0.45.2" +source = "git+https://github.com/rust-nostr/nostr#0a10756496173b7c97d7aa611fb8663931d1eaad" dependencies = [ "aes", "base64", @@ -5403,7 +5423,7 @@ dependencies = [ [[package]] name = "nostr-connect" version = "0.45.1" -source = "git+https://github.com/rust-nostr/nostr#f0f52cf82e04824b7d3c5ac2f0bfc7bbeef37b19" +source = "git+https://github.com/rust-nostr/nostr#0a10756496173b7c97d7aa611fb8663931d1eaad" dependencies = [ "async-utility", "futures-core", @@ -5417,7 +5437,7 @@ dependencies = [ [[package]] name = "nostr-database" version = "0.45.1" -source = "git+https://github.com/rust-nostr/nostr#f0f52cf82e04824b7d3c5ac2f0bfc7bbeef37b19" +source = "git+https://github.com/rust-nostr/nostr#0a10756496173b7c97d7aa611fb8663931d1eaad" dependencies = [ "nostr", "opaquerr", @@ -5426,7 +5446,7 @@ dependencies = [ [[package]] name = "nostr-gossip" version = "0.45.0" -source = "git+https://github.com/rust-nostr/nostr#f0f52cf82e04824b7d3c5ac2f0bfc7bbeef37b19" +source = "git+https://github.com/rust-nostr/nostr#0a10756496173b7c97d7aa611fb8663931d1eaad" dependencies = [ "nostr", "opaquerr", @@ -5435,7 +5455,7 @@ dependencies = [ [[package]] name = "nostr-gossip-memory" version = "0.45.0" -source = "git+https://github.com/rust-nostr/nostr#f0f52cf82e04824b7d3c5ac2f0bfc7bbeef37b19" +source = "git+https://github.com/rust-nostr/nostr#0a10756496173b7c97d7aa611fb8663931d1eaad" dependencies = [ "indexmap", "lru", @@ -5447,7 +5467,7 @@ dependencies = [ [[package]] name = "nostr-lmdb" version = "0.45.1" -source = "git+https://github.com/rust-nostr/nostr#f0f52cf82e04824b7d3c5ac2f0bfc7bbeef37b19" +source = "git+https://github.com/rust-nostr/nostr#0a10756496173b7c97d7aa611fb8663931d1eaad" dependencies = [ "async-utility", "flatbuffers", @@ -5462,7 +5482,7 @@ dependencies = [ [[package]] name = "nostr-memory" version = "0.45.1" -source = "git+https://github.com/rust-nostr/nostr#f0f52cf82e04824b7d3c5ac2f0bfc7bbeef37b19" +source = "git+https://github.com/rust-nostr/nostr#0a10756496173b7c97d7aa611fb8663931d1eaad" dependencies = [ "btreecap", "nostr", @@ -5473,7 +5493,7 @@ dependencies = [ [[package]] name = "nostr-sdk" version = "0.45.1" -source = "git+https://github.com/rust-nostr/nostr#f0f52cf82e04824b7d3c5ac2f0bfc7bbeef37b19" +source = "git+https://github.com/rust-nostr/nostr#0a10756496173b7c97d7aa611fb8663931d1eaad" dependencies = [ "async-utility", "async-wsocket", @@ -6209,7 +6229,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perf" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "collections", "serde", @@ -6961,7 +6981,7 @@ dependencies = [ [[package]] name = "refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "derive_refineable", ] @@ -7044,7 +7064,7 @@ dependencies = [ [[package]] name = "reqwest_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "anyhow", "bytes", @@ -7445,7 +7465,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "async-task", "backtrace", @@ -8168,7 +8188,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sum_tree" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "heapless 0.9.3", "log", @@ -9517,7 +9537,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "util_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "perf", "quote", @@ -9533,9 +9553,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.24.0" +version = "1.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -9744,9 +9764,9 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016ccf01d1c58b6f8999612813e17c9b2390f7d70671428869913310f83f54b8" +checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078" dependencies = [ "cc", "downcast-rs", @@ -11301,7 +11321,7 @@ checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" [[package]] name = "zlog" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "anyhow", "chrono", @@ -11318,7 +11338,7 @@ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "ztracing" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" dependencies = [ "tracing", "tracing-subscriber", @@ -11329,7 +11349,7 @@ dependencies = [ [[package]] name = "ztracing_macro" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#24e25552b1259d56a6fdd7956a419ed9e8a1a25e" +source = "git+https://github.com/zed-industries/zed#8968bf78084f30809aa2ce1574a3be68ed02a513" [[package]] name = "zune-core" @@ -11401,9 +11421,9 @@ dependencies = [ [[package]] name = "zvariant_utils" -version = "4.0.0" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629d80ece222cad20fe0e8741be493c4ab166acf3b85341bdc2cdbcfd8f3c2d6" +checksum = "6b84ebb462416c27cdb97f2e7f5f0ccc844da1fe2ecc7121e1b690b41318bf42" dependencies = [ "proc-macro2", "quote", diff --git a/crates/signed_core/src/model.rs b/crates/signed_core/src/model.rs index 60ada21..633f5f8 100644 --- a/crates/signed_core/src/model.rs +++ b/crates/signed_core/src/model.rs @@ -78,6 +78,13 @@ impl Announcement { pub fn addr(&self) -> crate::RepoAddr { crate::repo_addr(self.owner, self.id.clone()) } + + /// The description of the repository, or a default if none is provided. + pub fn description(&self) -> SharedString { + self.description + .clone() + .unwrap_or(SharedString::from("No description")) + } } #[cfg(test)] diff --git a/crates/workspace/src/views/repo_detail/browser.rs b/crates/workspace/src/views/repo_detail/browser.rs index 6720ed1..b6b9f2b 100644 --- a/crates/workspace/src/views/repo_detail/browser.rs +++ b/crates/workspace/src/views/repo_detail/browser.rs @@ -6,7 +6,7 @@ use gpui::prelude::*; use gpui::{AnyElement, Context, Entity, SharedString, WeakEntity, Window, div, px}; use gpui_component::button::{Button, ButtonVariants}; -use gpui_component::input::{Editor, EditorState, TabSize}; +use gpui_component::input::{Editor, EditorState}; use gpui_component::list::ListItem; use gpui_component::spinner::Spinner; use gpui_component::text::{TextView, TextViewState}; @@ -281,12 +281,9 @@ impl RepoDetailView { ) { let language = code_language(path.as_ref()).unwrap_or("text"); let state = cx.new(|cx| { - EditorState::new(language, window, cx) + EditorState::new(window, cx) + .language(language) .default_value(text) - .tab_size(TabSize { - tab_size: 4, - hard_tabs: false, - }) .line_number(true) .folding(true) }); diff --git a/crates/workspace/src/views/repo_detail/mod.rs b/crates/workspace/src/views/repo_detail/mod.rs index 120ec77..99fcf54 100644 --- a/crates/workspace/src/views/repo_detail/mod.rs +++ b/crates/workspace/src/views/repo_detail/mod.rs @@ -8,16 +8,15 @@ use assets::CustomIconName; use gix::Repository; use gpui::prelude::*; use gpui::{ - AnyElement, App, ClipboardItem, Context, Entity, EventEmitter, FocusHandle, Focusable, Pixels, - Render, SharedString, Size, Subscription, Task, WeakEntity, Window, div, px, size, + AnyElement, App, Context, Entity, EventEmitter, FocusHandle, Focusable, Pixels, Render, + SharedString, Size, Subscription, Task, WeakEntity, Window, div, px, size, }; use gpui_component::avatar::{Avatar, AvatarGroup}; -use gpui_component::button::{Button, ButtonVariants, DropdownButton}; +use gpui_component::button::{Button, ButtonVariants}; use gpui_component::combobox::{ Caret, Combobox, ComboboxEvent, ComboboxState, ComboboxTriggerContext, }; use gpui_component::dock::{DockArea, DockPlacement, Panel, PanelEvent}; -use gpui_component::menu::PopupMenuItem; use gpui_component::searchable_list::SearchableVec; use gpui_component::tab::{Tab, TabBar}; use gpui_component::tag::Tag; @@ -75,13 +74,8 @@ pub struct RepoDetailView { /// Snapshot taken at open time, shown until the store's first refresh /// completes (and as a fallback while the store has no announcement). initial: Announcement, - /// Latest announcement from the store, cached so `render` (which runs - /// every frame) does not re-read and re-clone the store's copy. - announcement: Option, - /// Relay/web URLs of [`Self::announcement`] as display strings, for the - /// header dropdowns; `Rc` so the menu builders clone cheaply per frame. - relays: Rc>, - web: Rc>, + /// Per-repository nostr store (announcement, issues, PRs, statuses). + store: Entity, /// File explorer state (worktree of the local clone). tree_state: Entity, /// Root of the local clone, for reading files on demand. @@ -135,8 +129,7 @@ pub struct RepoDetailView { /// In-flight tasks; finished tasks are pruned on every push, so the vec /// stays bounded by the number of concurrent loads. tasks: Vec>>, - /// Subscriptions keeping the selectors' confirm events and the store's - /// refreshes alive. + /// Subscriptions keeping the selectors' confirm events alive. _subscriptions: Vec, } @@ -170,36 +163,7 @@ impl RepoDetailView { .searchable(true) }); - // Cache the announcement for the header: the store only changes it - // during debounced refreshes, but `render` runs every frame. The - // observe subscription owns the store for the view's lifetime. - let subscription = cx.observe(&store, |this, store, cx| { - let fresh = store.read(cx).announcement.clone(); - if this.announcement == fresh { - return; - } - this.announcement = fresh; - // The header falls back to the open-time snapshot while the - // store has no announcement; keep its dropdown lists in sync. - let announcement = this.announcement.as_ref().unwrap_or(&this.initial); - this.relays = Rc::new( - announcement - .relays - .iter() - .map(|relay| relay.to_string().into()) - .collect(), - ); - this.web = Rc::new( - announcement - .web - .iter() - .map(|url| url.to_string().into()) - .collect(), - ); - cx.notify(); - }); - - let mut subscriptions = vec![ + let subscriptions = vec![ cx.subscribe_in(&branch_select, window, |this, _state, event, window, cx| { // `Change` fires only when the selection actually changed // (picking the already-selected branch emits nothing), so a @@ -218,36 +182,16 @@ impl RepoDetailView { } }), ]; - subscriptions.push(subscription); // Defer loading the repository until the window is ready. cx.defer_in(window, |this, window, cx| { this.load_repo(window, cx); }); - // Header dropdowns of the open-time snapshot, until the store's - // first refresh replaces them. - let relays = Rc::new( - initial - .relays - .iter() - .map(|relay| relay.to_string().into()) - .collect(), - ); - let web = Rc::new( - initial - .web - .iter() - .map(|url| url.to_string().into()) - .collect(), - ); - Self { initial, dock_area, - announcement: None, - relays, - web, + store, tree_state, worktree: None, md: None, @@ -693,7 +637,7 @@ impl RepoDetailView { }; // Same display name as the repo detail panel's title. - let repo_name = self.display_name(); + let repo_name = self.display_name(cx); let panel = cx.new(|cx| CommitDiffView::new(worktree, repo_name, commit_id.into(), window, cx)); @@ -703,6 +647,18 @@ impl RepoDetailView { }); } + /// Open the issue detail view (not implemented yet). + fn open_issue_detail(&mut self, _window: &mut Window, _cx: &mut Context) { + // TODO: open a per-issue detail view in the dock area, like + // [`Self::open_commit_diff`]. + } + + /// Open the pull request detail view (not implemented yet). + fn open_pull_request_detail(&mut self, _window: &mut Window, _cx: &mut Context) { + // TODO: open a per-PR detail view in the dock area, like + // [`Self::open_commit_diff`]. + } + /// Check out `name` (a branch or tag picked in the header) and refresh /// the explorer once the switch completes. fn switch_ref( @@ -930,164 +886,31 @@ impl RepoDetailView { } /// The latest announcement from the store, or the open-time snapshot. - fn announcement(&self) -> &Announcement { - self.announcement.as_ref().unwrap_or(&self.initial) + fn announcement<'a>(&'a self, cx: &'a App) -> &'a Announcement { + self.store + .read(cx) + .announcement + .as_ref() + .unwrap_or(&self.initial) } /// Display name: the announcement's name, or the ID if no name is set. - fn display_name(&self) -> SharedString { - let announcement = self.announcement(); + fn display_name(&self, cx: &App) -> SharedString { + let announcement = self.announcement(cx); announcement .name .clone() .unwrap_or_else(|| SharedString::from(announcement.id.clone())) } - /// Horizontal list of everyone who maintains the repository: the owner - /// shown in full, and any additional maintainers as a compact overlapping avatar group. - fn render_maintainers(&self, cx: &mut Context) -> AnyElement { - let announcement = self.announcement(); - let profile_store = ProfileStore::global(cx); - - let mut seen = HashSet::new(); - let rest: Vec<_> = announcement - .maintainers - .iter() - .copied() - .filter(|key| *key != announcement.owner && seen.insert(*key)) - .collect(); - - let owner = profile_store.read(cx).get(&announcement.owner); - let owner_name = owner.name(); - let owner_picture = owner.picture(); - - h_flex() - .w_full() - .gap_3() - .items_center() - .flex_wrap() - .child( - h_flex() - .gap_1() - .items_center() - .child( - Avatar::new() - .name(owner_name.clone()) - .when_some(owner_picture, |this, url| this.src(url)) - .small(), - ) - .child(div().text_xs().whitespace_nowrap().child(owner_name)), - ) - .when(!rest.is_empty(), |this| { - this.child(AvatarGroup::new().small().limit(5).ellipsis().children( - rest.into_iter().map(|key| { - let profile = profile_store.read(cx).get(&key); - Avatar::new() - .name(profile.name()) - .when_some(profile.picture(), |this, url| this.src(url)) - }), - )) - }) - .into_any_element() - } -} - -impl Panel for RepoDetailView { - fn panel_name(&self) -> &'static str { - "repo_detail" - } - - fn title(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { - self.display_name() - } -} - -/// Read the worktree state of `repo` (no network): entries, README, refs -/// and HEAD commit. The tree is built off the main thread; the seeds are -/// plain owned strings and convert to `TreeItem`s (which hold `Rc` state) -/// on the main thread. -fn load_repo_data(repo: &Repository) -> Result { - let entries = signed_git::worktree_entries(repo)?; - let tree = build_tree_items(&entries); - let readme_path = signed_git::find_readme(repo)?; - let readme = match &readme_path { - Some(path) => signed_git::worktree_read(repo, path)?, - None => None, - }; - let worktree = repo.workdir().map(Path::to_path_buf); - // Ref listing is auxiliary UI: a broken ref must not prevent the - // explorer from loading, so failures degrade to empty selectors. - let (branches, tags, current_branch) = match &worktree { - Some(_) => ( - signed_git::repo_branches(repo).unwrap_or_default(), - signed_git::repo_tags(repo).unwrap_or_default(), - signed_git::current_branch(repo).unwrap_or(None), - ), - None => (Vec::new(), Vec::new(), None), - }; - let head_commit = signed_git::head_commit(repo).unwrap_or(None); - Ok(RepoData { - tree, - readme_path, - readme, - worktree, - branches, - tags, - current_branch, - head_commit, - }) -} - -impl EventEmitter for RepoDetailView {} - -impl Focusable for RepoDetailView { - fn focus_handle(&self, _cx: &App) -> FocusHandle { - self.focus_handle.clone() - } -} - -impl Render for RepoDetailView { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - let tree_state = self.tree_state.clone(); - let view = cx.entity().downgrade(); - - let pane_title = self - .selected_file - .clone() - .or_else(|| self.readme_name.clone()) - .unwrap_or_else(|| "Overview".into()); - - v_flex() - .id("repo") - .size_full() - .child(self.render_header(cx)) - .child(match self.active_tab { - 0 => h_flex() - .flex_1() - .w_full() - .overflow_hidden() - .child(Self::render_tree_column(tree_state, view, cx)) - .child(self.render_content_column(pane_title, cx)) - .into_any_element(), - _ => self.render_commits_tab(cx), - }) - } -} - -impl RepoDetailView { - /// Header: repository name and description, relay/web/clone buttons, the - /// Files/Commits tab bar and the branch/tag selectors with the - /// latest-commit button. fn render_header(&self, cx: &mut Context) -> AnyElement { - let announcement = self.announcement(); - let relays = self.relays.clone(); - let web = self.web.clone(); + let store = self.store.read(cx); + let announcement = store.announcement.as_ref().unwrap_or(&self.initial); + let issue_count = store.issues.len(); + let pull_request_count = store.pull_requests.len(); - let name = self.display_name(); - let description = announcement - .description - .clone() - .unwrap_or(SharedString::from("No description")); + let name = self.display_name(cx); + let description = announcement.description(); let commits_count = self.all_commits.as_ref().map(|list| list.total); let worktree_empty = self.switching_ref || self.worktree.is_none(); @@ -1140,58 +963,36 @@ impl RepoDetailView { .gap_2() .justify_end() .child( - DropdownButton::new("relays") - .button( - Button::new("relay-trigger") - .label(format!("{} relays", relays.len())) - .small() - .ghost(), + Button::new("issues") + .child( + h_flex() + .gap_2() + .text_sm() + .child(SharedString::from("Issues")) + .child(Tag::new().xsmall().child(SharedString::from( + issue_count.to_string(), + ))), ) - .dropdown_menu(move |menu, _window, _cx| { - let mut menu = menu; - if relays.is_empty() { - return menu.item( - PopupMenuItem::new("No relays").disabled(true), - ); - } - for relay in relays.iter() { - let url = relay.to_string(); - menu = menu.item( - PopupMenuItem::new(url.clone()).on_click( - move |_, _, cx| { - cx.write_to_clipboard( - ClipboardItem::new_string(url.clone()), - ); - }, - ), - ); - } - menu - }), + .outline() + .on_click(cx.listener(|this, _event, window, cx| { + this.open_issue_detail(window, cx); + })), ) .child( - DropdownButton::new("web") - .button( - Button::new("web-trigger") - .label("Websites") - .small() - .ghost(), + Button::new("prs") + .child( + h_flex() + .gap_2() + .text_sm() + .child(SharedString::from("Pull Requests")) + .child(Tag::new().xsmall().child(SharedString::from( + pull_request_count.to_string(), + ))), ) - .dropdown_menu(move |menu, _window, _cx| { - let mut menu = menu; - if web.is_empty() { - return menu - .item(PopupMenuItem::new("No web").disabled(true)); - } - for url in web.iter() { - let href = url.to_string(); - menu = menu.item( - PopupMenuItem::new(href.clone()) - .on_click(move |_, _, cx| cx.open_url(&href)), - ); - } - menu - }), + .outline() + .on_click(cx.listener(|this, _event, window, cx| { + this.open_pull_request_detail(window, cx); + })), ) .child( Button::new("link") @@ -1306,4 +1107,135 @@ impl RepoDetailView { ) .into_any_element() } + + /// Horizontal list of everyone who maintains the repository: the owner + /// shown in full, and any additional maintainers as a compact overlapping avatar group. + fn render_maintainers(&self, cx: &mut Context) -> AnyElement { + let announcement = self.announcement(cx); + let profile_store = ProfileStore::global(cx); + + let mut seen = HashSet::new(); + let rest: Vec<_> = announcement + .maintainers + .iter() + .copied() + .filter(|key| *key != announcement.owner && seen.insert(*key)) + .collect(); + + let owner = profile_store.read(cx).get(&announcement.owner); + let owner_name = owner.name(); + let owner_picture = owner.picture(); + + h_flex() + .w_full() + .gap_3() + .items_center() + .flex_wrap() + .child( + h_flex() + .gap_1() + .items_center() + .child( + Avatar::new() + .name(owner_name.clone()) + .when_some(owner_picture, |this, url| this.src(url)) + .small(), + ) + .child(div().text_xs().whitespace_nowrap().child(owner_name)), + ) + .when(!rest.is_empty(), |this| { + this.child(AvatarGroup::new().small().limit(5).ellipsis().children( + rest.into_iter().map(|key| { + let profile = profile_store.read(cx).get(&key); + Avatar::new() + .name(profile.name()) + .when_some(profile.picture(), |this, url| this.src(url)) + }), + )) + }) + .into_any_element() + } +} + +impl Panel for RepoDetailView { + fn panel_name(&self) -> &'static str { + "repo_detail" + } + + fn title(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + self.display_name(cx) + } +} + +impl EventEmitter for RepoDetailView {} + +impl Focusable for RepoDetailView { + fn focus_handle(&self, _cx: &App) -> FocusHandle { + self.focus_handle.clone() + } +} + +impl Render for RepoDetailView { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let tree_state = self.tree_state.clone(); + let view = cx.entity().downgrade(); + + let pane_title = self + .selected_file + .clone() + .or_else(|| self.readme_name.clone()) + .unwrap_or_else(|| "Overview".into()); + + v_flex() + .id("repo") + .size_full() + .child(self.render_header(cx)) + .child(match self.active_tab { + 0 => h_flex() + .flex_1() + .w_full() + .overflow_hidden() + .child(Self::render_tree_column(tree_state, view, cx)) + .child(self.render_content_column(pane_title, cx)) + .into_any_element(), + _ => self.render_commits_tab(cx), + }) + } +} + +/// Read the worktree state of `repo` (no network): entries, README, refs +/// and HEAD commit. The tree is built off the main thread; the seeds are +/// plain owned strings and convert to `TreeItem`s (which hold `Rc` state) +/// on the main thread. +fn load_repo_data(repo: &Repository) -> Result { + let entries = signed_git::worktree_entries(repo)?; + let tree = build_tree_items(&entries); + let readme_path = signed_git::find_readme(repo)?; + let readme = match &readme_path { + Some(path) => signed_git::worktree_read(repo, path)?, + None => None, + }; + let worktree = repo.workdir().map(Path::to_path_buf); + // Ref listing is auxiliary UI: a broken ref must not prevent the + // explorer from loading, so failures degrade to empty selectors. + let (branches, tags, current_branch) = match &worktree { + Some(_) => ( + signed_git::repo_branches(repo).unwrap_or_default(), + signed_git::repo_tags(repo).unwrap_or_default(), + signed_git::current_branch(repo).unwrap_or(None), + ), + None => (Vec::new(), Vec::new(), None), + }; + let head_commit = signed_git::head_commit(repo).unwrap_or(None); + + Ok(RepoData { + tree, + readme_path, + readme, + worktree, + branches, + tags, + current_branch, + head_commit, + }) }