Compare commits
2
Commits
8be65b7904
...
9bdc3daa7e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bdc3daa7e | ||
|
|
052c30d12b |
Generated
+38
-37
@@ -1268,7 +1268,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collections"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"gpui_util",
|
||||
"indexmap",
|
||||
@@ -1734,7 +1734,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "derive_refineable"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1887,9 +1887,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.17.0"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
|
||||
checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
|
||||
|
||||
[[package]]
|
||||
name = "embed-resource"
|
||||
@@ -3494,7 +3494,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gpui"
|
||||
version = "0.2.2"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"anyhow",
|
||||
@@ -3567,7 +3567,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gpui-base"
|
||||
version = "0.5.2"
|
||||
source = "git+https://github.com/longbridge/gpui-component#270cf3ce8cf6f4cdac28db5c30dd92631849ed6e"
|
||||
source = "git+https://github.com/longbridge/gpui-component#14359a0cdd00657eb68fa946bfd99cd83c26e002"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -3599,7 +3599,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gpui-component"
|
||||
version = "0.5.2"
|
||||
source = "git+https://github.com/longbridge/gpui-component#270cf3ce8cf6f4cdac28db5c30dd92631849ed6e"
|
||||
source = "git+https://github.com/longbridge/gpui-component#14359a0cdd00657eb68fa946bfd99cd83c26e002"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -3679,7 +3679,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gpui-component-assets"
|
||||
version = "0.5.1"
|
||||
source = "git+https://github.com/longbridge/gpui-component#270cf3ce8cf6f4cdac28db5c30dd92631849ed6e"
|
||||
source = "git+https://github.com/longbridge/gpui-component#14359a0cdd00657eb68fa946bfd99cd83c26e002"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gpui",
|
||||
@@ -3693,7 +3693,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gpui-component-macros"
|
||||
version = "0.5.1"
|
||||
source = "git+https://github.com/longbridge/gpui-component#270cf3ce8cf6f4cdac28db5c30dd92631849ed6e"
|
||||
source = "git+https://github.com/longbridge/gpui-component#14359a0cdd00657eb68fa946bfd99cd83c26e002"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3703,7 +3703,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gpui_apple"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"block",
|
||||
@@ -3726,7 +3726,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gpui_linux"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_unix",
|
||||
@@ -3772,7 +3772,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gpui_macos"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_macos",
|
||||
@@ -3818,7 +3818,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gpui_macros"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
@@ -3829,7 +3829,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gpui_platform"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"gpui",
|
||||
@@ -3842,7 +3842,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gpui_shared_string"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -3852,7 +3852,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gpui_util"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"log",
|
||||
@@ -3862,7 +3862,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gpui_web"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"console_error_panic_hook",
|
||||
@@ -3886,7 +3886,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gpui_wgpu"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytemuck",
|
||||
@@ -3912,7 +3912,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gpui_windows"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_windows",
|
||||
@@ -3949,9 +3949,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.17"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f877e75f39e9827ec50a572dd592684ac28c029578726c85f1b2aa6ab807449"
|
||||
checksum = "839c0e8a181239723652be9062bb56ca5bf5f64011f73b623f6f4fc59086a228"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
@@ -4239,7 +4239,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "http_client"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-compression",
|
||||
@@ -4259,7 +4259,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "http_client_tls"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"rustls-platform-verifier 0.5.3",
|
||||
@@ -4434,9 +4434,9 @@ checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "2.3.0"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92a7ed671a6aad807a8651a2e1782a6598fda9ce5185dd8158549e95a91c6428"
|
||||
checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locale_core",
|
||||
@@ -5235,7 +5235,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "media"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bindgen",
|
||||
@@ -6282,7 +6282,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
[[package]]
|
||||
name = "perf"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"collections",
|
||||
"serde",
|
||||
@@ -7079,7 +7079,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "refineable"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"derive_refineable",
|
||||
]
|
||||
@@ -7162,7 +7162,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "reqwest_client"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -7574,7 +7574,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "scheduler"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"async-task",
|
||||
"backtrace",
|
||||
@@ -8301,7 +8301,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
[[package]]
|
||||
name = "sum_tree"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"heapless 0.9.3",
|
||||
"log",
|
||||
@@ -9674,7 +9674,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
[[package]]
|
||||
name = "util_macros"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"perf",
|
||||
"quote",
|
||||
@@ -10955,6 +10955,7 @@ dependencies = [
|
||||
"futures",
|
||||
"gix",
|
||||
"gpui",
|
||||
"gpui-base",
|
||||
"gpui-component",
|
||||
"log",
|
||||
"nostr",
|
||||
@@ -11453,9 +11454,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.11.5"
|
||||
version = "0.11.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f212a141d820099d57ffafb9569be9617a6f27d3dc881fbee8fb56642f917a9"
|
||||
checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -11471,7 +11472,7 @@ checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12"
|
||||
[[package]]
|
||||
name = "zlog"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -11488,7 +11489,7 @@ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
[[package]]
|
||||
name = "ztracing"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
@@ -11499,7 +11500,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ztracing_macro"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#1b04e4caf01e376624fb514ef85b0e6d8ee5d930"
|
||||
source = "git+https://github.com/zed-industries/zed#ec18126b1dbd32b089e51d7edee1e20b3bd53637"
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M16.2426 12.0005H7.75736M12 16.2431V7.75781M21.25 12C21.25 17.1086 17.1086 21.25 12 21.25C6.89137 21.25 2.75 17.1086 2.75 12C2.75 6.89137 6.89137 2.75 12 2.75C17.1086 2.75 21.25 6.89137 21.25 12Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 352 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M21.25 12C21.25 17.1086 17.1086 21.25 12 21.25C6.89137 21.25 2.75 17.1086 2.75 12C2.75 6.89137 6.89137 2.75 12 2.75C17.1086 2.75 21.25 6.89137 21.25 12Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="3 4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 356 B |
@@ -71,10 +71,12 @@ impl Assets {
|
||||
}
|
||||
|
||||
pub enum CustomIconName {
|
||||
CirclePlus,
|
||||
Unlock,
|
||||
Filter,
|
||||
GlobalOn,
|
||||
GlobalOff,
|
||||
GitIssueDone,
|
||||
GitIssueOpen,
|
||||
GitIssueClosed,
|
||||
GitIssueOngoing,
|
||||
@@ -90,10 +92,12 @@ pub enum CustomIconName {
|
||||
impl IconNamed for CustomIconName {
|
||||
fn path(self) -> gpui::SharedString {
|
||||
match self {
|
||||
CustomIconName::CirclePlus => "icons/circle-plus.svg",
|
||||
CustomIconName::Unlock => "icons/unlock.svg",
|
||||
CustomIconName::Filter => "icons/filter.svg",
|
||||
CustomIconName::GlobalOn => "icons/global-on.svg",
|
||||
CustomIconName::GlobalOff => "icons/global-off.svg",
|
||||
CustomIconName::GitIssueDone => "icons/git-issue-done.svg",
|
||||
CustomIconName::GitIssueOpen => "icons/git-issue-open.svg",
|
||||
CustomIconName::GitIssueClosed => "icons/git-issue-close.svg",
|
||||
CustomIconName::GitIssueOngoing => "icons/git-issue-ongoing.svg",
|
||||
|
||||
@@ -949,7 +949,9 @@ impl DockArea {
|
||||
.map(|view| view.entity_id());
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<DockEvent> for DockArea {}
|
||||
|
||||
impl Render for DockArea {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let view = cx.entity().clone();
|
||||
|
||||
@@ -147,11 +147,6 @@ pub trait Panel: EventEmitter<PanelEvent> + Render + Focusable {
|
||||
fn dump(&self, cx: &App) -> PanelState {
|
||||
PanelState::new(self)
|
||||
}
|
||||
|
||||
/// Whether the panel has inner padding when the panel is in the tabs layout, default is `true`.
|
||||
fn inner_padding(&self, cx: &App) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// The PanelView trait used to define the panel view.
|
||||
@@ -174,7 +169,6 @@ pub trait PanelView: 'static + Send + Sync {
|
||||
fn view(&self) -> AnyView;
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle;
|
||||
fn dump(&self, cx: &App) -> PanelState;
|
||||
fn inner_padding(&self, cx: &App) -> bool;
|
||||
}
|
||||
|
||||
impl<T: Panel> PanelView for Entity<T> {
|
||||
@@ -252,10 +246,6 @@ impl<T: Panel> PanelView for Entity<T> {
|
||||
fn dump(&self, cx: &App) -> PanelState {
|
||||
self.read(cx).dump(cx)
|
||||
}
|
||||
|
||||
fn inner_padding(&self, cx: &App) -> bool {
|
||||
self.read(cx).inner_padding(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&dyn PanelView> for AnyView {
|
||||
|
||||
@@ -230,11 +230,6 @@ impl Panel for TabPanel {
|
||||
}
|
||||
state
|
||||
}
|
||||
|
||||
fn inner_padding(&self, cx: &App) -> bool {
|
||||
self.active_panel(cx)
|
||||
.is_none_or(|panel| panel.inner_padding(cx))
|
||||
}
|
||||
}
|
||||
|
||||
/// State used to move the window when the title bar area is dragged.
|
||||
@@ -965,8 +960,6 @@ impl TabPanel {
|
||||
return Empty {}.into_any_element();
|
||||
};
|
||||
|
||||
let has_inner_padding = self.inner_padding(cx);
|
||||
|
||||
let placeholder = self.drop_placeholder_animation;
|
||||
let placeholder_animation_name = self.drop_placeholder_animation_name.clone();
|
||||
|
||||
@@ -974,7 +967,6 @@ impl TabPanel {
|
||||
.id("active-panel")
|
||||
.group("")
|
||||
.flex_1()
|
||||
.when(has_inner_padding, |this| this.pt_2())
|
||||
.child(
|
||||
div()
|
||||
.id("tab-content")
|
||||
|
||||
@@ -3,11 +3,12 @@ use nostr::prelude::*;
|
||||
use crate::RepoAddr;
|
||||
|
||||
/// Kinds that make up the activity of a repository.
|
||||
pub const ACTIVITY_KINDS: [Kind; 8] = [
|
||||
pub const ACTIVITY_KINDS: [Kind; 9] = [
|
||||
Kind::GitPatch,
|
||||
Kind::GitPullRequest,
|
||||
Kind::GitPullRequestUpdate,
|
||||
Kind::GitIssue,
|
||||
Kind::Comment,
|
||||
Kind::GitStatusOpen,
|
||||
Kind::GitStatusApplied,
|
||||
Kind::GitStatusClosed,
|
||||
@@ -30,7 +31,8 @@ pub fn state(addr: &RepoAddr) -> Filter {
|
||||
.identifier(addr.identifier.clone())
|
||||
}
|
||||
|
||||
/// All NIP-34 activity addressed to a repository (`#a` tag).
|
||||
/// All NIP-34 activity addressed to a repository (`#a` tag): issues, PRs,
|
||||
/// patches, statuses and comments (kind 1111).
|
||||
///
|
||||
/// Note: the `a` tag on status events is optional per NIP-34, so statuses
|
||||
/// published without it won't be matched here.
|
||||
|
||||
@@ -11,8 +11,9 @@ use crate::backend::{Backend, BackendEvent};
|
||||
/// events (e.g. per-event `NostrUpdate`s) collapse into one query.
|
||||
const REFRESH_DEBOUNCE: Duration = Duration::from_millis(300);
|
||||
|
||||
/// Per-repository store: announcement, state, issues, patches, PRs and
|
||||
/// their resolved statuses. Always derived from the local database.
|
||||
/// Per-repository store: announcement, state, issues, patches, PRs,
|
||||
/// comments and their resolved statuses. Always derived from the local
|
||||
/// database.
|
||||
pub struct RepoStore {
|
||||
addr: RepoAddr,
|
||||
pub announcement: Option<Announcement>,
|
||||
@@ -23,6 +24,8 @@ pub struct RepoStore {
|
||||
pub issues: Vec<Event>,
|
||||
pub patches: Vec<Event>,
|
||||
pub pull_requests: Vec<Event>,
|
||||
/// Comments on issues / PRs, oldest first.
|
||||
pub comments: Vec<Event>,
|
||||
statuses: Vec<Event>,
|
||||
/// Error of the last action initiated from this store, if any.
|
||||
pub last_error: Option<String>,
|
||||
@@ -73,6 +76,7 @@ impl RepoStore {
|
||||
issues: Vec::new(),
|
||||
patches: Vec::new(),
|
||||
pull_requests: Vec::new(),
|
||||
comments: Vec::new(),
|
||||
statuses: Vec::new(),
|
||||
last_error: None,
|
||||
refreshing: false,
|
||||
@@ -172,8 +176,8 @@ impl RepoStore {
|
||||
let all_states = states.into_iter().filter(|e| !deletions.is_deleted(e));
|
||||
let state = latest(all_states).map(|state| parse_state(&state));
|
||||
|
||||
let (mut issues, mut patches, mut pull_requests, mut statuses) =
|
||||
(Vec::new(), Vec::new(), Vec::new(), Vec::new());
|
||||
let (mut issues, mut patches, mut pull_requests, mut statuses, mut comments) =
|
||||
(Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new());
|
||||
|
||||
for event in activity {
|
||||
if deletions.is_deleted(&event) {
|
||||
@@ -183,6 +187,7 @@ impl RepoStore {
|
||||
Kind::GitIssue => issues.push(event),
|
||||
Kind::GitPatch => patches.push(event),
|
||||
Kind::GitPullRequest | Kind::GitPullRequestUpdate => pull_requests.push(event),
|
||||
Kind::Comment => comments.push(event),
|
||||
kind if RepoStatus::from_kind(kind).is_some() => statuses.push(event),
|
||||
_ => {}
|
||||
}
|
||||
@@ -191,6 +196,7 @@ impl RepoStore {
|
||||
sort_newest_first(&mut issues);
|
||||
sort_newest_first(&mut patches);
|
||||
sort_newest_first(&mut pull_requests);
|
||||
sort_oldest_first(&mut comments);
|
||||
|
||||
Ok::<_, Error>((
|
||||
announcement,
|
||||
@@ -199,21 +205,23 @@ impl RepoStore {
|
||||
patches,
|
||||
pull_requests,
|
||||
statuses,
|
||||
comments,
|
||||
))
|
||||
});
|
||||
|
||||
self.tasks.retain(|task| !task.is_ready());
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
let (announcement, state, issues, patches, pull_requests, statuses) = match work.await {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
return this.update(cx, |this, cx| {
|
||||
this.refreshing = false;
|
||||
this.last_error = Some(e.to_string());
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
};
|
||||
let (announcement, state, issues, patches, pull_requests, statuses, comments) =
|
||||
match work.await {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
return this.update(cx, |this, cx| {
|
||||
this.refreshing = false;
|
||||
this.last_error = Some(e.to_string());
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let again = this.update(cx, |this, cx| {
|
||||
this.announcement = announcement;
|
||||
@@ -226,6 +234,7 @@ impl RepoStore {
|
||||
this.issues = issues;
|
||||
this.patches = patches;
|
||||
this.pull_requests = pull_requests;
|
||||
this.comments = comments;
|
||||
this.statuses = statuses;
|
||||
|
||||
cx.notify();
|
||||
@@ -297,6 +306,28 @@ impl RepoStore {
|
||||
self.send(builder, cx);
|
||||
}
|
||||
|
||||
/// Comments on a root event (issue / PR), oldest first.
|
||||
pub fn comments_of(&self, root: &EventId) -> impl Iterator<Item = &Event> {
|
||||
self.comments
|
||||
.iter()
|
||||
.filter(move |e| signed_core::references_root(e, root))
|
||||
}
|
||||
|
||||
/// Comment on a root event (issue / PR) per NIP-34 (kind 1111).
|
||||
pub fn comment(&mut self, root: &Event, content: String, cx: &mut Context<Self>) {
|
||||
let Ok(root_ref) = Tag::parse(["e", &root.id.to_hex(), "", "root"]) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let builder = EventBuilder::new(Kind::Comment, content).tags([
|
||||
root_ref,
|
||||
Tag::public_key(root.pubkey),
|
||||
Tag::coordinate(self.addr.clone(), None),
|
||||
]);
|
||||
|
||||
self.send(builder, cx);
|
||||
}
|
||||
|
||||
/// Open a pull request on this repository: a root PR event whose content
|
||||
/// is the `git format-patch` output of the proposed changes.
|
||||
///
|
||||
@@ -390,6 +421,10 @@ fn sort_newest_first(events: &mut [Event]) {
|
||||
events.sort_by_key(|e| std::cmp::Reverse(e.created_at));
|
||||
}
|
||||
|
||||
fn sort_oldest_first(events: &mut [Event]) {
|
||||
events.sort_by_key(|e| e.created_at);
|
||||
}
|
||||
|
||||
/// The proposed commit of a `git format-patch` output: the `From <commit>`
|
||||
/// header on its first line.
|
||||
fn patch_current_commit(patch: &str) -> Option<&str> {
|
||||
|
||||
@@ -14,6 +14,7 @@ utils = { path = "../utils" }
|
||||
|
||||
gpui.workspace = true
|
||||
gpui-component.workspace = true
|
||||
gpui-base.workspace = true
|
||||
gix.workspace = true
|
||||
nostr.workspace = true
|
||||
|
||||
|
||||
@@ -262,6 +262,7 @@ impl RepoDetailView {
|
||||
.selectable(true)
|
||||
.scrollable(true)
|
||||
.p_4()
|
||||
.text_xs()
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
@@ -305,7 +306,7 @@ impl RepoDetailView {
|
||||
.bordered(false)
|
||||
.rounded_none()
|
||||
.h_full()
|
||||
.text_sm()
|
||||
.text_xs()
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,14 @@ use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Error;
|
||||
use assets::CustomIconName;
|
||||
use gpui::prelude::*;
|
||||
use gpui::{AnyElement, App, Task, Window, div, px};
|
||||
use gpui_component::list::ListItem;
|
||||
use gpui_component::tooltip::Tooltip;
|
||||
use gpui_component::tree::{TreeEntry, TreeItem};
|
||||
use gpui_component::{ActiveTheme, Icon, IconName, Sizable, h_flex, v_flex};
|
||||
use signed_core::RepoStatus;
|
||||
|
||||
/// A `Send` file-tree node: the tree is built on a background thread and
|
||||
/// converted into [`TreeItem`]s (which hold `Rc` state, so they cannot
|
||||
@@ -227,6 +230,53 @@ pub(super) fn placeholder(message: &str, cx: &App) -> AnyElement {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// The status badge shown next to an issue or pull request: icon + colored
|
||||
/// square, with a tooltip describing the status.
|
||||
pub(super) fn status_badge(status: RepoStatus, cx: &App) -> AnyElement {
|
||||
let (icon, label, tooltip, bg, fg) = match status {
|
||||
RepoStatus::Open => (
|
||||
CustomIconName::GitIssueOpen,
|
||||
"open",
|
||||
"Issue is open",
|
||||
cx.theme().secondary,
|
||||
cx.theme().secondary_foreground,
|
||||
),
|
||||
RepoStatus::Closed => (
|
||||
CustomIconName::GitIssueClosed,
|
||||
"closed",
|
||||
"Issue is closed",
|
||||
cx.theme().danger,
|
||||
cx.theme().danger_foreground,
|
||||
),
|
||||
RepoStatus::Draft => (
|
||||
CustomIconName::GitIssueOngoing,
|
||||
"draft",
|
||||
"Issue is draft",
|
||||
cx.theme().accent,
|
||||
cx.theme().accent_foreground,
|
||||
),
|
||||
RepoStatus::Applied => (
|
||||
CustomIconName::GitIssueOpen,
|
||||
"applied",
|
||||
"Issue is completed",
|
||||
cx.theme().primary,
|
||||
cx.theme().primary_foreground,
|
||||
),
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.id(label)
|
||||
.flex_shrink_0()
|
||||
.size_7()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.rounded(cx.theme().radius)
|
||||
.bg(bg)
|
||||
.child(Icon::new(icon).small().text_color(fg))
|
||||
.tooltip(move |window, cx| Tooltip::new(tooltip).build(window, cx))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
use dock::{Panel, PanelEvent};
|
||||
use gpui::prelude::*;
|
||||
use gpui::{
|
||||
App, Context, Entity, EventEmitter, FocusHandle, Focusable, Render, SharedString, Window, div,
|
||||
px,
|
||||
};
|
||||
use gpui_component::avatar::Avatar;
|
||||
use gpui_component::button::{Button, ButtonVariants};
|
||||
use gpui_component::input::{Textarea, TextareaState};
|
||||
use gpui_component::{ActiveTheme, Sizable, StyledExt, h_flex, v_flex};
|
||||
use nostr::prelude::{Event, EventId};
|
||||
use signed_core::activity_subject;
|
||||
use signed_state::{ProfileStore, RepoStore};
|
||||
use utils::relative_time;
|
||||
|
||||
use super::helpers::{placeholder, status_badge};
|
||||
|
||||
/// Detail panel of a single issue.
|
||||
pub struct IssueDetailView {
|
||||
focus_handle: FocusHandle,
|
||||
/// Repo store holding the issues and their statuses.
|
||||
store: Entity<RepoStore>,
|
||||
issue_id: EventId,
|
||||
/// Input state of the "leave a comment" textarea.
|
||||
comment_input: Entity<TextareaState>,
|
||||
}
|
||||
|
||||
impl IssueDetailView {
|
||||
pub fn new(
|
||||
store: Entity<RepoStore>,
|
||||
issue_id: EventId,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
// Issue author avatars stay in the shared cache until the panel closes.
|
||||
crate::image_cache::clear_on_release(&cx.entity(), window, cx);
|
||||
|
||||
let comment_input =
|
||||
cx.new(|cx| TextareaState::new(window, cx).placeholder("Leave a comment…"));
|
||||
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
store,
|
||||
issue_id,
|
||||
comment_input,
|
||||
}
|
||||
}
|
||||
|
||||
fn render_comments(&mut self, id: &EventId, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let store = self.store.read(cx);
|
||||
let comments: Vec<&Event> = store.comments_of(id).collect();
|
||||
|
||||
v_flex()
|
||||
.gap_3()
|
||||
.children(comments.iter().map(|comment| {
|
||||
let profile = ProfileStore::global(cx).read(cx).get(&comment.pubkey);
|
||||
let author = profile.name();
|
||||
let picture = profile.picture();
|
||||
let age = relative_time(comment.created_at);
|
||||
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Avatar::new()
|
||||
.name(author.clone())
|
||||
.when_some(picture, |this, url| this.src(url))
|
||||
.xsmall(),
|
||||
)
|
||||
.child(author),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child(SharedString::from(age)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.child(SharedString::from(comment.content.clone())),
|
||||
)
|
||||
}))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_form(&mut self, id: &EventId, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let comment_input = self.comment_input.clone();
|
||||
let store = self.store.clone();
|
||||
let id = id.to_owned();
|
||||
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(Textarea::new(&self.comment_input).h(px(96.)))
|
||||
.child(
|
||||
h_flex().justify_end().child(
|
||||
Button::new("comment")
|
||||
.primary()
|
||||
.label("Comment")
|
||||
.tooltip("Post comment")
|
||||
.on_click(move |_event, window, cx| {
|
||||
let content = comment_input.read(cx).value().trim().to_string();
|
||||
if content.is_empty() {
|
||||
return;
|
||||
}
|
||||
let Some(root) = store
|
||||
.read(cx)
|
||||
.issues
|
||||
.iter()
|
||||
.find(|issue| issue.id == id)
|
||||
.cloned()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
store.update(cx, |store, cx| {
|
||||
store.comment(&root, content, cx);
|
||||
});
|
||||
comment_input.update(cx, |input, cx| {
|
||||
input.set_value("", window, cx);
|
||||
});
|
||||
}),
|
||||
),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
impl Panel for IssueDetailView {
|
||||
fn panel_name(&self) -> &'static str {
|
||||
"issue_detail"
|
||||
}
|
||||
|
||||
fn title(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let short_id = self
|
||||
.store
|
||||
.read(cx)
|
||||
.issues
|
||||
.iter()
|
||||
.find(|issue| issue.id == self.issue_id)
|
||||
.map(|issue| {
|
||||
let hex = issue.id.to_hex();
|
||||
SharedString::from(&hex[..8])
|
||||
})
|
||||
.unwrap_or_else(|| SharedString::from("Issue"));
|
||||
|
||||
div().text_sm().child(short_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<PanelEvent> for IssueDetailView {}
|
||||
|
||||
impl Focusable for IssueDetailView {
|
||||
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for IssueDetailView {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
// Extract everything owned first: the store borrow must end before
|
||||
// the markdown state is (re)built below.
|
||||
let (title, author, picture, status, age, issue_id, content) = {
|
||||
let store = self.store.read(cx);
|
||||
let Some(issue) = store.issues.iter().find(|issue| issue.id == self.issue_id) else {
|
||||
return placeholder("Issue not found", cx);
|
||||
};
|
||||
let profile = ProfileStore::global(cx).read(cx).get(&issue.pubkey);
|
||||
(
|
||||
activity_subject(issue),
|
||||
profile.name(),
|
||||
profile.picture(),
|
||||
store.status_of(issue),
|
||||
relative_time(issue.created_at),
|
||||
issue.id,
|
||||
issue.content.clone(),
|
||||
)
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.id("issue-detail")
|
||||
.size_full()
|
||||
.overflow_y_scroll()
|
||||
.gap_6()
|
||||
.px_4()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.items_center()
|
||||
.child(status_badge(status, cx))
|
||||
.child(div().font_semibold().child(title)),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.px_4()
|
||||
.gap_8()
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Avatar::new()
|
||||
.name(author.clone())
|
||||
.when_some(picture, |this, url| this.src(url))
|
||||
.small(),
|
||||
)
|
||||
.child(author),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child(SharedString::from("opened")),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child(SharedString::from(age)),
|
||||
),
|
||||
)
|
||||
.child(div().text_sm().child(SharedString::from(&content))),
|
||||
)
|
||||
.child(self.render_comments(&issue_id, cx))
|
||||
.child(self.render_form(&issue_id, cx)),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
@@ -3,37 +3,38 @@
|
||||
//! the header's All/Open/Closed filter.
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use assets::CustomIconName;
|
||||
use dock::{Panel, PanelEvent};
|
||||
use dock::{DockArea, DockPlacement, Panel, PanelEvent};
|
||||
use gpui::prelude::*;
|
||||
use gpui::{
|
||||
AnyElement, App, Context, Entity, EventEmitter, FocusHandle, Focusable, Pixels, Render,
|
||||
SharedString, Size, Window, div, px, size,
|
||||
SharedString, Size, WeakEntity, Window, div, px, relative, size,
|
||||
};
|
||||
use gpui_base::Button as BaseButton;
|
||||
use gpui_component::avatar::Avatar;
|
||||
use gpui_component::button::{Button, ButtonVariants};
|
||||
use gpui_component::dialog::{DialogDescription, DialogFooter, DialogHeader, DialogTitle};
|
||||
use gpui_component::form::{field, v_form};
|
||||
use gpui_component::input::{Input, InputState, Textarea, TextareaState};
|
||||
use gpui_component::scroll::Scrollbar;
|
||||
use gpui_component::tooltip::Tooltip;
|
||||
use gpui_component::{
|
||||
ActiveTheme, Icon, IconName, Selectable, Sizable, StyledExt, VirtualListScrollHandle,
|
||||
WindowExt, h_flex, v_flex, v_virtual_list,
|
||||
ActiveTheme, Icon, Sizable, VirtualListScrollHandle, WindowExt, h_flex, v_flex, v_virtual_list,
|
||||
};
|
||||
use nostr::prelude::Event;
|
||||
use nostr::prelude::{Event, EventId};
|
||||
use signed_core::{RepoStatus, activity_subject};
|
||||
use signed_state::{ProfileStore, RepoStore};
|
||||
use utils::relative_time;
|
||||
|
||||
use super::helpers::placeholder;
|
||||
use super::helpers::{placeholder, status_badge};
|
||||
use super::issue_detail::IssueDetailView;
|
||||
|
||||
/// Height of one issue row in the virtual list: 12px padding on top and
|
||||
/// bottom, a 14px title line and a 24px meta line (the small avatar is the
|
||||
/// tallest item). Gpui's default line height is phi (~1.62x), so the title
|
||||
/// line is ~22.7px; the row totals ~71px.
|
||||
const ISSUE_ROW_HEIGHT: f32 = 71.;
|
||||
/// Height of one issue row in the virtual list: 8px vertical padding
|
||||
/// (`py_2`) on top and bottom, a 32px title line (`h_8`) and a 24px meta
|
||||
/// line (`h_6`), plus the 1px bottom border; the row totals 73px. The
|
||||
/// status chip (`size_7`, 28px) is shorter than the content.
|
||||
const ISSUE_ROW_HEIGHT: f32 = 73.;
|
||||
|
||||
/// Status filter of the issues list, chosen via the header's filter buttons.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -42,7 +43,8 @@ enum IssueFilter {
|
||||
All,
|
||||
/// Issues whose resolved status is [`RepoStatus::Open`].
|
||||
Open,
|
||||
/// Issues whose resolved status is [`RepoStatus::Closed`].
|
||||
/// Issues whose resolved status is [`RepoStatus::Closed`] or
|
||||
/// [`RepoStatus::Applied`] (both are "done" states).
|
||||
Closed,
|
||||
}
|
||||
|
||||
@@ -52,13 +54,18 @@ impl IssueFilter {
|
||||
match self {
|
||||
Self::All => true,
|
||||
Self::Open => store.status_of(issue) == RepoStatus::Open,
|
||||
Self::Closed => store.status_of(issue) == RepoStatus::Closed,
|
||||
Self::Closed => matches!(
|
||||
store.status_of(issue),
|
||||
RepoStatus::Closed | RepoStatus::Applied
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IssuesView {
|
||||
focus_handle: FocusHandle,
|
||||
/// Dock area the issue detail panel is opened in.
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
/// Repo store holding the issues and their statuses.
|
||||
store: Entity<RepoStore>,
|
||||
/// Display name of the repository, for the panel title.
|
||||
@@ -79,6 +86,7 @@ pub struct IssuesView {
|
||||
|
||||
impl IssuesView {
|
||||
pub fn new(
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
store: Entity<RepoStore>,
|
||||
repo_name: SharedString,
|
||||
window: &mut Window,
|
||||
@@ -90,6 +98,7 @@ impl IssuesView {
|
||||
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
dock_area,
|
||||
store,
|
||||
repo_name,
|
||||
filter: IssueFilter::Open,
|
||||
@@ -100,7 +109,28 @@ impl IssuesView {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_row(&self, ix: usize, issue: &Event, cx: &App) -> AnyElement {
|
||||
/// Open the detail panel of `issue_id` at the bottom of the dock area.
|
||||
fn open_issue_detail(
|
||||
&mut self,
|
||||
issue_id: EventId,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(dock_area) = self.dock_area.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let panel = cx.new(|cx| IssueDetailView::new(self.store.clone(), issue_id, window, cx));
|
||||
|
||||
dock_area.update(cx, |dock_area, cx| {
|
||||
dock_area.add_panel(Arc::new(panel), DockPlacement::Bottom, window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
/// Render one row of the issue list; `ix` is the row index and
|
||||
/// `issue_ix` the index of the issue in the store's `issues`.
|
||||
fn render_row(&self, ix: usize, issue_ix: usize, cx: &mut Context<Self>) -> AnyElement {
|
||||
let issue = &self.store.read(cx).issues[issue_ix];
|
||||
let title = activity_subject(issue);
|
||||
let id_hex = issue.id.to_hex();
|
||||
let profile = ProfileStore::global(cx).read(cx).get(&issue.pubkey);
|
||||
@@ -108,22 +138,27 @@ impl IssuesView {
|
||||
let picture = profile.picture();
|
||||
let age = relative_time(issue.created_at);
|
||||
let status = self.store.read(cx).status_of(issue);
|
||||
let issue_id = issue.id;
|
||||
|
||||
h_flex()
|
||||
.id(ix)
|
||||
.h(px(ISSUE_ROW_HEIGHT))
|
||||
.w_full()
|
||||
.gap_4()
|
||||
.p_3()
|
||||
.px_4()
|
||||
.py_2()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().border)
|
||||
.items_start()
|
||||
.child(Self::render_status(status, cx))
|
||||
.on_click(cx.listener(move |this, _event, window, cx| {
|
||||
this.open_issue_detail(issue_id, window, cx);
|
||||
}))
|
||||
.child(status_badge(status, cx))
|
||||
.child(
|
||||
v_flex()
|
||||
.flex_1()
|
||||
.child(
|
||||
div()
|
||||
.h_8()
|
||||
.min_w_0()
|
||||
.text_ellipsis()
|
||||
.whitespace_nowrap()
|
||||
@@ -133,6 +168,7 @@ impl IssuesView {
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.h_6()
|
||||
.gap_2()
|
||||
.text_xs()
|
||||
.child(
|
||||
@@ -142,7 +178,7 @@ impl IssuesView {
|
||||
Avatar::new()
|
||||
.name(author.clone())
|
||||
.when_some(picture, |this, url| this.src(url))
|
||||
.xsmall(),
|
||||
.small(),
|
||||
)
|
||||
.child(div().child(author)),
|
||||
)
|
||||
@@ -152,117 +188,160 @@ impl IssuesView {
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child(SharedString::from(&id_hex[..8])),
|
||||
)
|
||||
.child(div().child(age)),
|
||||
.child(SharedString::from(age)),
|
||||
),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_status(status: RepoStatus, cx: &App) -> AnyElement {
|
||||
let (icon, label, tooltip, bg, fg) = match status {
|
||||
RepoStatus::Open => (
|
||||
CustomIconName::GitIssueOpen,
|
||||
"open",
|
||||
"Issue is open",
|
||||
cx.theme().secondary,
|
||||
cx.theme().secondary_foreground,
|
||||
),
|
||||
RepoStatus::Closed => (
|
||||
CustomIconName::GitIssueClosed,
|
||||
"closed",
|
||||
"Issue is closed",
|
||||
cx.theme().warning,
|
||||
cx.theme().warning_foreground,
|
||||
),
|
||||
RepoStatus::Draft => (
|
||||
CustomIconName::GitIssueOngoing,
|
||||
"draft",
|
||||
"Issue is draft",
|
||||
cx.theme().accent,
|
||||
cx.theme().accent_foreground,
|
||||
),
|
||||
RepoStatus::Applied => (
|
||||
CustomIconName::GitIssueOpen,
|
||||
"applied",
|
||||
"Issue is completed",
|
||||
cx.theme().primary,
|
||||
cx.theme().primary_foreground,
|
||||
),
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.id(label)
|
||||
.flex_shrink_0()
|
||||
.size_6()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.rounded(cx.theme().radius)
|
||||
.bg(bg)
|
||||
.child(Icon::new(icon).xsmall().text_color(fg))
|
||||
.tooltip(move |window, cx| Tooltip::new(tooltip).build(window, cx))
|
||||
.hover(|this| this.bg(cx.theme().list_hover))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_header(&self, cx: &mut Context<Self>) -> AnyElement {
|
||||
let store = self.store.read(cx);
|
||||
let (total, open, closed) =
|
||||
store
|
||||
.issues
|
||||
.iter()
|
||||
.fold(
|
||||
(0usize, 0usize, 0usize),
|
||||
|(total, open, closed), issue| match store.status_of(issue) {
|
||||
RepoStatus::Open => (total + 1, open + 1, closed),
|
||||
RepoStatus::Closed => (total + 1, open, closed + 1),
|
||||
RepoStatus::Draft | RepoStatus::Applied => (total + 1, open, closed),
|
||||
},
|
||||
);
|
||||
|
||||
h_flex()
|
||||
.px_4()
|
||||
.w_full()
|
||||
.items_center()
|
||||
.gap_3()
|
||||
.px_3()
|
||||
.pb_2()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().border)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.font_semibold()
|
||||
.child("Issues"),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.h_12()
|
||||
.gap_2()
|
||||
.child(
|
||||
Button::new("all")
|
||||
.icon(CustomIconName::GitIssueOpen)
|
||||
.label("All")
|
||||
.ghost()
|
||||
BaseButton::new("all")
|
||||
.flex()
|
||||
.items_center()
|
||||
.h_7()
|
||||
.px_2()
|
||||
.gap_1()
|
||||
.child(Icon::new(CustomIconName::GitIssueDone))
|
||||
.child(div().text_sm().child("All"))
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_center()
|
||||
.ml_2()
|
||||
.px_1()
|
||||
.py_0p5()
|
||||
.min_w_4()
|
||||
.text_size(px(8.))
|
||||
.bg(cx.theme().muted)
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.rounded(cx.theme().radius)
|
||||
.line_height(relative(1.))
|
||||
.child(SharedString::from(total.to_string())),
|
||||
)
|
||||
.text_color(cx.theme().button_foreground)
|
||||
.rounded(cx.theme().radius)
|
||||
.hover(|this| this.bg(cx.theme().button_hover))
|
||||
.active(|this| this.bg(cx.theme().button_active))
|
||||
.selected(self.filter == IssueFilter::All)
|
||||
.when(self.filter == IssueFilter::All, |this| {
|
||||
this.bg(cx.theme().button_active)
|
||||
})
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.filter = IssueFilter::All;
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("open")
|
||||
.icon(CustomIconName::GitIssueOpen)
|
||||
.label("Open")
|
||||
.ghost()
|
||||
BaseButton::new("open")
|
||||
.flex()
|
||||
.items_center()
|
||||
.h_7()
|
||||
.px_2()
|
||||
.gap_1()
|
||||
.child(Icon::new(CustomIconName::GitIssueOpen))
|
||||
.child(div().text_sm().child("Open"))
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_center()
|
||||
.ml_2()
|
||||
.px_1()
|
||||
.py_0p5()
|
||||
.min_w_4()
|
||||
.text_size(px(8.))
|
||||
.bg(cx.theme().muted)
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.rounded(cx.theme().radius)
|
||||
.line_height(relative(1.))
|
||||
.child(SharedString::from(open.to_string())),
|
||||
)
|
||||
.text_color(cx.theme().button_foreground)
|
||||
.rounded(cx.theme().radius)
|
||||
.hover(|this| this.bg(cx.theme().button_hover))
|
||||
.selected(self.filter == IssueFilter::Open)
|
||||
.when(self.filter == IssueFilter::Open, |this| {
|
||||
this.bg(cx.theme().button_active)
|
||||
})
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.filter = IssueFilter::Open;
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("closed")
|
||||
.icon(CustomIconName::GitIssueClosed)
|
||||
.label("Closed")
|
||||
.ghost()
|
||||
BaseButton::new("closed")
|
||||
.flex()
|
||||
.items_center()
|
||||
.h_7()
|
||||
.px_2()
|
||||
.gap_1()
|
||||
.child(Icon::new(CustomIconName::GitIssueClosed))
|
||||
.child(div().text_sm().child("Closed"))
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_center()
|
||||
.ml_2()
|
||||
.px_1()
|
||||
.py_0p5()
|
||||
.min_w_4()
|
||||
.text_size(px(8.))
|
||||
.bg(cx.theme().muted)
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.rounded(cx.theme().radius)
|
||||
.line_height(relative(1.))
|
||||
.child(SharedString::from(closed.to_string())),
|
||||
)
|
||||
.text_color(cx.theme().button_foreground)
|
||||
.rounded(cx.theme().radius)
|
||||
.hover(|this| this.bg(cx.theme().button_hover))
|
||||
.selected(self.filter == IssueFilter::Closed)
|
||||
.when(self.filter == IssueFilter::Closed, |this| {
|
||||
this.bg(cx.theme().button_active)
|
||||
})
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.filter = IssueFilter::Closed;
|
||||
cx.notify();
|
||||
})),
|
||||
),
|
||||
)
|
||||
// Spacer: pushes the button to the right edge.
|
||||
.child(div().flex_1())
|
||||
.child(
|
||||
Button::new("new-issue")
|
||||
.icon(IconName::Plus)
|
||||
.label("New issue")
|
||||
.primary()
|
||||
BaseButton::new("new")
|
||||
.flex()
|
||||
.items_center()
|
||||
.h_7()
|
||||
.px_2()
|
||||
.gap_1()
|
||||
.child(Icon::new(CustomIconName::CirclePlus))
|
||||
.child(div().text_sm().child("New issue"))
|
||||
.text_color(cx.theme().button_primary_foreground)
|
||||
.rounded(cx.theme().radius)
|
||||
.bg(cx.theme().button_primary)
|
||||
.hover(|this| this.bg(cx.theme().button_primary_hover))
|
||||
.active(|this| this.bg(cx.theme().button_primary_active))
|
||||
.on_click(cx.listener(|this, _event, window, cx| {
|
||||
open_new_issue_dialog(this.store.clone(), window, cx);
|
||||
})),
|
||||
@@ -283,8 +362,8 @@ fn open_new_issue_dialog(store: Entity<RepoStore>, window: &mut Window, cx: &mut
|
||||
let store = store.clone();
|
||||
|
||||
dialog
|
||||
.width(px(520.))
|
||||
.margin_top(px(50.))
|
||||
.keyboard(true)
|
||||
.close_button(true)
|
||||
.content(move |body, _window, _cx| {
|
||||
body.child(
|
||||
DialogHeader::new()
|
||||
@@ -383,7 +462,6 @@ impl Render for IssuesView {
|
||||
|
||||
let sizes = self.item_sizes.clone();
|
||||
let scroll_handle = self.scroll_handle.clone();
|
||||
let view = cx.entity().clone();
|
||||
|
||||
v_flex()
|
||||
.size_full()
|
||||
@@ -396,15 +474,19 @@ impl Render for IssuesView {
|
||||
.w_full()
|
||||
.when(count > 0, |this| {
|
||||
this.child(
|
||||
v_virtual_list(view, "il", sizes, move |this, range, _window, cx| {
|
||||
let issues = &this.store.read(cx).issues;
|
||||
range
|
||||
.map(|ix| {
|
||||
let issue_ix = this.visible_issues[ix];
|
||||
this.render_row(issue_ix, &issues[issue_ix], cx)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
v_virtual_list(
|
||||
cx.entity().clone(),
|
||||
"issues",
|
||||
sizes,
|
||||
move |this, range, _window, cx| {
|
||||
range
|
||||
.map(|ix| {
|
||||
let issue = this.visible_issues[ix];
|
||||
this.render_row(ix, issue, cx)
|
||||
})
|
||||
.collect()
|
||||
},
|
||||
)
|
||||
.track_scroll(&scroll_handle)
|
||||
.size_full(),
|
||||
)
|
||||
|
||||
@@ -18,11 +18,11 @@ use gpui_component::combobox::{
|
||||
Caret, Combobox, ComboboxEvent, ComboboxState, ComboboxTriggerContext,
|
||||
};
|
||||
use gpui_component::searchable_list::SearchableVec;
|
||||
use gpui_component::tab::{Tab, TabBar};
|
||||
use gpui_component::tag::Tag;
|
||||
use gpui_component::tree::TreeState;
|
||||
use gpui_component::{
|
||||
ActiveTheme, Icon, IconName, Sizable, StyledExt, VirtualListScrollHandle, h_flex, v_flex,
|
||||
ActiveTheme, Icon, IconName, Selectable, Sizable, StyledExt, VirtualListScrollHandle, h_flex,
|
||||
v_flex,
|
||||
};
|
||||
use signed_core::Announcement;
|
||||
use signed_git::{CommitList, FileCommit};
|
||||
@@ -32,6 +32,7 @@ mod browser;
|
||||
mod commits;
|
||||
mod diff;
|
||||
mod helpers;
|
||||
mod issue_detail;
|
||||
mod issues;
|
||||
mod pull_requests;
|
||||
|
||||
@@ -661,8 +662,15 @@ impl RepoDetailView {
|
||||
return;
|
||||
};
|
||||
|
||||
let panel =
|
||||
cx.new(|cx| IssuesView::new(self.store.clone(), self.display_name(cx), window, cx));
|
||||
let panel = cx.new(|cx| {
|
||||
IssuesView::new(
|
||||
self.dock_area.clone(),
|
||||
self.store.clone(),
|
||||
self.display_name(cx),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
dock_area.update(cx, |dock_area, cx| {
|
||||
dock_area.add_panel(Arc::new(panel), DockPlacement::Center, window, cx);
|
||||
@@ -960,7 +968,7 @@ impl RepoDetailView {
|
||||
.child(
|
||||
div()
|
||||
.min_w_0()
|
||||
.text_xs()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.line_clamp(2)
|
||||
.text_ellipsis()
|
||||
@@ -1034,24 +1042,31 @@ impl RepoDetailView {
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child(
|
||||
TabBar::new("repo-tabs")
|
||||
.segmented()
|
||||
.selected_index(self.active_tab)
|
||||
.child(Tab::new().label("Files"))
|
||||
.child(Tab::new().label("Commits").when_some(
|
||||
commits_count,
|
||||
|this, count| {
|
||||
this.suffix(
|
||||
Tag::secondary()
|
||||
.xsmall()
|
||||
.mr_1()
|
||||
.child(SharedString::from(count.to_string())),
|
||||
)
|
||||
},
|
||||
))
|
||||
.on_click(cx.listener(|this, index, _window, cx| {
|
||||
this.active_tab = *index;
|
||||
Button::new("files-tab")
|
||||
.label("Files")
|
||||
.selected(self.active_tab == 0)
|
||||
.toggled(self.active_tab == 0)
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.active_tab = 0;
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("commits-tab")
|
||||
.label("Commits")
|
||||
.selected(self.active_tab == 1)
|
||||
.toggled(self.active_tab == 1)
|
||||
.when_some(commits_count, |this, count| {
|
||||
this.child(
|
||||
Tag::secondary()
|
||||
.xsmall()
|
||||
.child(SharedString::from(count.to_string())),
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.active_tab = 1;
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
|
||||
@@ -150,10 +150,6 @@ impl Panel for SidebarPanel {
|
||||
fn closable(&self, _cx: &App) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn inner_padding(&self, _cx: &App) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<PanelEvent> for SidebarPanel {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use assets::CustomIconName;
|
||||
use gpui::prelude::*;
|
||||
use gpui::{AnyWindowHandle, App, Entity, SharedString, Subscription, Window, div, px};
|
||||
use gpui::{AnyWindowHandle, App, Entity, SharedString, Subscription, Window, div};
|
||||
use gpui_component::button::{Button, ButtonVariants};
|
||||
use gpui_component::dialog::{DialogDescription, DialogFooter, DialogHeader, DialogTitle};
|
||||
use gpui_component::form::{field, v_form};
|
||||
@@ -49,8 +49,9 @@ pub fn open(window: &mut Window, cx: &mut App) {
|
||||
let state = state.clone();
|
||||
|
||||
dialog
|
||||
.width(px(420.))
|
||||
.margin_top(px(50.))
|
||||
.close_button(false)
|
||||
.overlay_closable(false)
|
||||
.keyboard(false)
|
||||
.content(move |content, _window, cx| {
|
||||
let busy = state.read(cx).busy;
|
||||
let error = state.read(cx).error.clone();
|
||||
|
||||
Reference in New Issue
Block a user