add issues and prs button

This commit is contained in:
2026-08-17 09:13:04 +07:00
parent 2d281dfbbb
commit 9f484ea98d
4 changed files with 275 additions and 319 deletions
Generated
+73 -53
View File
@@ -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",
+7
View File
@@ -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)]
@@ -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)
});
+192 -260
View File
@@ -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<Announcement>,
/// Relay/web URLs of [`Self::announcement`] as display strings, for the
/// header dropdowns; `Rc` so the menu builders clone cheaply per frame.
relays: Rc<Vec<SharedString>>,
web: Rc<Vec<SharedString>>,
/// Per-repository nostr store (announcement, issues, PRs, statuses).
store: Entity<RepoStore>,
/// File explorer state (worktree of the local clone).
tree_state: Entity<TreeState>,
/// 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<Task<Result<(), Error>>>,
/// Subscriptions keeping the selectors' confirm events and the store's
/// refreshes alive.
/// Subscriptions keeping the selectors' confirm events alive.
_subscriptions: Vec<Subscription>,
}
@@ -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<Self>) {
// 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<Self>) {
// 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<Self>) -> 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<Self>) -> 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<RepoData, Error> {
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<PanelEvent> 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<Self>) -> 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<Self>) -> 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<Self>) -> 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<Self>) -> impl IntoElement {
self.display_name(cx)
}
}
impl EventEmitter<PanelEvent> 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<Self>) -> 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<RepoData, Error> {
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,
})
}