update
This commit is contained in:
Generated
+79
-58
@@ -561,7 +561,7 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
"pastey",
|
"pastey",
|
||||||
"rayon",
|
"rayon",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"v_frame",
|
"v_frame",
|
||||||
"y4m",
|
"y4m",
|
||||||
]
|
]
|
||||||
@@ -1788,6 +1788,46 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
|
checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "defmt"
|
||||||
|
version = "0.3.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad"
|
||||||
|
dependencies = [
|
||||||
|
"defmt 1.1.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "defmt"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"defmt-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "defmt-macros"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
|
||||||
|
dependencies = [
|
||||||
|
"defmt-parser",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "defmt-parser"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror 2.0.21",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.5.8"
|
version = "0.5.8"
|
||||||
@@ -1989,13 +2029,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.41"
|
version = "0.8.42"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7b5ef0006ac9ab233c38522f5ae99cae3625151de8f706cacee1cba4b8e2832a"
|
checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"core_detect",
|
"core_detect",
|
||||||
"multiversion",
|
|
||||||
"multiversion_no_op",
|
"multiversion_no_op",
|
||||||
"rustversion",
|
"rustversion",
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
@@ -2145,10 +2184,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "faster-hex"
|
name = "faster-hex"
|
||||||
version = "0.10.0"
|
version = "0.10.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73"
|
checksum = "04839bdf9d8c10f66806fad16b852fc72aab80873aebc3cb69d85b4fa41543ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"defmt 0.3.100",
|
||||||
"heapless 0.8.0",
|
"heapless 0.8.0",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
@@ -2685,7 +2726,7 @@ dependencies = [
|
|||||||
"hashbrown 0.16.1",
|
"hashbrown 0.16.1",
|
||||||
"log",
|
"log",
|
||||||
"presser",
|
"presser",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"windows 0.62.2",
|
"windows 0.62.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2805,7 +2846,7 @@ dependencies = [
|
|||||||
"spin 0.10.1",
|
"spin 0.10.1",
|
||||||
"strum",
|
"strum",
|
||||||
"taffy",
|
"taffy",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"ttf-parser",
|
"ttf-parser",
|
||||||
"url",
|
"url",
|
||||||
"usvg",
|
"usvg",
|
||||||
@@ -3293,7 +3334,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2 0.10.9",
|
"sha2 0.10.9",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"tracing",
|
"tracing",
|
||||||
"ureq",
|
"ureq",
|
||||||
]
|
]
|
||||||
@@ -3624,16 +3665,17 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-util"
|
name = "hyper-util"
|
||||||
version = "0.1.20"
|
version = "0.1.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.23.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
|
"httparse",
|
||||||
"hyper",
|
"hyper",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -3954,7 +3996,7 @@ dependencies = [
|
|||||||
"jni-sys 0.4.1",
|
"jni-sys 0.4.1",
|
||||||
"log",
|
"log",
|
||||||
"simd_cesu8",
|
"simd_cesu8",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"windows-link 0.2.1",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
@@ -4210,9 +4252,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lru"
|
name = "lru"
|
||||||
version = "0.18.4"
|
version = "0.18.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ff9840bcc50b71349309900da0ce7279aa336ae71d73250b07998932c7d97c25"
|
checksum = "ef9ac18847474e638e3702b76c65d4eb93428471a74778ef0f1be711717f89b5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lru-slab"
|
name = "lru-slab"
|
||||||
@@ -4487,27 +4529,6 @@ dependencies = [
|
|||||||
"pxfm",
|
"pxfm",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "multiversion"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b4ca4bea16ffc3f443cf7d866912118196bfef4c6a1556ca00f9f9b00bb43f7c"
|
|
||||||
dependencies = [
|
|
||||||
"multiversion-macros",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "multiversion-macros"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0d416831a7317ef4b08bee00b69cbbb9c8763da7959a7026244d6266869f9c83"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"rustversion",
|
|
||||||
"syn 3.0.6",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "multiversion_no_op"
|
name = "multiversion_no_op"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -4536,7 +4557,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"rustc-hash 1.1.0",
|
"rustc-hash 1.1.0",
|
||||||
"spirv",
|
"spirv",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5912,7 +5933,7 @@ dependencies = [
|
|||||||
"rustc-hash 2.1.3",
|
"rustc-hash 2.1.3",
|
||||||
"rustls",
|
"rustls",
|
||||||
"socket2",
|
"socket2",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"web-time",
|
"web-time",
|
||||||
@@ -5934,7 +5955,7 @@ dependencies = [
|
|||||||
"rustls",
|
"rustls",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"slab",
|
"slab",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
"tracing",
|
"tracing",
|
||||||
"web-time",
|
"web-time",
|
||||||
@@ -6102,7 +6123,7 @@ dependencies = [
|
|||||||
"rand 0.9.5",
|
"rand 0.9.5",
|
||||||
"rand_chacha 0.9.0",
|
"rand_chacha 0.9.0",
|
||||||
"simd_helpers",
|
"simd_helpers",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"v_frame",
|
"v_frame",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
@@ -6500,9 +6521,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-platform-verifier"
|
name = "rustls-platform-verifier"
|
||||||
version = "0.7.0"
|
version = "0.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
|
checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.1",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
@@ -6521,9 +6542,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-platform-verifier-android"
|
name = "rustls-platform-verifier-android"
|
||||||
version = "0.1.1"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
@@ -7426,7 +7447,7 @@ version = "0.7.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9ed071c670382e85fc2f48ae706492d8c338f4f89bf72520d32f8abfe880aade"
|
checksum = "9ed071c670382e85fc2f48ae706492d8c338f4f89bf72520d32f8abfe880aade"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"windows 0.61.3",
|
"windows 0.61.3",
|
||||||
"windows-version",
|
"windows-version",
|
||||||
]
|
]
|
||||||
@@ -7490,11 +7511,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.20"
|
version = "2.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
|
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl 2.0.20",
|
"thiserror-impl 2.0.21",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7510,9 +7531,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "2.0.20"
|
version = "2.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
|
checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -7958,7 +7979,7 @@ dependencies = [
|
|||||||
"rustls",
|
"rustls",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"sha1",
|
"sha1",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"utf-8",
|
"utf-8",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -8641,7 +8662,7 @@ dependencies = [
|
|||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"rustc-hash 1.1.0",
|
"rustc-hash 1.1.0",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"wgpu-core-deps-apple",
|
"wgpu-core-deps-apple",
|
||||||
"wgpu-core-deps-emscripten",
|
"wgpu-core-deps-emscripten",
|
||||||
"wgpu-core-deps-wasm",
|
"wgpu-core-deps-wasm",
|
||||||
@@ -8730,7 +8751,7 @@ dependencies = [
|
|||||||
"raw-window-metal",
|
"raw-window-metal",
|
||||||
"renderdoc-sys",
|
"renderdoc-sys",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wayland-sys",
|
"wayland-sys",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
@@ -8858,7 +8879,7 @@ checksum = "3a4df73e95feddb9ec1a7e9c2ca6323b8c97d5eeeff78d28f1eccdf19c882b24"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"rayon",
|
"rayon",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"windows 0.61.3",
|
"windows 0.61.3",
|
||||||
"windows-future 0.2.1",
|
"windows-future 0.2.1",
|
||||||
]
|
]
|
||||||
@@ -9810,18 +9831,18 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.57"
|
version = "0.8.58"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873"
|
checksum = "c17e8fafad82b542ff3717217ecdc736231b59e387768c9630123b4ce4d2db44"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.57"
|
version = "0.8.58"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc"
|
checksum = "595f56e044df4f46a0c9a626f65c3d99eb8488f7e8a8baa12dd76326d9710bf2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 669 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 598 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path d="M8.75 3.75L6.75 20.25M17.25 3.75L15.25 20.25M3.75 7.75H20.25M20.25 16.25H3.75" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 258 B |
@@ -67,6 +67,7 @@ pub enum IconName {
|
|||||||
Ship,
|
Ship,
|
||||||
Shield,
|
Shield,
|
||||||
Group,
|
Group,
|
||||||
|
Hashtag,
|
||||||
History,
|
History,
|
||||||
UserKey,
|
UserKey,
|
||||||
Upload,
|
Upload,
|
||||||
@@ -148,6 +149,7 @@ impl IconNamed for IconName {
|
|||||||
Self::Upload => "icons/upload.svg",
|
Self::Upload => "icons/upload.svg",
|
||||||
Self::Usb => "icons/usb.svg",
|
Self::Usb => "icons/usb.svg",
|
||||||
Self::Group => "icons/group.svg",
|
Self::Group => "icons/group.svg",
|
||||||
|
Self::Hashtag => "icons/hashtag.svg",
|
||||||
Self::History => "icons/history.svg",
|
Self::History => "icons/history.svg",
|
||||||
Self::PanelLeft => "icons/panel-left.svg",
|
Self::PanelLeft => "icons/panel-left.svg",
|
||||||
Self::PanelLeftOpen => "icons/panel-left-open.svg",
|
Self::PanelLeftOpen => "icons/panel-left-open.svg",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ pub mod input;
|
|||||||
pub mod menu;
|
pub mod menu;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
pub mod modal;
|
pub mod modal;
|
||||||
|
pub mod nav;
|
||||||
pub mod nav_item;
|
pub mod nav_item;
|
||||||
pub mod notification;
|
pub mod notification;
|
||||||
pub mod popover;
|
pub mod popover;
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use gpui::prelude::FluentBuilder as _;
|
||||||
|
use gpui::{
|
||||||
|
AnyElement, App, ClickEvent, ElementId, InteractiveElement, IntoElement, ParentElement,
|
||||||
|
RenderOnce, SharedString, StatefulInteractiveElement as _, StyleRefinement, Styled, Window,
|
||||||
|
div,
|
||||||
|
};
|
||||||
|
use theme::ActiveTheme;
|
||||||
|
|
||||||
|
use crate::{Selectable, StyledExt, h_flex, v_flex};
|
||||||
|
|
||||||
|
/// A single navigation row, such as an entry in a sidebar list.
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct Nav {
|
||||||
|
id: ElementId,
|
||||||
|
style: StyleRefinement,
|
||||||
|
prefix: Option<AnyElement>,
|
||||||
|
label: SharedString,
|
||||||
|
suffix: Option<AnyElement>,
|
||||||
|
selected: bool,
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Nav {
|
||||||
|
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||||
|
Self {
|
||||||
|
id: id.into(),
|
||||||
|
style: StyleRefinement::default(),
|
||||||
|
prefix: None,
|
||||||
|
label: SharedString::default(),
|
||||||
|
suffix: None,
|
||||||
|
selected: false,
|
||||||
|
on_click: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the element shown before the label, such as an avatar or icon.
|
||||||
|
pub fn prefix(mut self, prefix: impl IntoElement) -> Self {
|
||||||
|
self.prefix = Some(prefix.into_any_element());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the row's label.
|
||||||
|
pub fn label(mut self, label: impl Into<SharedString>) -> Self {
|
||||||
|
self.label = label.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the element shown after the label, such as a timestamp or badge.
|
||||||
|
pub fn suffix(mut self, suffix: impl IntoElement) -> Self {
|
||||||
|
self.suffix = Some(suffix.into_any_element());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_click(
|
||||||
|
mut self,
|
||||||
|
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||||
|
) -> Self {
|
||||||
|
self.on_click = Some(Rc::new(handler));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Selectable for Nav {
|
||||||
|
fn selected(mut self, selected: bool) -> Self {
|
||||||
|
self.selected = selected;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_selected(&self) -> bool {
|
||||||
|
self.selected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Styled for Nav {
|
||||||
|
fn style(&mut self) -> &mut StyleRefinement {
|
||||||
|
&mut self.style
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for Nav {
|
||||||
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
let clickable = self.on_click.is_some();
|
||||||
|
|
||||||
|
v_flex()
|
||||||
|
.id(format!("nav-{}", self.id))
|
||||||
|
.w_full()
|
||||||
|
.h_10()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.id(self.id)
|
||||||
|
.h_9()
|
||||||
|
.w_full()
|
||||||
|
.px_1p5()
|
||||||
|
.gap_1p5()
|
||||||
|
.rounded(cx.theme().radius_lg)
|
||||||
|
.when_some(self.prefix, |this, prefix| this.child(prefix))
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.flex_1()
|
||||||
|
.child(div().truncate().min_w_0().child(self.label))
|
||||||
|
.child(div().flex_1())
|
||||||
|
.when_some(self.suffix, |this, suffix| {
|
||||||
|
this.child(div().flex_shrink_0().child(suffix))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.when(clickable, |this| {
|
||||||
|
this.cursor_pointer()
|
||||||
|
.hover(|this| this.bg(cx.theme().ghost_element_hover))
|
||||||
|
.when(self.selected, |this| {
|
||||||
|
this.bg(cx.theme().ghost_element_active)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.when_some(self.on_click, |this, handler| {
|
||||||
|
this.on_click(move |event, window, cx| handler(event, window, cx))
|
||||||
|
})
|
||||||
|
.refine_style(&self.style),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ pub fn open(window: &mut Window, cx: &mut App) {
|
|||||||
.show_close(false)
|
.show_close(false)
|
||||||
.overlay_closable(false)
|
.overlay_closable(false)
|
||||||
.keyboard(false)
|
.keyboard(false)
|
||||||
.title("Onboarding")
|
.title("Get Started")
|
||||||
.child(import.clone())
|
.child(import.clone())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ use theme::ActiveTheme;
|
|||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
use ui::dock::{DockArea, DockPlacement, Panel, PanelEvent, PanelHandle};
|
use ui::dock::{DockArea, DockPlacement, Panel, PanelEvent, PanelHandle};
|
||||||
use ui::input::{Input, InputEvent, InputState};
|
use ui::input::{Input, InputEvent, InputState};
|
||||||
|
use ui::nav::Nav;
|
||||||
use ui::notification::Notification;
|
use ui::notification::Notification;
|
||||||
use ui::{Icon, IconName, Selectable, Sizable, StyledExt, WindowExtension, h_flex, v_flex};
|
use ui::{Icon, IconName, Selectable, Sizable, StyledExt, WindowExtension, h_flex, v_flex};
|
||||||
|
|
||||||
use crate::sidebar::{TreeRow, TreeRowKind};
|
use crate::sidebar::nav_avatar;
|
||||||
|
|
||||||
const INPUT_PLACEHOLDER: &str = "Find or start a conversation";
|
const INPUT_PLACEHOLDER: &str = "Find or start a conversation";
|
||||||
|
|
||||||
@@ -357,13 +358,17 @@ impl SearchPanel {
|
|||||||
this.select(&pkey_clone, cx);
|
this.select(&pkey_clone, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
TreeRow::new(
|
Nav::new(ElementId::NamedInteger(
|
||||||
ElementId::NamedInteger("search-result".into(), (range.start + ix) as u64),
|
"search-result".into(),
|
||||||
TreeRowKind::Room,
|
(range.start + ix) as u64,
|
||||||
profile.name(),
|
))
|
||||||
|
.label(profile.name())
|
||||||
|
.text_sm()
|
||||||
|
.font_medium()
|
||||||
|
.when_some(
|
||||||
|
nav_avatar(Some(profile.avatar_seed()), profile.avatar(), cx),
|
||||||
|
|this, avatar| this.prefix(avatar),
|
||||||
)
|
)
|
||||||
.avatar(profile.avatar_seed())
|
|
||||||
.picture(profile.avatar())
|
|
||||||
.on_click(handler)
|
.on_click(handler)
|
||||||
.selected(selected)
|
.selected(selected)
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
@@ -398,13 +403,17 @@ impl SearchPanel {
|
|||||||
this.select(&pkey_clone, cx);
|
this.select(&pkey_clone, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
TreeRow::new(
|
Nav::new(ElementId::NamedInteger(
|
||||||
ElementId::NamedInteger("contact".into(), (range.start + ix) as u64),
|
"contact".into(),
|
||||||
TreeRowKind::Room,
|
(range.start + ix) as u64,
|
||||||
profile.name().trim(),
|
))
|
||||||
|
.label(profile.name().trim())
|
||||||
|
.text_sm()
|
||||||
|
.font_medium()
|
||||||
|
.when_some(
|
||||||
|
nav_avatar(Some(profile.avatar_seed()), profile.avatar(), cx),
|
||||||
|
|this, avatar| this.prefix(avatar),
|
||||||
)
|
)
|
||||||
.avatar(profile.avatar_seed())
|
|
||||||
.picture(profile.avatar())
|
|
||||||
.on_click(handler)
|
.on_click(handler)
|
||||||
.selected(selected)
|
.selected(selected)
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
|
|||||||
+187
-161
@@ -2,12 +2,12 @@ use std::ops::Range;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use auto_update::AutoUpdater;
|
use auto_update::AutoUpdater;
|
||||||
use chat::{ChatEvent, ChatRegistry, RoomKind};
|
use chat::{ChatEvent, ChatRegistry, Room, RoomKind};
|
||||||
use common::TimestampExt;
|
use common::TimestampExt;
|
||||||
use community::{ChannelId, Community, CommunityEvent, CommunityRegistry};
|
use community::{ChannelId, Community, CommunityEvent, CommunityRegistry};
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, Context, Div, ElementId, Entity, EventEmitter, FocusHandle, Focusable,
|
AnyElement, App, Context, Div, Entity, EventEmitter, FocusHandle, Focusable,
|
||||||
InteractiveElement, IntoElement, ObjectFit, ParentElement, Render, SharedString, Stateful,
|
InteractiveElement, IntoElement, ObjectFit, ParentElement, Render, SharedString, Stateful,
|
||||||
Styled, StyledImage, Subscription, UniformListScrollHandle, WeakEntity, Window, div, img, px,
|
Styled, StyledImage, Subscription, UniformListScrollHandle, WeakEntity, Window, div, img, px,
|
||||||
retain_all, uniform_list,
|
retain_all, uniform_list,
|
||||||
@@ -15,32 +15,59 @@ use gpui::{
|
|||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use person::PersonRegistry;
|
use person::PersonRegistry;
|
||||||
use smallvec::{SmallVec, smallvec};
|
use smallvec::{SmallVec, smallvec};
|
||||||
use state::NostrRegistry;
|
use state::{NostrRegistry, StateEvent};
|
||||||
use theme::{ActiveTheme, TABBAR_HEIGHT};
|
use theme::{ActiveTheme, TABBAR_HEIGHT};
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonCustomVariant, ButtonVariants};
|
||||||
use ui::dock::{DockArea, DockPlacement, Panel, PanelEvent, PanelHandle};
|
use ui::dock::{DockArea, DockPlacement, Panel, PanelEvent, PanelHandle};
|
||||||
use ui::indicator::Indicator;
|
use ui::indicator::Indicator;
|
||||||
use ui::menu::{DropdownMenu, PopupMenuItem};
|
use ui::menu::{DropdownMenu, PopupMenuItem};
|
||||||
|
use ui::nav::Nav;
|
||||||
use ui::nav_item::NavItem;
|
use ui::nav_item::NavItem;
|
||||||
use ui::notification::Notification;
|
use ui::notification::Notification;
|
||||||
use ui::scroll::Scrollbar;
|
use ui::scroll::Scrollbar;
|
||||||
use ui::tab::Tab;
|
use ui::tab::Tab;
|
||||||
use ui::tab::tab_bar::TabBar;
|
use ui::tab::tab_bar::TabBar;
|
||||||
use ui::{
|
use ui::{
|
||||||
Disableable, Icon, IconName, Sizable, StyledExt, TRAFFIC_LIGHT_PADDING, WindowExtension,
|
Disableable, Icon, IconName, Selectable, Sizable, StyledExt, TRAFFIC_LIGHT_PADDING,
|
||||||
h_flex, title_bar_drag_handlers, v_flex,
|
WindowExtension, h_flex, title_bar_drag_handlers, v_flex,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::Command;
|
use crate::Command;
|
||||||
use crate::dialogs::import;
|
use crate::dialogs::import;
|
||||||
|
|
||||||
mod tab;
|
mod tab;
|
||||||
mod tree;
|
mod utils;
|
||||||
|
|
||||||
use tab::SidebarTab;
|
use tab::SidebarTab;
|
||||||
use tree::{CommunityRow, CommunitySection, SidebarRow};
|
pub(crate) use utils::{nav_avatar, nav_icon, pick_banner};
|
||||||
pub(crate) use tree::{TreeRow, TreeRowKind};
|
|
||||||
|
pub enum SidebarRow {
|
||||||
|
Room { room: Entity<Room> },
|
||||||
|
Community { community: Entity<Community> },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A collapsible group of rows in the sidebar's community view.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum CommunitySection {
|
||||||
|
Channels,
|
||||||
|
Admins,
|
||||||
|
Members,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A row in the sidebar's community view.
|
||||||
|
pub enum CommunityRow {
|
||||||
|
Section(CommunitySection),
|
||||||
|
Channel {
|
||||||
|
id: ChannelId,
|
||||||
|
name: SharedString,
|
||||||
|
private: bool,
|
||||||
|
selected: bool,
|
||||||
|
},
|
||||||
|
Member {
|
||||||
|
public_key: PublicKey,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Sidebar {
|
pub struct Sidebar {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
@@ -49,6 +76,8 @@ pub struct Sidebar {
|
|||||||
community_scroll: UniformListScrollHandle,
|
community_scroll: UniformListScrollHandle,
|
||||||
/// The dock the sidebar opens its panels in
|
/// The dock the sidebar opens its panels in
|
||||||
dock: WeakEntity<DockArea>,
|
dock: WeakEntity<DockArea>,
|
||||||
|
/// Background shown behind the signed-out screen, picked at random
|
||||||
|
banner: SharedString,
|
||||||
active_tab: SidebarTab,
|
active_tab: SidebarTab,
|
||||||
/// The community the sidebar is browsing, if any
|
/// The community the sidebar is browsing, if any
|
||||||
community: Option<WeakEntity<Community>>,
|
community: Option<WeakEntity<Community>>,
|
||||||
@@ -63,6 +92,7 @@ impl Sidebar {
|
|||||||
pub fn new(window: &mut Window, dock: WeakEntity<DockArea>, cx: &mut Context<Self>) -> Self {
|
pub fn new(window: &mut Window, dock: WeakEntity<DockArea>, cx: &mut Context<Self>) -> Self {
|
||||||
let chat = ChatRegistry::global(cx);
|
let chat = ChatRegistry::global(cx);
|
||||||
let communities = CommunityRegistry::global(cx);
|
let communities = CommunityRegistry::global(cx);
|
||||||
|
let nostr = NostrRegistry::global(cx);
|
||||||
|
|
||||||
let mut subscriptions = smallvec![];
|
let mut subscriptions = smallvec![];
|
||||||
|
|
||||||
@@ -85,6 +115,16 @@ impl Sidebar {
|
|||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
|
subscriptions.push(
|
||||||
|
cx.subscribe_in(&nostr, window, |this, _nostr, event, _window, cx| {
|
||||||
|
// Re-pick the background each time the signed-out screen is shown.
|
||||||
|
if let StateEvent::NoSigner = event {
|
||||||
|
this.banner = pick_banner();
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
scroll_handles: [
|
scroll_handles: [
|
||||||
@@ -93,6 +133,7 @@ impl Sidebar {
|
|||||||
],
|
],
|
||||||
community_scroll: UniformListScrollHandle::new(),
|
community_scroll: UniformListScrollHandle::new(),
|
||||||
dock,
|
dock,
|
||||||
|
banner: pick_banner(),
|
||||||
active_tab: SidebarTab::Inbox,
|
active_tab: SidebarTab::Inbox,
|
||||||
community: None,
|
community: None,
|
||||||
channels_open: true,
|
channels_open: true,
|
||||||
@@ -381,6 +422,7 @@ impl Sidebar {
|
|||||||
let (banner, rows) = {
|
let (banner, rows) = {
|
||||||
let community = community.read(cx);
|
let community = community.read(cx);
|
||||||
let owner = community.state().owner;
|
let owner = community.state().owner;
|
||||||
|
|
||||||
let mut admins = Vec::new();
|
let mut admins = Vec::new();
|
||||||
let mut members = Vec::new();
|
let mut members = Vec::new();
|
||||||
|
|
||||||
@@ -412,25 +454,26 @@ impl Sidebar {
|
|||||||
|
|
||||||
rows.push(CommunityRow::Section(CommunitySection::Admins));
|
rows.push(CommunityRow::Section(CommunitySection::Admins));
|
||||||
if self.admins_open {
|
if self.admins_open {
|
||||||
rows.extend(admins.into_iter().map(|public_key| CommunityRow::Member {
|
rows.extend(
|
||||||
prefix: "community-admin",
|
admins
|
||||||
public_key,
|
.into_iter()
|
||||||
}));
|
.map(|public_key| CommunityRow::Member { public_key }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
rows.push(CommunityRow::Section(CommunitySection::Members));
|
rows.push(CommunityRow::Section(CommunitySection::Members));
|
||||||
if self.members_open {
|
if self.members_open {
|
||||||
rows.extend(members.into_iter().map(|public_key| CommunityRow::Member {
|
rows.extend(
|
||||||
prefix: "member",
|
members
|
||||||
public_key,
|
.into_iter()
|
||||||
}));
|
.map(|public_key| CommunityRow::Member { public_key }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
(banner, rows)
|
(banner, rows)
|
||||||
};
|
};
|
||||||
|
|
||||||
let rows = Rc::new(rows);
|
let rows = Rc::new(rows);
|
||||||
let scroll_handle = &self.community_scroll;
|
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
@@ -439,7 +482,7 @@ impl Sidebar {
|
|||||||
.gap_2()
|
.gap_2()
|
||||||
.when_some(banner, |this, banner| {
|
.when_some(banner, |this, banner| {
|
||||||
this.child(
|
this.child(
|
||||||
div().px_2().child(
|
div().px_2().flex_shrink_0().child(
|
||||||
img(banner)
|
img(banner)
|
||||||
.w_full()
|
.w_full()
|
||||||
.h_20()
|
.h_20()
|
||||||
@@ -460,11 +503,11 @@ impl Sidebar {
|
|||||||
this.render_community_rows(range, rows.as_slice(), &community, cx)
|
this.render_community_rows(range, rows.as_slice(), &community, cx)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.track_scroll(scroll_handle)
|
.track_scroll(&self.community_scroll)
|
||||||
.h_full()
|
.h_full()
|
||||||
.px_2(),
|
.px_2(),
|
||||||
)
|
)
|
||||||
.child(Scrollbar::vertical(scroll_handle)),
|
.child(Scrollbar::vertical(&self.community_scroll)),
|
||||||
)
|
)
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
@@ -480,36 +523,14 @@ impl Sidebar {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(|row| match row {
|
.map(|row| match row {
|
||||||
CommunityRow::Section(CommunitySection::Channels) => self.section_row(
|
CommunityRow::Section(section) => self.section_row(section, cx),
|
||||||
"channels",
|
CommunityRow::Member { public_key } => self.member_row(public_key, cx),
|
||||||
"Channels",
|
|
||||||
self.channels_open,
|
|
||||||
|sidebar| sidebar.channels_open = !sidebar.channels_open,
|
|
||||||
cx,
|
|
||||||
),
|
|
||||||
CommunityRow::Section(CommunitySection::Admins) => self.section_row(
|
|
||||||
"admins",
|
|
||||||
"Admins",
|
|
||||||
self.admins_open,
|
|
||||||
|sidebar| sidebar.admins_open = !sidebar.admins_open,
|
|
||||||
cx,
|
|
||||||
),
|
|
||||||
CommunityRow::Section(CommunitySection::Members) => self.section_row(
|
|
||||||
"members",
|
|
||||||
"Members",
|
|
||||||
self.members_open,
|
|
||||||
|sidebar| sidebar.members_open = !sidebar.members_open,
|
|
||||||
cx,
|
|
||||||
),
|
|
||||||
CommunityRow::Channel {
|
CommunityRow::Channel {
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
private,
|
private,
|
||||||
selected,
|
selected,
|
||||||
} => self.channel_row(*id, name.clone(), *private, *selected, community, cx),
|
} => self.channel_row(*id, name.clone(), *private, *selected, community, cx),
|
||||||
CommunityRow::Member { prefix, public_key } => {
|
|
||||||
self.member_row(prefix, *public_key, cx)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
@@ -559,26 +580,30 @@ impl Sidebar {
|
|||||||
let dock = self.dock.clone();
|
let dock = self.dock.clone();
|
||||||
let room = room.clone();
|
let room = room.clone();
|
||||||
|
|
||||||
TreeRow::new(
|
Nav::new(SharedString::from(format!("room-{index}")))
|
||||||
ElementId::NamedInteger("tree-row".into(), index as u64),
|
.label(name)
|
||||||
TreeRowKind::Room,
|
.text_sm()
|
||||||
name,
|
.font_medium()
|
||||||
)
|
.when_some(nav_avatar(Some(seed), picture, cx), |this, avatar| {
|
||||||
.avatar(seed)
|
this.prefix(avatar)
|
||||||
.picture(picture)
|
})
|
||||||
.created_at(created_at)
|
.suffix(
|
||||||
// Run in an app context: docking a new panel reads every
|
div()
|
||||||
// docked panel's id, which would panic inside a sidebar update.
|
.font_normal()
|
||||||
.on_click(move |_event, window, cx| {
|
.text_xs()
|
||||||
ui::dock::add_panel_to(
|
.text_color(cx.theme().text_placeholder)
|
||||||
&dock,
|
.child(created_at),
|
||||||
PanelHandle::new(chat_ui::init(room.downgrade(), window, cx)),
|
)
|
||||||
DockPlacement::Center,
|
.on_click(move |_event, window, cx| {
|
||||||
window,
|
ui::dock::add_panel_to(
|
||||||
cx,
|
&dock,
|
||||||
);
|
PanelHandle::new(chat_ui::init(room.downgrade(), window, cx)),
|
||||||
})
|
DockPlacement::Center,
|
||||||
.into_any_element()
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.into_any_element()
|
||||||
}
|
}
|
||||||
SidebarRow::Community { community } => {
|
SidebarRow::Community { community } => {
|
||||||
let name = community.read(cx).name();
|
let name = community.read(cx).name();
|
||||||
@@ -588,58 +613,66 @@ impl Sidebar {
|
|||||||
let sidebar = cx.entity().downgrade();
|
let sidebar = cx.entity().downgrade();
|
||||||
let community = community.clone();
|
let community = community.clone();
|
||||||
|
|
||||||
TreeRow::new(
|
Nav::new(SharedString::from(format!("com-{index}")))
|
||||||
ElementId::NamedInteger("tree-row".into(), index as u64),
|
.label(name)
|
||||||
TreeRowKind::Community,
|
.text_sm()
|
||||||
name,
|
.when_some(nav_avatar(Some(seed), picture, cx), |this, avatar| {
|
||||||
)
|
this.prefix(avatar)
|
||||||
.avatar(seed)
|
})
|
||||||
.picture(picture)
|
.on_click(move |_event, window, cx| {
|
||||||
.on_click(move |_event, window, cx| {
|
ui::dock::add_panel_to(
|
||||||
ui::dock::add_panel_to(
|
&dock,
|
||||||
&dock,
|
PanelHandle::new(community_ui::init(
|
||||||
PanelHandle::new(community_ui::init(community.clone(), window, cx)),
|
community.clone(),
|
||||||
DockPlacement::Center,
|
window,
|
||||||
window,
|
cx,
|
||||||
cx,
|
)),
|
||||||
);
|
DockPlacement::Center,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
if let Err(error) = sidebar.update(cx, |this, cx| {
|
if let Err(error) = sidebar.update(cx, |this, cx| {
|
||||||
this.community = Some(community.downgrade());
|
this.community = Some(community.downgrade());
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}) {
|
}) {
|
||||||
log::error!("Failed to show community in sidebar: {error}");
|
log::error!("Failed to show community in sidebar: {error}");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn section_row(
|
fn section_row(&self, section: &CommunitySection, cx: &mut Context<Sidebar>) -> AnyElement {
|
||||||
&self,
|
let section = *section;
|
||||||
id: &'static str,
|
let (label, open) = match section {
|
||||||
label: &'static str,
|
CommunitySection::Channels => ("Channels", self.channels_open),
|
||||||
open: bool,
|
CommunitySection::Admins => ("Admins", self.admins_open),
|
||||||
toggle: impl Fn(&mut Sidebar) + 'static,
|
CommunitySection::Members => ("Members", self.members_open),
|
||||||
cx: &mut Context<Sidebar>,
|
};
|
||||||
) -> AnyElement {
|
let icon = if open {
|
||||||
div()
|
IconName::CaretDown
|
||||||
.flex_shrink_0()
|
} else {
|
||||||
.child(
|
IconName::CaretRight
|
||||||
TreeRow::new(ElementId::Name(id.into()), TreeRowKind::Section, label)
|
};
|
||||||
.icon(if open {
|
|
||||||
IconName::CaretDown
|
Nav::new(label)
|
||||||
} else {
|
.label(label)
|
||||||
IconName::CaretRight
|
.suffix(nav_icon(icon, cx))
|
||||||
})
|
.text_xs()
|
||||||
.on_click(cx.listener(move |this, _event, _window, cx| {
|
.font_semibold()
|
||||||
toggle(this);
|
.text_color(cx.theme().text_placeholder)
|
||||||
cx.notify();
|
.on_click(cx.listener(move |this, _ev, _window, cx| {
|
||||||
})),
|
match section {
|
||||||
)
|
CommunitySection::Channels => this.channels_open = !this.channels_open,
|
||||||
|
CommunitySection::Admins => this.admins_open = !this.admins_open,
|
||||||
|
CommunitySection::Members => this.members_open = !this.members_open,
|
||||||
|
}
|
||||||
|
cx.notify();
|
||||||
|
}))
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -653,50 +686,38 @@ impl Sidebar {
|
|||||||
cx: &mut Context<Sidebar>,
|
cx: &mut Context<Sidebar>,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
let community = community.clone();
|
let community = community.clone();
|
||||||
|
let icon = if private {
|
||||||
|
IconName::Lock
|
||||||
|
} else {
|
||||||
|
IconName::Hashtag
|
||||||
|
};
|
||||||
|
|
||||||
div()
|
Nav::new(id.to_hex())
|
||||||
.flex_shrink_0()
|
.label(name)
|
||||||
.rounded(cx.theme().radius)
|
.prefix(nav_icon(icon, cx))
|
||||||
.child(
|
.text_sm()
|
||||||
TreeRow::new(
|
.font_medium()
|
||||||
ElementId::Name(SharedString::from(format!(
|
.selected(selected)
|
||||||
"community-channel-{}",
|
.on_click(cx.listener(move |_this, _event, _window, cx| {
|
||||||
id.to_hex()
|
community.update(cx, |community, cx| {
|
||||||
))),
|
community.set_active_channel(id, cx);
|
||||||
TreeRowKind::Room,
|
});
|
||||||
name,
|
cx.notify();
|
||||||
)
|
}))
|
||||||
.icon(if private {
|
|
||||||
IconName::Lock
|
|
||||||
} else {
|
|
||||||
IconName::Message
|
|
||||||
})
|
|
||||||
.on_click(cx.listener(move |_this, _event, _window, cx| {
|
|
||||||
community.update(cx, |community, cx| community.set_active_channel(id, cx));
|
|
||||||
cx.notify();
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.when(selected, |this| this.bg(cx.theme().ghost_element_active))
|
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn member_row(&self, prefix: &str, public_key: PublicKey, cx: &App) -> AnyElement {
|
fn member_row(&self, public_key: &PublicKey, cx: &App) -> AnyElement {
|
||||||
let persons = PersonRegistry::global(cx);
|
let persons = PersonRegistry::global(cx);
|
||||||
let person = persons.read(cx).get(&public_key, cx);
|
let person = persons.read(cx).get(public_key, cx);
|
||||||
|
|
||||||
div()
|
Nav::new(public_key.to_hex())
|
||||||
.flex_shrink_0()
|
.label(person.name())
|
||||||
.child(
|
.text_sm()
|
||||||
TreeRow::new(
|
.font_medium()
|
||||||
ElementId::Name(SharedString::from(format!(
|
.when_some(
|
||||||
"{prefix}-{}",
|
nav_avatar(Some(person.avatar_seed()), person.avatar(), cx),
|
||||||
public_key.to_hex()
|
|this, avatar| this.prefix(avatar),
|
||||||
))),
|
|
||||||
TreeRowKind::Room,
|
|
||||||
person.name(),
|
|
||||||
)
|
|
||||||
.avatar(person.avatar_seed())
|
|
||||||
.picture(person.avatar()),
|
|
||||||
)
|
)
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
@@ -770,27 +791,32 @@ impl Render for Sidebar {
|
|||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
))
|
))
|
||||||
|
.child(
|
||||||
|
div().absolute().inset_0().child(
|
||||||
|
img(self.banner.clone())
|
||||||
|
.size_full()
|
||||||
|
.object_fit(ObjectFit::Cover),
|
||||||
|
),
|
||||||
|
)
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
.justify_end()
|
.justify_end()
|
||||||
.gap_4()
|
.gap_4()
|
||||||
.p_4()
|
.p_4()
|
||||||
|
.child(img("brand/headline.png").max_w_48())
|
||||||
.child(
|
.child(
|
||||||
v_flex().child(div().font_semibold().child("Coop")).child(
|
Button::new("import")
|
||||||
div()
|
.label("Import Identity")
|
||||||
.text_xs()
|
.custom(
|
||||||
.text_color(cx.theme().text_muted)
|
ButtonCustomVariant::new(window, cx)
|
||||||
.child("TODO"),
|
.color(gpui::white())
|
||||||
),
|
.foreground(gpui::black())
|
||||||
)
|
.hover(gpui::white().opacity(0.9))
|
||||||
.child(
|
.active(gpui::white().opacity(0.8)),
|
||||||
Button::new("import-identity")
|
)
|
||||||
.label("Import identity")
|
.large()
|
||||||
.primary()
|
|
||||||
.font_semibold()
|
.font_semibold()
|
||||||
.h_8()
|
|
||||||
.w_full()
|
|
||||||
.on_click(|_, window, cx| {
|
.on_click(|_, window, cx| {
|
||||||
import::open(window, cx);
|
import::open(window, cx);
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,221 +0,0 @@
|
|||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use chat::Room;
|
|
||||||
use community::{ChannelId, Community};
|
|
||||||
use gpui::prelude::FluentBuilder;
|
|
||||||
use gpui::{
|
|
||||||
App, ClickEvent, ElementId, Entity, ImageSource, InteractiveElement, IntoElement,
|
|
||||||
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window, div, px,
|
|
||||||
};
|
|
||||||
use nostr_sdk::prelude::PublicKey;
|
|
||||||
use settings::AppSettings;
|
|
||||||
use theme::ActiveTheme;
|
|
||||||
use ui::avatar::{Avatar, PixelAvatar};
|
|
||||||
use ui::{Icon, IconName, Selectable, Sizable, StyledExt, h_flex};
|
|
||||||
|
|
||||||
pub enum SidebarRow {
|
|
||||||
Room { room: Entity<Room> },
|
|
||||||
Community { community: Entity<Community> },
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A collapsible group of rows in the sidebar's community view.
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum CommunitySection {
|
|
||||||
Channels,
|
|
||||||
Admins,
|
|
||||||
Members,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A row in the sidebar's community view.
|
|
||||||
pub enum CommunityRow {
|
|
||||||
Section(CommunitySection),
|
|
||||||
Channel {
|
|
||||||
id: ChannelId,
|
|
||||||
name: SharedString,
|
|
||||||
private: bool,
|
|
||||||
selected: bool,
|
|
||||||
},
|
|
||||||
Member {
|
|
||||||
prefix: &'static str,
|
|
||||||
public_key: PublicKey,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub enum TreeRowKind {
|
|
||||||
Section,
|
|
||||||
Room,
|
|
||||||
Community,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
pub struct TreeRow {
|
|
||||||
id: ElementId,
|
|
||||||
kind: TreeRowKind,
|
|
||||||
label: SharedString,
|
|
||||||
avatar: Option<SharedString>,
|
|
||||||
picture: Option<ImageSource>,
|
|
||||||
icon: Option<IconName>,
|
|
||||||
created_at: Option<SharedString>,
|
|
||||||
selected: bool,
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TreeRow {
|
|
||||||
pub fn new(
|
|
||||||
id: impl Into<ElementId>,
|
|
||||||
kind: TreeRowKind,
|
|
||||||
label: impl Into<SharedString>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
id: id.into(),
|
|
||||||
kind,
|
|
||||||
label: label.into(),
|
|
||||||
avatar: None,
|
|
||||||
picture: None,
|
|
||||||
icon: None,
|
|
||||||
created_at: None,
|
|
||||||
selected: false,
|
|
||||||
on_click: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the seed for the row's generated avatar.
|
|
||||||
pub fn avatar(mut self, seed: impl Into<SharedString>) -> Self {
|
|
||||||
self.avatar = Some(seed.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shows `picture` instead of the generated avatar.
|
|
||||||
pub fn picture(mut self, picture: Option<impl Into<ImageSource>>) -> Self {
|
|
||||||
self.picture = picture.map(Into::into);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shows `icon` in the avatar slot when the row has no avatar or picture.
|
|
||||||
pub fn icon(mut self, icon: IconName) -> Self {
|
|
||||||
self.icon = Some(icon);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn created_at(mut self, created_at: impl Into<SharedString>) -> Self {
|
|
||||||
self.created_at = Some(created_at.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_click(
|
|
||||||
mut self,
|
|
||||||
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
|
||||||
) -> Self {
|
|
||||||
self.on_click = Some(Rc::new(handler));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Selectable for TreeRow {
|
|
||||||
fn selected(mut self, selected: bool) -> Self {
|
|
||||||
self.selected = selected;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_selected(&self) -> bool {
|
|
||||||
self.selected
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for TreeRow {
|
|
||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
||||||
let hide_avatar = AppSettings::get_hide_avatar(cx);
|
|
||||||
|
|
||||||
let is_section = self.kind == TreeRowKind::Section;
|
|
||||||
let is_room = self.kind == TreeRowKind::Room;
|
|
||||||
let is_community = self.kind == TreeRowKind::Community;
|
|
||||||
let is_selected = self.selected;
|
|
||||||
|
|
||||||
let avatar = if hide_avatar {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
match (self.avatar, self.picture) {
|
|
||||||
(None, None) => None,
|
|
||||||
(seed, Some(picture)) => Some(
|
|
||||||
Avatar::from_source(picture)
|
|
||||||
.when_some(seed, |avatar, seed| avatar.seed(seed))
|
|
||||||
.small()
|
|
||||||
.flex_shrink_0()
|
|
||||||
.into_any_element(),
|
|
||||||
),
|
|
||||||
(Some(seed), None) => Some(
|
|
||||||
PixelAvatar::new(seed)
|
|
||||||
.small()
|
|
||||||
.flex_shrink_0()
|
|
||||||
.into_any_element(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let avatar = avatar.or_else(|| {
|
|
||||||
self.icon.map(|icon| {
|
|
||||||
h_flex()
|
|
||||||
.flex_shrink_0()
|
|
||||||
.w(px(20.))
|
|
||||||
.justify_center()
|
|
||||||
.text_color(cx.theme().icon_muted)
|
|
||||||
.child(Icon::new(icon).small())
|
|
||||||
.into_any_element()
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
h_flex()
|
|
||||||
.id(self.id)
|
|
||||||
.w_full()
|
|
||||||
.h_10()
|
|
||||||
.px_2()
|
|
||||||
.gap_2()
|
|
||||||
.rounded(cx.theme().radius)
|
|
||||||
.when(is_section, |this| {
|
|
||||||
this.text_xs()
|
|
||||||
.text_color(cx.theme().text_placeholder)
|
|
||||||
.font_semibold()
|
|
||||||
})
|
|
||||||
.when(is_room || is_community, |this| this.text_sm())
|
|
||||||
.when_some(avatar, |this, avatar| this.child(avatar))
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_1()
|
|
||||||
.flex_1()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.truncate()
|
|
||||||
.min_w_0()
|
|
||||||
.when(is_room, |this| this.font_medium())
|
|
||||||
.child(self.label),
|
|
||||||
)
|
|
||||||
.child(div().flex_1())
|
|
||||||
.when(is_selected, |this| {
|
|
||||||
this.child(
|
|
||||||
Icon::new(IconName::CheckCircle)
|
|
||||||
.small()
|
|
||||||
.flex_shrink_0()
|
|
||||||
.text_color(cx.theme().icon_accent),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.when_some(self.created_at, |this, created_at| {
|
|
||||||
this.child(
|
|
||||||
div()
|
|
||||||
.flex_shrink_0()
|
|
||||||
.text_color(cx.theme().text_placeholder)
|
|
||||||
.text_xs()
|
|
||||||
.child(created_at),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.when_some(self.on_click, |this, handler| {
|
|
||||||
this.cursor_pointer()
|
|
||||||
.when(!is_section, |this| {
|
|
||||||
this.hover(|this| this.bg(cx.theme().ghost_element_hover))
|
|
||||||
})
|
|
||||||
.on_click(move |event, window, cx| handler(event, window, cx))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
use gpui::prelude::FluentBuilder as _;
|
||||||
|
use gpui::{AnyElement, App, ImageSource, IntoElement, ParentElement, SharedString, Styled, px};
|
||||||
|
use settings::AppSettings;
|
||||||
|
use theme::ActiveTheme;
|
||||||
|
use ui::avatar::{Avatar, PixelAvatar};
|
||||||
|
use ui::{Icon, IconName, Sizable, h_flex};
|
||||||
|
|
||||||
|
pub(crate) fn nav_avatar(
|
||||||
|
seed: Option<impl Into<SharedString>>,
|
||||||
|
picture: Option<impl Into<ImageSource>>,
|
||||||
|
cx: &App,
|
||||||
|
) -> Option<AnyElement> {
|
||||||
|
if AppSettings::get_hide_avatar(cx) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match (seed.map(Into::into), picture.map(Into::into)) {
|
||||||
|
(None, None) => None,
|
||||||
|
(seed, Some(picture)) => Some(
|
||||||
|
Avatar::from_source(picture)
|
||||||
|
.when_some(seed, |avatar, seed| avatar.seed(seed))
|
||||||
|
.small()
|
||||||
|
.flex_shrink_0()
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
(Some(seed), None) => Some(
|
||||||
|
PixelAvatar::new(seed)
|
||||||
|
.small()
|
||||||
|
.flex_shrink_0()
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn nav_icon(icon: IconName, cx: &App) -> AnyElement {
|
||||||
|
h_flex()
|
||||||
|
.flex_shrink_0()
|
||||||
|
.w(px(20.))
|
||||||
|
.justify_center()
|
||||||
|
.text_color(cx.theme().icon_muted)
|
||||||
|
.child(Icon::new(icon).small())
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Brand backgrounds shown behind the signed-out screen; one is picked at random.
|
||||||
|
const SIGNED_OUT_BANNERS: [&str; 2] = ["brand/bg1.jpg", "brand/bg2.jpg"];
|
||||||
|
|
||||||
|
/// Pick one of the signed-out backgrounds at random.
|
||||||
|
pub(crate) fn pick_banner() -> SharedString {
|
||||||
|
let nanos = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.map(|elapsed| elapsed.subsec_nanos())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let index = nanos as usize % SIGNED_OUT_BANNERS.len();
|
||||||
|
SIGNED_OUT_BANNERS[index].into()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user