3 Commits
Author SHA1 Message Date
reya 273ddabde5 clean up the sidebar 2026-09-23 14:54:34 +07:00
reya 7ba289440b refactor panel open flow 2026-09-23 08:53:20 +07:00
reya 6510e45e4e update deps 2026-09-23 08:21:37 +07:00
14 changed files with 829 additions and 1024 deletions
Generated
+194 -105
View File
@@ -987,9 +987,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.4.6"
version = "1.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3eb0f42d6c360dc3f8a821f6bf2fdea7f72bfd36b3076eb0e6d1e9e0752fff4"
checksum = "54413ede23c2daf518f35156dfde027feb2374004d63bd497f983c8db9c0e313"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -1339,7 +1339,7 @@ dependencies = [
"hmac 0.12.1",
"nostr",
"nostr-sdk",
"rand 0.10.2",
"rand 0.10.3",
"serde",
"serde_json",
"sha2 0.10.9",
@@ -2190,9 +2190,9 @@ dependencies = [
[[package]]
name = "find-msvc-tools"
version = "0.1.12"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
checksum = "ef25905e51abafe4dcea6c15fec58c57b601cdbd0ee53d22ea1d3016c587d39b"
[[package]]
name = "fixedbitset"
@@ -2711,14 +2711,16 @@ dependencies = [
[[package]]
name = "gpui-base"
version = "0.6.1"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d45dcaaeac889bf1e7757db1beb26c9043c8ea3156651facc11c6be56bb6722"
checksum = "c9e904ee0d351f4e85b967e26ae986d3aa195a8d9d35fcd18184a2c40989694e"
dependencies = [
"aho-corasick",
"anyhow",
"ashpd",
"async-channel",
"chrono",
"data-url",
"futures",
"gpui-pre",
"gpui-pre-macros",
@@ -2742,13 +2744,14 @@ dependencies = [
"tracing",
"unicode-segmentation",
"web-time",
"windows 0.58.0",
]
[[package]]
name = "gpui-pre"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a9ca98fdcad06276de623c2f48ebd710f1e7724eebef2f2673c583e85dc8335"
checksum = "a0437c0b83e636a92bd1a39fa1d05fb632ae671289537497b35871ffbe231b84"
dependencies = [
"accesskit",
"anyhow",
@@ -2774,7 +2777,6 @@ dependencies = [
"gpui-pre-sum-tree",
"gpui-pre-util",
"gpui-pre-util-macros",
"gpui-pre-ztracing",
"heapless 0.9.3",
"image",
"inventory",
@@ -2782,6 +2784,8 @@ dependencies = [
"log",
"lyon",
"num_cpus",
"objc2-core-foundation",
"objc2-core-video",
"parking",
"parking_lot",
"pin-project",
@@ -2802,7 +2806,6 @@ dependencies = [
"strum",
"taffy",
"thiserror 2.0.20",
"tracing",
"ttf-parser",
"url",
"usvg",
@@ -2816,9 +2819,9 @@ dependencies = [
[[package]]
name = "gpui-pre-apple"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fc4da82c5c2dbb25443655cfbc4305a2e2d1380bc0b815e80664fdfbf4cfdb"
checksum = "3af534f0746eb9e014114bcc0ee00dc250c4b8d75df2e437180c403aa65649c1"
dependencies = [
"anyhow",
"block",
@@ -2830,7 +2833,6 @@ dependencies = [
"etagere",
"foreign-types",
"gpui-pre",
"gpui-pre-collections",
"image",
"log",
"metal",
@@ -2841,9 +2843,9 @@ dependencies = [
[[package]]
name = "gpui-pre-collections"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "626a09f683ff6f3b2f708341bdcea9d479af5b14a6a2fccec9cff935ec0751b7"
checksum = "15e7a65204039187d1f4b3d4c2e0233eeac54d4acb0b13f9dc1a3b5adad26c1f"
dependencies = [
"gpui-pre-util",
"indexmap",
@@ -2852,9 +2854,9 @@ dependencies = [
[[package]]
name = "gpui-pre-derive-refineable"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56d06116a2b857588f6ea7bfb447b3554ba985e780b5995ae47780252da152f3"
checksum = "79dfb4a05f3cd9893d7768dbf5dba1bbbe99cab7394e89b360ccb3b2bcb6b34e"
dependencies = [
"proc-macro2",
"quote",
@@ -2863,9 +2865,9 @@ dependencies = [
[[package]]
name = "gpui-pre-http-client"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0d2071c582c1a0f71b8c7c00607a3b9cd18f81fb3510d41845db3447b2a11c9"
checksum = "cfd5a7542c77887a5aff997d78c95884119c8463409569b63fcf060956da1682"
dependencies = [
"anyhow",
"async-compression",
@@ -2884,9 +2886,9 @@ dependencies = [
[[package]]
name = "gpui-pre-http-client-tls"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b6f3936802580259d5af9bcb2ab37fb0d7002caeed449eaabb007c62aea7341"
checksum = "72aed510adb5e7723f8f57b9751007e3451d46a84edb59b92227178cf59cc19c"
dependencies = [
"log",
"rustls",
@@ -2896,9 +2898,9 @@ dependencies = [
[[package]]
name = "gpui-pre-linux"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6b427589681ec1fed27951fd82edc70a7e288bb2487ede26dbbd07c2ce23d4f"
checksum = "aab655cda1d6dcd549ac869e8678babc84cf573ab6794a1fe1a39c6c70c60b48"
dependencies = [
"accesskit",
"accesskit_unix",
@@ -2943,15 +2945,14 @@ dependencies = [
[[package]]
name = "gpui-pre-macos"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "806eac0cb5c0eebc0b032c6450c11d283264f9ac05754dda83fc80d0b86b4ee8"
checksum = "951d17e41a72067ad2d35c60e08a62e8ad7fa9400b41c802e26cfb459f6c05da"
dependencies = [
"accesskit",
"accesskit_macos",
"anyhow",
"async-task",
"block",
"block2 0.6.2",
"cocoa 0.26.1",
"core-foundation 0.10.1",
@@ -2965,17 +2966,17 @@ dependencies = [
"gpui-pre",
"gpui-pre-apple",
"gpui-pre-collections",
"gpui-pre-media",
"gpui-pre-util",
"image",
"itertools 0.14.0",
"libc",
"log",
"mach2",
"metal",
"objc",
"objc2 0.6.4",
"objc2-app-kit 0.3.2",
"objc2-core-graphics",
"objc2-core-media",
"objc2-foundation 0.3.2",
"objc2-screen-capture-kit",
"objc2-user-notifications",
@@ -2991,9 +2992,9 @@ dependencies = [
[[package]]
name = "gpui-pre-macros"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06139051bf1db51949bfbb4ee338208bd64b4c35340d8c1a6a8200744e28fe1"
checksum = "6324092b40ea8ec7c327a4a28427e366a489b4d5d23b5d1bd0243fc2f4ca7a9e"
dependencies = [
"heck 0.5.0",
"proc-macro-crate",
@@ -3002,26 +3003,11 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "gpui-pre-media"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "500acb13f818ff234021c1b9eae8592b0cbe017a64e26908c736b7421b1c6213"
dependencies = [
"anyhow",
"bindgen",
"core-foundation 0.10.1",
"core-video",
"foreign-types",
"metal",
"objc",
]
[[package]]
name = "gpui-pre-perf"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0973bbb02bf46940ac0861d9dce0fc03a3109634172b69108d1bc13fa29d76c"
checksum = "b94bff26ccd8105f6206144f8dab16a2dc02e7a68f39dada02cdf492f0afd940"
dependencies = [
"gpui-pre-collections",
"serde",
@@ -3030,9 +3016,9 @@ dependencies = [
[[package]]
name = "gpui-pre-platform"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b613512e29f1e7b825bfa7e50a447388ff1347044350a475e4c98a892a985c8d"
checksum = "8423d1e0a693d2f9f16c0326bb01b5e81ec0531854d596a18476934ac6880478"
dependencies = [
"console_error_panic_hook",
"gpui-pre",
@@ -3044,9 +3030,9 @@ dependencies = [
[[package]]
name = "gpui-pre-refineable"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "428f23703ead5601618ffea73c137897b98a94ce8d30e257f22be947732dfbf9"
checksum = "93b4b0a8d296a3ee5beb1136bc48f8167a6028eb1e243b56851dcd8c9fc6d7a1"
dependencies = [
"gpui-pre-derive-refineable",
]
@@ -3103,9 +3089,9 @@ dependencies = [
[[package]]
name = "gpui-pre-reqwest-client"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05d4e3d2eb2ab777597879e46047247447b9d93cef568e63d545c9deb1b3e57"
checksum = "8f32ccea5d1a7c8d2868d7b346c2c1f76aa3f86ec9e0575c4e7340af3afd9e9d"
dependencies = [
"anyhow",
"bytes",
@@ -3121,9 +3107,9 @@ dependencies = [
[[package]]
name = "gpui-pre-scheduler"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dd1b7ff2e7bf43302ae982cd786a22c16c52e16682e67b66cfbe2cb2bc3a72d"
checksum = "ff7b10b3d2fc3fda24847a04436eca9531612c8414874a37ddc005f20e521e12"
dependencies = [
"async-task",
"backtrace",
@@ -3138,9 +3124,9 @@ dependencies = [
[[package]]
name = "gpui-pre-shared-string"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "424a4dd6a570473d57acf1df2bfb9f3028d4b568abcaeb9252e0f13047b50c03"
checksum = "fe17f81bd98a0253140ea79f7f76442d7499bd8d17db302861af8e46c11d38df"
dependencies = [
"schemars",
"serde",
@@ -3149,9 +3135,9 @@ dependencies = [
[[package]]
name = "gpui-pre-sum-tree"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4855718e82630d29198b515d2b027361ef63dd19b6021cefe945789dcff82372"
checksum = "0a6d35e998a51d24248dad731de1f09d386cbbf46a04edde3b67f469b2ce3f16"
dependencies = [
"gpui-pre-ztracing",
"heapless 0.9.3",
@@ -3162,9 +3148,9 @@ dependencies = [
[[package]]
name = "gpui-pre-util"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e973148165346c0f9b3f5072b321d7c2f33807c93daea67fe298c005c0e0c2a8"
checksum = "6a026a30c578930d20974fd8c3d77de36223ac7ba193bd4259e7238929bc8806"
dependencies = [
"anyhow",
"log",
@@ -3173,9 +3159,9 @@ dependencies = [
[[package]]
name = "gpui-pre-util-macros"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f71f6212d3772a70988c7d64627c7284dec73c17992ac4f9f0cdfdd46cbb677"
checksum = "5cbf79627a6afc626d4e887dad9d2d92bb069413e8cc692ba3e90bff219eb7a9"
dependencies = [
"gpui-pre-perf",
"quote",
@@ -3184,9 +3170,9 @@ dependencies = [
[[package]]
name = "gpui-pre-web"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f0daada903f106be946d87e25f5bc85ad6cf60d6d82cb8f8aed945417a9c1a3"
checksum = "34be433b9fcb370b1fe9a1c7fe911f0eb6058791ef8167dcf6c5a5deb624975c"
dependencies = [
"anyhow",
"console_error_panic_hook",
@@ -3213,9 +3199,9 @@ dependencies = [
[[package]]
name = "gpui-pre-wgpu"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "640b666a16ddf9504e2eb3e7a997acb2b2adfaa7859925bdd36fc3acfd8f30a7"
checksum = "05515b32799b1767d69300921c1b562cd53104965211a0e8905acdc7aa18e693"
dependencies = [
"anyhow",
"bytemuck",
@@ -3240,9 +3226,9 @@ dependencies = [
[[package]]
name = "gpui-pre-windows"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c12c7d84ec422feb2f064117d572447998381e5c219fcad69fe19f10d0b7aaea"
checksum = "27662e65bfcf4445d2cccfad91f1570ec07f4b1b5de98400a9f207fd19c464cf"
dependencies = [
"accesskit",
"accesskit_windows",
@@ -3269,9 +3255,9 @@ dependencies = [
[[package]]
name = "gpui-pre-zlog"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98352858fac3d4d05b46d6014ad04bad313652a691d4e21dc55fa96fb4acb7cf"
checksum = "f3a5af0b734a391a84872eb770b3ee9830091ec2487b79d06371fe14eefb2b5d"
dependencies = [
"anyhow",
"chrono",
@@ -3281,9 +3267,9 @@ dependencies = [
[[package]]
name = "gpui-pre-ztracing"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d63a3c7276875b4d7e6d3961e92a138e9233e86a591bf318f2e54df4ad58102"
checksum = "a24cb19f78c5bc3aab8ddf1e8e0bd6b402fbcb9c7f112f64ddbedef8f8768a59"
dependencies = [
"gpui-pre-zlog",
"gpui-pre-ztracing-macro",
@@ -3293,9 +3279,9 @@ dependencies = [
[[package]]
name = "gpui-pre-ztracing-macro"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84e2a5ff79002223edfcfa76c6f9f79f4f60f6ec545ccc27832f12bd46348b39"
checksum = "a1bb25850ad31b88bbdef454afb93ec4cdb81f5e200ba5b3f1a0368111780d2c"
[[package]]
name = "gpui-updater-core"
@@ -3621,9 +3607,9 @@ dependencies = [
[[package]]
name = "hyper-rustls"
version = "0.27.9"
version = "0.27.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53"
dependencies = [
"http",
"hyper",
@@ -4131,9 +4117,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
name = "libredox"
version = "0.1.24"
version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6480ccc157a1389bb2e4891b24751b0f798ba640d22386f23143fbcc89da195a"
checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d"
dependencies = [
"libc",
]
@@ -4617,8 +4603,8 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
[[package]]
name = "nostr"
version = "0.45.4"
source = "git+https://github.com/rust-nostr/nostr#b230cecf9dbb38e0228e6fff4544ed9d261326fc"
version = "0.45.5"
source = "git+https://github.com/rust-nostr/nostr#01f1950c671b35f3d1186ca907373dd86ee3b336"
dependencies = [
"aes",
"base64 0.22.1",
@@ -4630,7 +4616,7 @@ dependencies = [
"chacha20poly1305",
"faster-hex",
"opaquerr 0.2.0",
"rand 0.10.2",
"rand 0.10.3",
"scrypt",
"secp256k1",
"serde",
@@ -4643,8 +4629,8 @@ dependencies = [
[[package]]
name = "nostr-blossom"
version = "0.45.0"
source = "git+https://github.com/rust-nostr/nostr#b230cecf9dbb38e0228e6fff4544ed9d261326fc"
version = "0.45.1"
source = "git+https://github.com/rust-nostr/nostr#01f1950c671b35f3d1186ca907373dd86ee3b336"
dependencies = [
"base64 0.22.1",
"bitcoin_hashes 1.2.0",
@@ -4656,8 +4642,8 @@ dependencies = [
[[package]]
name = "nostr-connect"
version = "0.45.1"
source = "git+https://github.com/rust-nostr/nostr#b230cecf9dbb38e0228e6fff4544ed9d261326fc"
version = "0.45.2"
source = "git+https://github.com/rust-nostr/nostr#01f1950c671b35f3d1186ca907373dd86ee3b336"
dependencies = [
"async-utility",
"futures-core",
@@ -4670,8 +4656,8 @@ dependencies = [
[[package]]
name = "nostr-database"
version = "0.45.1"
source = "git+https://github.com/rust-nostr/nostr#b230cecf9dbb38e0228e6fff4544ed9d261326fc"
version = "0.45.2"
source = "git+https://github.com/rust-nostr/nostr#01f1950c671b35f3d1186ca907373dd86ee3b336"
dependencies = [
"nostr",
"opaquerr 0.2.0",
@@ -4679,8 +4665,8 @@ dependencies = [
[[package]]
name = "nostr-gossip"
version = "0.45.0"
source = "git+https://github.com/rust-nostr/nostr#b230cecf9dbb38e0228e6fff4544ed9d261326fc"
version = "0.45.1"
source = "git+https://github.com/rust-nostr/nostr#01f1950c671b35f3d1186ca907373dd86ee3b336"
dependencies = [
"nostr",
"opaquerr 0.2.0",
@@ -4688,8 +4674,8 @@ dependencies = [
[[package]]
name = "nostr-gossip-memory"
version = "0.45.0"
source = "git+https://github.com/rust-nostr/nostr#b230cecf9dbb38e0228e6fff4544ed9d261326fc"
version = "0.45.1"
source = "git+https://github.com/rust-nostr/nostr#01f1950c671b35f3d1186ca907373dd86ee3b336"
dependencies = [
"indexmap",
"lru",
@@ -4700,8 +4686,8 @@ dependencies = [
[[package]]
name = "nostr-lmdb"
version = "0.45.2"
source = "git+https://github.com/rust-nostr/nostr#b230cecf9dbb38e0228e6fff4544ed9d261326fc"
version = "0.45.3"
source = "git+https://github.com/rust-nostr/nostr#01f1950c671b35f3d1186ca907373dd86ee3b336"
dependencies = [
"async-utility",
"flatbuffers",
@@ -4715,8 +4701,8 @@ dependencies = [
[[package]]
name = "nostr-memory"
version = "0.45.1"
source = "git+https://github.com/rust-nostr/nostr#b230cecf9dbb38e0228e6fff4544ed9d261326fc"
version = "0.45.2"
source = "git+https://github.com/rust-nostr/nostr#01f1950c671b35f3d1186ca907373dd86ee3b336"
dependencies = [
"btreecap",
"nostr",
@@ -4726,8 +4712,8 @@ dependencies = [
[[package]]
name = "nostr-sdk"
version = "0.45.2"
source = "git+https://github.com/rust-nostr/nostr#b230cecf9dbb38e0228e6fff4544ed9d261326fc"
version = "0.45.4"
source = "git+https://github.com/rust-nostr/nostr#01f1950c671b35f3d1186ca907373dd86ee3b336"
dependencies = [
"async-utility",
"async-wsocket",
@@ -4739,7 +4725,7 @@ dependencies = [
"nostr-database",
"nostr-gossip",
"opaquerr 0.2.0",
"rand 0.10.2",
"rand 0.10.3",
"tokio",
"tokio-stream",
"tracing",
@@ -4990,6 +4976,28 @@ dependencies = [
"objc2-foundation 0.3.2",
]
[[package]]
name = "objc2-core-audio"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2"
dependencies = [
"dispatch2",
"objc2 0.6.4",
"objc2-core-audio-types",
"objc2-core-foundation",
]
[[package]]
name = "objc2-core-audio-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c"
dependencies = [
"bitflags 2.13.2",
"objc2 0.6.4",
]
[[package]]
name = "objc2-core-data"
version = "0.2.2"
@@ -5069,6 +5077,21 @@ dependencies = [
"objc2-foundation 0.3.2",
]
[[package]]
name = "objc2-core-media"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05ec576860167a15dd9fce7fbee7512beb4e31f532159d3482d1f9c6caedf31d"
dependencies = [
"bitflags 2.13.2",
"dispatch2",
"objc2 0.6.4",
"objc2-core-audio",
"objc2-core-audio-types",
"objc2-core-foundation",
"objc2-core-video",
]
[[package]]
name = "objc2-core-text"
version = "0.3.2"
@@ -5193,8 +5216,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74b7c5390f477482f001bc354d6571a70db7e4f8d5288e860c45521fbce11394"
dependencies = [
"block2 0.6.2",
"dispatch2",
"objc2 0.6.4",
"objc2-core-graphics",
"objc2-core-media",
"objc2-foundation 0.3.2",
]
@@ -5902,7 +5927,7 @@ dependencies = [
"bytes",
"getrandom 0.4.3",
"lru-slab",
"rand 0.10.2",
"rand 0.10.3",
"rand_pcg",
"ring",
"rustc-hash 2.1.3",
@@ -5973,9 +5998,9 @@ dependencies = [
[[package]]
name = "rand"
version = "0.10.2"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af"
dependencies = [
"chacha20 0.10.2",
"getrandom 0.4.3",
@@ -8004,9 +8029,9 @@ checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e"
[[package]]
name = "unicode-id"
version = "0.3.6"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ba288e709927c043cbe476718d37be306be53fb1fafecd0dbe36d072be2580"
checksum = "65dd75ab2d9d379dbe86d4059e91d8b5a6e7054865eaba1ddbfc7a817ff96392"
[[package]]
name = "unicode-ident"
@@ -8790,6 +8815,16 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
dependencies = [
"windows-core 0.58.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows"
version = "0.61.3"
@@ -8858,6 +8893,19 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-core"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
dependencies = [
"windows-implement 0.58.0",
"windows-interface 0.58.0",
"windows-result 0.2.0",
"windows-strings 0.1.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-core"
version = "0.61.2"
@@ -8917,6 +8965,17 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "windows-implement"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
@@ -8939,6 +8998,17 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "windows-interface"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
@@ -9013,6 +9083,15 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-result"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-result"
version = "0.3.4"
@@ -9031,6 +9110,16 @@ dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "windows-strings"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
dependencies = [
"windows-result 0.2.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-strings"
version = "0.3.1"
+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<path d="M2.75 12C2.75 17.1086 6.89137 21.25 12 21.25C17.1086 21.25 21.25 17.1086 21.25 12C21.25 6.89137 17.1086 2.75 12 2.75C6.89137 2.75 2.75 6.89137 2.75 12Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M15.25 13L12 16.25L8.75 13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M12 7.75V15.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 571 B

+5 -30
View File
@@ -9,7 +9,7 @@ use fuzzy_matcher::FuzzyMatcher;
use fuzzy_matcher::skim::SkimMatcherV2;
use gpui::{
App, AppContext, Context, Entity, EventEmitter, Global, SharedString, Subscription, Task,
WeakEntity, Window,
WeakEntity,
};
use instant::Duration;
use nostr_sdk::prelude::*;
@@ -37,15 +37,8 @@ impl Global for GlobalChatRegistry {}
/// Chat event.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum ChatEvent {
/// An event to open a room by its ID
OpenRoom(u64),
/// An event to close a room by its ID
CloseRoom(u64),
/// An event to notify UI about a new chat request
Ping,
/// No Inbox Relays found, the app is not ready to subscribe messages
InboxRelayNotFound,
/// An error occurred
Error(String),
}
@@ -543,34 +536,16 @@ impl ChatRegistry {
cx.notify();
}
/// Emit an open room event.
///
/// If the room is new, add it to the registry.
pub fn emit_room(&mut self, room: &Entity<Room>, window: &mut Window, cx: &mut Context<Self>) {
// Get the room's ID.
/// Track a room so it is listed and can be looked up by id.
pub fn track_room(&mut self, room: &Entity<Room>, cx: &mut Context<Self>) {
let id = room.read(cx).id;
// If the room is new, add it to the registry and index.
if let hash_map::Entry::Vacant(e) = self.room_index.entry(id) {
let entity = room.to_owned();
e.insert(entity.clone());
self.rooms.insert(0, entity);
}
// Emit the open room event deferred to avoid re-entrant reads
cx.defer_in(window, move |_this, _window, cx| {
cx.emit(ChatEvent::OpenRoom(id));
});
}
/// Close a room.
pub fn close_room(&mut self, id: u64, window: &mut Window, cx: &mut Context<Self>) {
if self.room_index.contains_key(&id) {
self.room_index.remove(&id);
self.rooms.retain(|r| r.read(cx).id != id);
cx.defer_in(window, move |_this, _window, cx| {
cx.emit(ChatEvent::CloseRoom(id));
});
cx.notify();
}
}
-2
View File
@@ -107,8 +107,6 @@ impl SubscriptionKey {
#[derive(Debug, Clone)]
pub enum CommunityEvent {
Updated(CommunityId),
Open(CommunityId),
Close(CommunityId),
Channel(CommunityId, ChannelId),
/// History exists here that no held key can open.
Unreadable(CommunityId),
+1 -22
View File
@@ -7,7 +7,7 @@ pub use concord::cord03::{ChatMessage, ReplyRef};
use concord::state::CommunityState;
pub use concord::{ChannelId, CommunityId, Epoch};
use futures::future::{Either, select};
use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Subscription, Task, Window};
use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Subscription, Task};
use nostr_sdk::prelude::*;
use smallvec::{SmallVec, smallvec};
use state::NostrRegistry;
@@ -225,27 +225,6 @@ impl CommunityRegistry {
self.index.get(id).cloned()
}
/// Ask the workspace to open a community's panel.
pub fn emit_community(
&mut self,
community: &Entity<Community>,
window: &mut Window,
cx: &mut Context<Self>,
) {
let id = community.read(cx).id();
cx.defer_in(window, move |_this, _window, cx| {
cx.emit(CommunityEvent::Open(id));
});
}
/// Ask the workspace to close a community's panel.
pub fn emit_close(&mut self, id: CommunityId, window: &mut Window, cx: &mut Context<Self>) {
cx.defer_in(window, move |_this, _window, cx| {
cx.emit(CommunityEvent::Close(id));
});
}
/// Create a community owned by the current account and begin tracking it.
pub fn create(&mut self, metadata: CommunityMetadata, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
+9 -11
View File
@@ -145,21 +145,19 @@ impl CommunityPanel {
window,
|_this, _community, event, window, cx| {
match event {
CommunityEvent::Updated(_)
| CommunityEvent::Unreadable(_)
| CommunityEvent::Failed(_) => {
cx.defer_in(window, |this, window, cx| this.reload(window, cx));
}
CommunityEvent::Channel(..) => {
cx.defer_in(window, |this, window, cx| this.load(window, cx));
cx.defer_in(window, |this, window, cx| {
this.load(window, cx);
});
}
CommunityEvent::Error(error) => {
window.push_notification(
Notification::error(error.clone()).autohide(false),
cx,
);
window.push_notification(Notification::error(error.clone()), cx);
}
_ => {
cx.defer_in(window, |this, window, cx| {
this.reload(window, cx);
});
}
CommunityEvent::Open(_) | CommunityEvent::Close(_) => {}
};
},
));
+14 -15
View File
@@ -14,7 +14,7 @@ use gpui::{
pub use gpui_base::dock::{DockArea, DockLayout, DockPlacement};
use gpui_base::dock::{
DockAreaRenderer, DockContext, DragPanel, DropIndicator, InsertTarget, NodeId, PaneNode,
PaneRef, PanelId, TabGroupContext, TabGroupRenderer, TileContext, TilesRenderer,
PaneRef, PanelId, TabGroupContext, TabGroupRenderer,
};
use gpui_base::{Placement, ResizeHandleContext, Side};
use theme::{ActiveTheme, TABBAR_HEIGHT};
@@ -95,6 +95,19 @@ pub fn add_panel(
area.add_panel_view(Arc::new(panel), placement, None, window, cx);
}
/// Add a panel to a dock reached through a weak handle.
pub fn add_panel_to(
dock: &WeakEntity<DockArea>,
panel: PanelHandle,
placement: DockPlacement,
window: &mut Window,
cx: &mut App,
) {
let _ = dock.update(cx, |area, cx| {
add_panel(area, panel, placement, window, cx);
});
}
/// The panel in any region of `area` whose logical id is `key`.
fn find_panel(area: &DockArea, key: &SharedString, cx: &App) -> Option<(PanelId, NodeId, usize)> {
let placements = [
@@ -167,7 +180,6 @@ fn left_top_group(node: &PaneNode) -> Option<NodeId> {
match node.kind() {
PaneRef::Tabs { .. } => Some(node.id()),
PaneRef::Split { children, .. } => children.first().and_then(left_top_group),
PaneRef::Tiles { .. } => None,
}
}
@@ -182,7 +194,6 @@ fn right_top_group(node: &PaneNode) -> Option<NodeId> {
};
child.and_then(right_top_group)
}
PaneRef::Tiles { .. } => None,
}
}
@@ -263,18 +274,6 @@ impl DockAreaRenderer for DockSkin {
fn tab_group_renderer(&self) -> Rc<dyn TabGroupRenderer> {
Rc::new(TabGroupSkin::new(self.shared.clone()))
}
fn tiles_renderer(&self) -> Rc<dyn TilesRenderer> {
Rc::new(NoTiles)
}
}
struct NoTiles;
impl TilesRenderer for NoTiles {
fn render_drag_bar(&self, _: &TileContext, _: &mut Window, _: &mut App) -> AnyElement {
Empty.into_any_element()
}
}
/// The payload a dock's resize handle drags; the handle is the affordance.
-3
View File
@@ -19,9 +19,6 @@ pub enum PanelEvent {
pub trait Panel: EventEmitter<PanelEvent> + Render + Focusable {
/// The name of the panel used to serialize, deserialize and identify the panel.
///
/// This is used to identify the panel when deserializing the panel.
/// Once you have defined a panel id, this must not be changed.
fn panel_id(&self) -> SharedString;
/// The title of the panel
+2
View File
@@ -22,6 +22,7 @@ impl<T: IconNamed> From<T> for Icon {
pub enum IconName {
ArrowLeft,
ArrowRight,
ArrowDownCircle,
Boom,
Book,
ChevronDown,
@@ -99,6 +100,7 @@ impl IconNamed for IconName {
match self {
Self::ArrowLeft => "icons/arrow-left.svg",
Self::ArrowRight => "icons/arrow-right.svg",
Self::ArrowDownCircle => "icons/arrow-down-circle.svg",
Self::Boom => "icons/boom.svg",
Self::Book => "icons/book.svg",
Self::ChevronDown => "icons/chevron-down.svg",
+22 -7
View File
@@ -2,17 +2,18 @@ use chat::{ChatRegistry, Room, RoomKind};
use gpui::prelude::FluentBuilder;
use gpui::{
App, AppContext, Context, Entity, IntoElement, ParentElement, Render, SharedString, Styled,
Subscription, Window, div, px,
Subscription, WeakEntity, Window, div, px,
};
use nostr_sdk::prelude::*;
use state::NostrRegistry;
use theme::ActiveTheme;
use ui::button::{Button, ButtonVariants};
use ui::dock::{DockArea, DockPlacement, PanelHandle};
use ui::input::{Input, InputEvent, InputState};
use ui::{StyledExt, WindowExtension, v_flex};
pub fn open(window: &mut Window, cx: &mut App) {
let view = cx.new(|cx| NewChat::new(window, cx));
pub fn open(dock: WeakEntity<DockArea>, window: &mut Window, cx: &mut App) {
let view = cx.new(|cx| NewChat::new(dock, window, cx));
window.open_modal(cx, move |this, _window, _cx| {
this.width(px(420.)).title("New chat").child(view.clone())
@@ -20,6 +21,9 @@ pub fn open(window: &mut Window, cx: &mut App) {
}
pub struct NewChat {
/// The dock a started chat opens in
dock: WeakEntity<DockArea>,
/// Public key input
input: Entity<InputState>,
@@ -31,7 +35,7 @@ pub struct NewChat {
}
impl NewChat {
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
fn new(dock: WeakEntity<DockArea>, window: &mut Window, cx: &mut Context<Self>) -> Self {
let input = cx.new(|cx| InputState::new(window, cx).placeholder("npub"));
let subscription = cx.subscribe_in(&input, window, |this, _input, event, window, cx| {
@@ -41,6 +45,7 @@ impl NewChat {
});
Self {
dock,
input,
error: None,
_subscription: Some(subscription),
@@ -56,6 +61,8 @@ impl NewChat {
};
let nostr = NostrRegistry::global(cx);
let chat = ChatRegistry::global(cx);
let Some(current_user) = nostr.read(cx).current_user() else {
self.set_error("You are not signed in", cx);
return;
@@ -70,12 +77,20 @@ impl NewChat {
.organize(&current_user)
.kind(RoomKind::Ongoing);
let chat = ChatRegistry::global(cx);
chat.update(cx, |chat, cx| {
let room = chat.update(cx, |chat, cx| {
let room = cx.new(|_| room);
chat.emit_room(&room, window, cx);
chat.track_room(&room, cx);
room
});
ui::dock::add_panel_to(
&self.dock,
PanelHandle::new(chat_ui::init(room.downgrade(), window, cx)),
DockPlacement::Center,
window,
cx,
);
window.close_modal(cx);
}
+16 -266
View File
@@ -1,4 +1,3 @@
use std::rc::Rc;
use std::sync::Arc;
use ::settings::AppSettings;
@@ -6,25 +5,20 @@ use anyhow::Error;
use auto_update::AutoUpdater;
use chat::{ChatEvent, ChatRegistry};
use common::download_dir;
use community::{CommunityEvent, CommunityRegistry};
use community_ui::CommunityPanel;
use device::{DeviceEvent, DeviceRegistry};
use gpui::prelude::FluentBuilder;
use gpui::{
Action, AnyElement, App, AppContext, Context, Entity, InteractiveElement, IntoElement,
ParentElement, Render, SharedString, Styled, Subscription, Task, WeakEntity, Window, div, px,
Action, App, AppContext, Context, Entity, InteractiveElement, IntoElement, ParentElement,
Render, SharedString, Styled, Subscription, Task, Window, div, px,
};
use nostr_sdk::prelude::*;
use person::{PersonRegistry, shorten_pubkey};
use serde::Deserialize;
use smallvec::{SmallVec, smallvec};
use state::{NostrRegistry, StateEvent};
use theme::{ActiveTheme, SIDEBAR_WIDTH, Theme, ThemeRegistry};
use ui::button::{Button, ButtonVariants};
use ui::dock::{self, ClosePanel, DockArea, DockLayout, DockPlacement, Panel, PanelHandle};
use ui::menu::{DropdownMenu, PopupMenuItem};
use ui::dock::{self, DockArea, DockLayout, DockPlacement, Panel, PanelHandle};
use ui::notification::{Notification, NotificationKind};
use ui::{Icon, IconName, Root, Sizable, WindowExtension, h_flex, v_flex};
use ui::{IconName, Root, Sizable, WindowExtension, h_flex, v_flex};
use crate::dialogs::restore::RestoreEncryption;
use crate::dialogs::{new_chat, new_community, settings};
@@ -71,9 +65,6 @@ enum Command {
pub struct Workspace {
dock: Entity<DockArea>,
title_bar_chrome: Rc<dock::TitleBarChrome>,
/// The community panel currently docked, if any
community_panel: Option<WeakEntity<CommunityPanel>>,
/// Async tasks
tasks: Vec<Task<Result<(), Error>>>,
/// Event subscriptions
@@ -83,12 +74,11 @@ pub struct Workspace {
impl Workspace {
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let chat = ChatRegistry::global(cx);
let communities = CommunityRegistry::global(cx);
let device = DeviceRegistry::global(cx);
let nostr = NostrRegistry::global(cx);
let sidebar = cx.new(|cx| Sidebar::new(window, cx));
let (dock, title_bar_chrome) = dock::dock_area("coop", window, cx);
let (dock, _) = dock::dock_area("coop", window, cx);
let sidebar = cx.new(|cx| Sidebar::new(window, dock.downgrade(), cx));
let mut subscriptions = smallvec![];
@@ -164,7 +154,7 @@ impl Workspace {
subscriptions.push(
// Observe all events emitted by the chat registry
cx.subscribe_in(&chat, window, move |this, chat, ev, window, cx| {
cx.subscribe_in(&chat, window, move |_this, _chat, ev, window, cx| {
match ev {
ChatEvent::InboxRelayNotFound => {
const MSG: &str = "Messaging Relays not found. Cannot receive messages.";
@@ -187,28 +177,6 @@ impl Workspace {
cx,
);
}
ChatEvent::OpenRoom(id) => {
if let Some(room) = chat.read(cx).room(id, cx) {
this.add_panel_to_dock(
chat_ui::init(room, window, cx),
DockPlacement::Center,
window,
cx,
);
}
}
ChatEvent::CloseRoom(..) => {
this.dock.update(cx, |area, cx| {
// Force focus to the tab panel
ui::dock::focus_tab_panel(area, window, cx);
// Dispatch the close panel action
cx.defer_in(window, |_, window, cx| {
window.dispatch_action(Box::new(ClosePanel), cx);
window.close_all_modals(cx);
});
});
}
ChatEvent::Error(error) => {
window.push_notification(Notification::error(error).autohide(false), cx);
}
@@ -217,71 +185,23 @@ impl Workspace {
}),
);
subscriptions.push(
// Observe all events emitted by the community registry
cx.subscribe_in(
&communities,
window,
move |this, communities, event, window, cx| match event {
CommunityEvent::Open(id) => {
if let Some(community) = communities.read(cx).community(id) {
let panel = community_ui::init(community, window, cx);
this.community_panel = Some(panel.downgrade());
this.add_panel_to_dock(panel, DockPlacement::Center, window, cx);
}
}
CommunityEvent::Close(_) => {
let Some(panel) = this
.community_panel
.take()
.and_then(|panel| panel.upgrade())
else {
return;
};
this.dock.update(cx, |area, cx| {
ui::dock::add_panel(
area,
PanelHandle::new(panel),
DockPlacement::Center,
window,
cx,
);
ui::dock::focus_tab_panel(area, window, cx);
cx.defer_in(window, |_, window, cx| {
window.dispatch_action(Box::new(ClosePanel), cx);
});
});
}
_ => {}
},
),
);
cx.defer_in(window, move |this, window, cx| {
let sidebar = PanelHandle::new(sidebar);
this.dock.update(cx, |area, cx| {
let left = DockLayout::tabs().panel_view(Arc::new(sidebar), cx);
area.set_dock(DockPlacement::Left, left, window, cx);
area.set_dock_size(DockPlacement::Left, SIDEBAR_WIDTH, window, cx);
});
let greeter = PanelHandle::new(greeter::init(window, cx));
this.dock.update(cx, |this, cx| {
let left = DockLayout::tabs().panel_view(Arc::new(sidebar), cx);
let center = DockLayout::v_split()
.child(DockLayout::tabs().panel_view(Arc::new(greeter), cx), None);
this.dock.update(cx, |area, cx| {
area.set_center(center, window, cx);
this.set_dock(DockPlacement::Left, left, window, cx);
this.set_dock_size(DockPlacement::Left, SIDEBAR_WIDTH, window, cx);
this.set_center(center, window, cx);
});
});
Self {
dock,
title_bar_chrome,
community_panel: None,
tasks: vec![],
_subscriptions: subscriptions,
}
@@ -365,10 +285,11 @@ impl Workspace {
self.add_panel_to_dock(browse::init(window, cx), DockPlacement::Center, window, cx);
}
Command::ShowSearch => {
self.add_panel_to_dock(search::init(window, cx), DockPlacement::Center, window, cx);
let panel = search::init(self.dock.downgrade(), window, cx);
self.add_panel_to_dock(panel, DockPlacement::Center, window, cx);
}
Command::NewChat => {
new_chat::open(window, cx);
new_chat::open(self.dock.downgrade(), window, cx);
}
Command::NewCommunity => {
new_community::open(window, cx);
@@ -583,174 +504,6 @@ impl Workspace {
}))
});
}
fn titlebar_right(_window: &mut Window, cx: &mut App) -> AnyElement {
let auto_updater = AutoUpdater::try_global(cx);
let chat = ChatRegistry::global(cx);
let nip4e_enabled = AppSettings::get_nip4e(cx);
let nostr = NostrRegistry::global(cx);
let Some(public_key) = nostr.read(cx).current_user() else {
return div().into_any_element();
};
let persons = PersonRegistry::global(cx);
let profile = persons.read(cx).get(&public_key, cx);
let announcement = profile.announcement();
let updater_status = auto_updater.as_ref().and_then(|updater| {
let updater = updater.read(cx);
(!updater.idle()).then(|| updater.status())
});
let staged_update = auto_updater
.as_ref()
.is_some_and(|updater| updater.read(cx).staged());
h_flex()
.when(!cx.theme().platform.is_mac(), |this| this.pr_2())
.gap_2()
.when_some(updater_status, |this, status| {
this.child(div().text_xs().italic().child(status))
})
.when(staged_update, |this| {
this.child(
Button::new("restart-to-update")
.label("Restart to Update")
.tooltip("Quit and relaunch into the installed update")
.small()
.ghost()
.on_click(|_event, _window, cx| {
if let Some(auto_updater) = AutoUpdater::try_global(cx) {
auto_updater.update(cx, |this, cx| this.restart(cx));
}
}),
)
})
.when(nip4e_enabled, |this| {
this.child(
Button::new("key")
.icon(IconName::UserKey)
.tooltip("Decoupled encryption key")
.small()
.ghost()
.dropdown_menu(move |this, _window, _cx| {
this.min_w(px(260.))
.label("Encryption Key")
.when_some(announcement.as_ref(), |this, announcement| {
let name = announcement.client_name();
let pkey = shorten_pubkey(announcement.public_key(), 8);
this.item(PopupMenuItem::element(move |_window, cx| {
h_flex()
.gap_1()
.text_sm()
.child(
Icon::new(IconName::Device)
.small()
.text_color(cx.theme().icon_muted),
)
.child(name.clone())
}))
.item(
PopupMenuItem::element(move |_window, cx| {
h_flex()
.gap_1()
.text_sm()
.child(
Icon::new(IconName::UserKey)
.small()
.text_color(cx.theme().icon_muted),
)
.child(SharedString::from(pkey.clone()))
}),
)
})
.separator()
.menu_with_icon(
"Backup",
IconName::Shield,
Box::new(Command::BackupEncryption),
)
.menu_with_icon(
"Restore from secret key",
IconName::Usb,
Box::new(Command::ImportEncryption),
)
.separator()
.menu_with_icon(
"Reload",
IconName::Refresh,
Box::new(Command::RefreshEncryption),
)
.menu_with_icon(
"Reset",
IconName::Warning,
Box::new(Command::ResetEncryption),
)
}),
)
})
.child(
Button::new("inbox")
.icon(IconName::Inbox)
.small()
.ghost()
.dropdown_menu(move |this, _window, cx| {
let urls: Vec<(SharedString, SharedString)> = profile
.messaging_relays()
.iter()
.map(|url| {
(
SharedString::from(url.to_string()),
chat.read(cx).count_messages(url).to_string().into(),
)
})
.collect();
// Header
let menu = this.min_w(px(260.)).label("Messaging Relays");
// Content
let menu = urls.into_iter().fold(menu, |this, (url, count)| {
this.item(PopupMenuItem::element(move |_window, cx| {
h_flex()
.px_1()
.w_full()
.text_sm()
.justify_between()
.child(url.clone())
.child(
div()
.text_xs()
.text_color(cx.theme().text_muted)
.child(count.clone()),
)
}))
});
// Footer
menu.separator()
.menu_with_icon(
"Manage gossip relays",
IconName::Relay,
Box::new(Command::ShowRelayList),
)
.menu_with_icon(
"Manage messaging relays",
IconName::Relay,
Box::new(Command::ShowMessaging),
)
.separator()
.menu_with_icon(
"Reload",
IconName::Refresh,
Box::new(Command::RefreshMessagingRelays),
)
}),
)
.into_any_element()
}
}
impl Render for Workspace {
@@ -758,9 +511,6 @@ impl Render for Workspace {
let modal_layer = Root::render_modal_layer(window, cx);
let notification_layer = Root::render_notification_layer(window, cx);
// Render the title bar chrome
self.title_bar_chrome.set_trailing(Self::titlebar_right);
div()
.id("workspace")
.on_action(cx.listener(Self::on_command))
+25 -9
View File
@@ -7,8 +7,8 @@ use common::DebouncedDelay;
use gpui::prelude::FluentBuilder;
use gpui::{
AnyElement, App, AppContext, Context, ElementId, Entity, EventEmitter, FocusHandle, Focusable,
IntoElement, ParentElement, Render, SharedString, Styled, Subscription, Task, Window, div,
uniform_list,
IntoElement, ParentElement, Render, SharedString, Styled, Subscription, Task, WeakEntity,
Window, div, uniform_list,
};
use instant::Duration;
use nostr_sdk::prelude::*;
@@ -17,7 +17,7 @@ use smallvec::{SmallVec, smallvec};
use state::{FIND_DELAY, NostrRegistry};
use theme::ActiveTheme;
use ui::button::{Button, ButtonVariants};
use ui::dock::{Panel, PanelEvent};
use ui::dock::{DockArea, DockPlacement, Panel, PanelEvent, PanelHandle};
use ui::input::{Input, InputEvent, InputState};
use ui::notification::Notification;
use ui::{Icon, IconName, Selectable, Sizable, StyledExt, WindowExtension, h_flex, v_flex};
@@ -26,13 +26,15 @@ use crate::sidebar::{TreeRow, TreeRowKind};
const INPUT_PLACEHOLDER: &str = "Find or start a conversation";
pub fn init(window: &mut Window, cx: &mut App) -> Entity<SearchPanel> {
cx.new(|cx| SearchPanel::new(window, cx))
pub fn init(dock: WeakEntity<DockArea>, window: &mut Window, cx: &mut App) -> Entity<SearchPanel> {
cx.new(|cx| SearchPanel::new(dock, window, cx))
}
pub struct SearchPanel {
name: SharedString,
focus_handle: FocusHandle,
/// The dock a started chat opens in
dock: WeakEntity<DockArea>,
/// Find input state
find_input: Entity<InputState>,
@@ -63,7 +65,7 @@ pub struct SearchPanel {
}
impl SearchPanel {
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
fn new(dock: WeakEntity<DockArea>, window: &mut Window, cx: &mut Context<Self>) -> Self {
let contact_list = cx.new(|_| None);
let selected_pkeys = cx.new(|_| HashSet::new());
let find_results = cx.new(|_| None);
@@ -107,6 +109,7 @@ impl SearchPanel {
Self {
name: "Search".into(),
focus_handle: cx.focus_handle(),
dock,
find_input,
find_debouncer: DebouncedDelay::new(),
find_results,
@@ -285,6 +288,7 @@ impl SearchPanel {
fn create_room(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let chat = ChatRegistry::global(cx);
let async_chat = chat.downgrade();
let dock = self.dock.clone();
let nostr = NostrRegistry::global(cx);
let Some(public_key) = nostr.read(cx).current_user() else {
@@ -295,14 +299,26 @@ impl SearchPanel {
let receivers = self.get_selected(cx);
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
// Create a new room and emit it
async_chat.update_in(cx, |this, _window, cx| {
// Create a new room and register it
let room = async_chat.update_in(cx, |chat, _window, cx| {
let room = cx.new(|_| {
Room::new(public_key, receivers)
.organize(&public_key)
.kind(RoomKind::Ongoing)
});
this.emit_room(&room, _window, cx);
chat.track_room(&room, cx);
room
})?;
// Open it in the dock
cx.update(|window, cx| {
ui::dock::add_panel_to(
&dock,
PanelHandle::new(chat_ui::init(room.downgrade(), window, cx)),
DockPlacement::Center,
window,
cx,
);
})?;
// Reset the find panel
+393 -335
View File
@@ -2,15 +2,15 @@ use std::ops::Range;
use std::rc::Rc;
use auto_update::AutoUpdater;
use chat::{ChatEvent, ChatRegistry, RoomKind};
use chat::{ChatEvent, ChatRegistry, Room, RoomKind};
use common::TimestampExt;
use community::{ChannelId, Community, CommunityEvent, CommunityRegistry};
use gpui::prelude::FluentBuilder;
use gpui::{
AnyElement, App, Context, ElementId, Entity, EventEmitter, FocusHandle, Focusable,
AnyElement, App, Context, Div, ElementId, Entity, EventEmitter, FocusHandle, Focusable,
InteractiveElement, IntoElement, ObjectFit, ParentElement, Render, ScrollHandle, SharedString,
StatefulInteractiveElement, Styled, StyledImage, Subscription, UniformListScrollHandle,
WeakEntity, Window, div, img, px, retain_all, uniform_list,
Stateful, StatefulInteractiveElement, Styled, StyledImage, Subscription,
UniformListScrollHandle, WeakEntity, Window, div, img, px, retain_all, uniform_list,
};
use nostr_sdk::prelude::*;
use person::PersonRegistry;
@@ -20,7 +20,7 @@ use state::NostrRegistry;
use theme::{ActiveTheme, TABBAR_HEIGHT};
use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants};
use ui::dock::{Panel, PanelEvent};
use ui::dock::{DockArea, DockPlacement, Panel, PanelEvent, PanelHandle};
use ui::indicator::Indicator;
use ui::menu::{DropdownMenu, PopupMenuItem};
use ui::nav_item::NavItem;
@@ -32,8 +32,8 @@ use ui::{
};
use crate::Command;
use crate::dialogs::import;
mod onboarding;
mod tab;
mod tree;
@@ -46,23 +46,22 @@ pub struct Sidebar {
scroll_handles: [UniformListScrollHandle; 3],
/// Scroll state of the channel and member lists
community_scroll: ScrollHandle,
/// The dock the sidebar opens its panels in
dock: WeakEntity<DockArea>,
active_tab: SidebarTab,
/// The community the sidebar is browsing, if any
community: Option<WeakEntity<Community>>,
/// Expanded state of the community's sections
channels_open: bool,
admins_open: bool,
members_open: bool,
/// Whether there are new chat requests
new_requests: bool,
_subscriptions: SmallVec<[Subscription; 4]>,
}
impl Sidebar {
pub fn new(window: &mut Window, 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 communities = CommunityRegistry::global(cx);
let nostr = NostrRegistry::global(cx);
let mut subscriptions = smallvec![];
@@ -80,16 +79,11 @@ impl Sidebar {
window,
|_this, _communities, event, window, cx| {
if let CommunityEvent::Error(error) = event {
window
.push_notification(Notification::error(error.clone()).autohide(false), cx);
window.push_notification(Notification::error(error.clone()), cx);
}
},
));
subscriptions.push(cx.observe(&nostr, |_this, _nostr, cx| cx.notify()));
subscriptions.push(cx.observe(&communities, |_this, _communities, cx| cx.notify()));
Self {
focus_handle: cx.focus_handle(),
scroll_handles: [
@@ -98,6 +92,7 @@ impl Sidebar {
UniformListScrollHandle::new(),
],
community_scroll: ScrollHandle::default(),
dock,
active_tab: SidebarTab::Recents,
community: None,
channels_open: true,
@@ -112,7 +107,6 @@ impl Sidebar {
if self.active_tab == tab {
return;
}
self.active_tab = tab;
cx.notify();
}
@@ -130,33 +124,42 @@ impl Sidebar {
settings.record_recent_community(id, cx);
});
CommunityRegistry::global(cx).update(cx, |registry, cx| {
registry.emit_community(&community, window, cx);
});
self.community = Some(community.downgrade());
ui::dock::add_panel_to(
&self.dock,
PanelHandle::new(community_ui::init(community, window, cx)),
DockPlacement::Center,
window,
cx,
);
cx.notify();
}
fn close_community(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let Some(community) = self.community.take() else {
fn open_room(&mut self, room: Entity<Room>, window: &mut Window, cx: &mut Context<Self>) {
ui::dock::add_panel_to(
&self.dock,
PanelHandle::new(chat_ui::init(room.downgrade(), window, cx)),
DockPlacement::Center,
window,
cx,
);
}
/// Leave the community view, returning the sidebar to its tab list.
fn reset_community(&mut self, cx: &mut Context<Self>) {
if self.community.take().is_none() {
return;
};
if let Ok(id) = community.read_with(cx, |community, _cx| community.id()) {
CommunityRegistry::global(cx).update(cx, |registry, cx| {
registry.emit_close(id, window, cx);
});
}
cx.notify();
}
fn render_user(&self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
fn render_user(&self, cx: &mut Context<Self>) -> Stateful<Div> {
let nostr = NostrRegistry::global(cx);
let current_user = nostr.read(cx).current_user();
let mut row = h_flex()
h_flex()
.id("sidebar-user")
.w_full()
.h(TABBAR_HEIGHT)
@@ -166,16 +169,15 @@ impl Sidebar {
.px_2()
.when(cfg!(target_os = "macos"), |this| {
this.pl(px(TRAFFIC_LIGHT_PADDING))
});
if let Some(public_key) = current_user.as_ref() {
})
.when_some(current_user.as_ref(), |this, public_key| {
let persons = PersonRegistry::global(cx);
let profile = persons.read(cx).get(public_key, cx);
let avatar = profile.avatar();
let avatar_seed = profile.avatar_seed();
let name = profile.name();
row = row.child(
this.child(
Button::new("current-user")
.child(
Avatar::new(avatar.clone())
@@ -205,7 +207,11 @@ impl Sidebar {
.child(name.clone())
}))
.separator()
.menu_with_icon("Inbox", IconName::Inbox, Box::new(Command::ShowInbox))
.menu_with_icon(
"Inbox",
IconName::Inbox,
Box::new(Command::ShowInbox),
)
.menu_with_icon(
"Search",
IconName::Search,
@@ -226,7 +232,11 @@ impl Sidebar {
IconName::UserKey,
Box::new(Command::ShowBackup),
)
.menu_with_icon("Themes", IconName::Sun, Box::new(Command::ToggleTheme))
.menu_with_icon(
"Themes",
IconName::Sun,
Box::new(Command::ToggleTheme),
)
.when(AutoUpdater::is_available(cx), |this| {
this.separator().menu_with_icon(
"Check for Updates",
@@ -240,315 +250,58 @@ impl Sidebar {
Box::new(Command::ShowSettings),
)
}),
);
}
if self.community.is_some() {
row = row.child(div().flex_1()).child(
)
})
.child(div().flex_1())
.when_some(AutoUpdater::try_global(cx), |this, updater| {
this.child(self.render_updater(updater, cx))
})
.when(self.community.is_some(), |this| {
this.child(
Button::new("sidebar-back")
.icon(IconName::ArrowLeft)
.tooltip("Back")
.ghost()
.small()
.on_click(cx.listener(|this, _event, window, cx| {
this.close_community(window, cx);
.on_click(cx.listener(|this, _event, _window, cx| {
this.reset_community(cx);
})),
);
}
title_bar_drag_handlers(row, window, cx).into_any_element()
}
}
fn recent_communities(cx: &App) -> Vec<Entity<Community>> {
const LIMIT: usize = 3;
let registry = CommunityRegistry::global(cx);
let communities = registry.read(cx).communities();
let recent = AppSettings::get_recent_communities(cx);
let mut rows: Vec<Entity<Community>> = recent
.iter()
.filter_map(|id| {
communities
.iter()
.find(|community| community.read(cx).id().to_hex() == *id)
)
})
.take(LIMIT)
.cloned()
.collect();
if rows.is_empty() {
rows = communities.iter().take(LIMIT).cloned().collect();
}
rows
}
fn render_updater(&self, updater: Entity<AutoUpdater>, cx: &mut App) -> AnyElement {
let status = updater.read(cx).status();
let staged = updater.read(cx).staged();
fn rows_for(tab: SidebarTab, cx: &App) -> Vec<SidebarRow> {
match tab {
SidebarTab::Recents => {
let chat = ChatRegistry::global(cx);
let rooms = chat.read(cx).rooms(&RoomKind::Ongoing, cx);
let registry = CommunityRegistry::global(cx);
let community_count = registry.read(cx).communities().len();
let communities = recent_communities(cx);
if communities.is_empty() && rooms.is_empty() {
return vec![SidebarRow::Hint {
text: "Nothing recent yet".into(),
}];
}
let mut rows = Vec::new();
if !communities.is_empty() {
rows.push(SidebarRow::Section {
label: "Communities".into(),
count: community_count,
});
rows.extend(
communities
.into_iter()
.map(|community| SidebarRow::Community { community }),
);
rows.push(SidebarRow::Action {
label: "Show all communities".into(),
tab: SidebarTab::Communities,
});
}
if !rooms.is_empty() {
rows.push(SidebarRow::Section {
label: "Chats".into(),
count: rooms.len(),
});
rows.extend(
rooms
.into_iter()
.take(5)
.map(|room| SidebarRow::Room { room }),
);
rows.push(SidebarRow::Action {
label: "Show all chats".into(),
tab: SidebarTab::Chats,
});
}
rows
}
SidebarTab::Chats => {
let chat = ChatRegistry::global(cx);
let chat = chat.read(cx);
let messages = chat.rooms(&RoomKind::Ongoing, cx);
let mut rows = vec![SidebarRow::Section {
label: "Chats".into(),
count: messages.len(),
}];
if messages.is_empty() {
rows.push(SidebarRow::Hint {
text: "No conversations yet".into(),
});
} else {
rows.extend(messages.into_iter().map(|room| SidebarRow::Room { room }));
}
rows
}
SidebarTab::Communities => {
let registry = CommunityRegistry::global(cx);
let communities = registry.read(cx).communities();
let mut rows = vec![SidebarRow::Section {
label: "Communities".into(),
count: communities.len(),
}];
if communities.is_empty() {
rows.push(SidebarRow::Hint {
text: "No communities yet".into(),
});
} else {
rows.extend(
communities
.iter()
.cloned()
.map(|community| SidebarRow::Community { community }),
);
}
rows
}
}
}
fn render_rows(range: Range<usize>, rows: &[SidebarRow], cx: &Context<Sidebar>) -> Vec<AnyElement> {
rows.get(range.clone())
.into_iter()
.flatten()
.enumerate()
.map(|(offset, row)| {
let index = range.start + offset;
match row {
SidebarRow::Section { label, count } => TreeRow::new(
ElementId::NamedInteger("tree-row".into(), index as u64),
TreeRowKind::Section,
label.clone(),
)
.count(*count)
.into_any_element(),
SidebarRow::Room { room } => {
let name = room.read(cx).display_name(cx);
let picture = room.read(cx).display_image(cx);
let seed = room.read(cx).display_image_seed(cx);
let created_at = room.read(cx).created_at.to_ago();
let room_clone = room.clone();
let handler = cx.listener(move |_this, _event, window, cx| {
ChatRegistry::global(cx).update(cx, |chat, cx| {
chat.emit_room(&room_clone, window, cx);
});
});
TreeRow::new(
ElementId::NamedInteger("tree-row".into(), index as u64),
TreeRowKind::Room,
name,
)
.avatar(seed)
.picture(picture)
.created_at(created_at)
.on_click(handler)
.into_any_element()
}
SidebarRow::Community { community } => {
let name = community.read(cx).name();
let seed = community.read(cx).id().to_hex();
let picture = community.read(cx).icon();
let community = community.clone();
TreeRow::new(
ElementId::NamedInteger("tree-row".into(), index as u64),
TreeRowKind::Community,
name,
)
.avatar(seed)
.picture(picture)
.on_click(cx.listener(move |this, _event, window, cx| {
this.open_community(community.clone(), window, cx);
}))
.into_any_element()
}
SidebarRow::Action { label, tab } => {
let tab = *tab;
TreeRow::new(
ElementId::NamedInteger("tree-row".into(), index as u64),
TreeRowKind::Action,
label.clone(),
)
.icon(IconName::ArrowRight)
.on_click(cx.listener(move |this, _event, _window, cx| {
this.select_tab(tab, cx);
}))
.into_any_element()
}
SidebarRow::Hint { text } => TreeRow::new(
ElementId::NamedInteger("tree-row".into(), index as u64),
TreeRowKind::Hint,
text.clone(),
)
.into_any_element(),
}
})
.collect()
}
impl Panel for Sidebar {
fn panel_id(&self) -> SharedString {
"Sidebar".into()
}
fn title(&self, _cx: &App) -> AnyElement {
SharedString::from("Sidebar").into_any_element()
}
fn closable(&self, _cx: &App) -> bool {
false
}
fn zoomable(&self, _cx: &App) -> bool {
false
}
}
impl EventEmitter<PanelEvent> for Sidebar {}
impl Focusable for Sidebar {
fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
impl Render for Sidebar {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let (logged_in, ready) = {
let nostr = NostrRegistry::global(cx);
(
nostr.read(cx).current_user().is_some(),
nostr.read(cx).ready(),
)
};
if !logged_in {
if !ready {
return v_flex()
.size_full()
.bg(cx.theme().surface_background)
.border_r_1()
.border_color(cx.theme().border_variant)
.into_any_element();
}
return onboarding::render(window, cx).into_any_element();
}
let community = self
.community
.clone()
.and_then(|community| community.upgrade());
if community.is_none() {
self.community = None;
}
v_flex()
.image_cache(retain_all("sidebar"))
.size_full()
.relative()
h_flex()
.gap_2()
.bg(cx.theme().surface_background)
.border_r_1()
.border_color(cx.theme().border_variant)
.child(self.render_user(window, cx))
.map(|this| match community {
Some(community) => this.child(self.render_community(community, cx)),
None => this.child(self.render_tabs(cx)),
.text_xs()
.child(status)
.when(staged, |this| {
this.child(
Button::new("restart-to-update")
.icon(IconName::ArrowDownCircle)
.tooltip("Quit and relaunch into the installed update")
.small()
.ghost()
.on_click(move |_, _window, cx| {
updater.update(cx, |this, cx| {
this.restart(cx);
});
}),
)
})
.into_any_element()
}
}
impl Sidebar {
fn render_tabs(&mut self, cx: &mut Context<Self>) -> AnyElement {
let chat = ChatRegistry::global(cx);
let loading = chat.read(cx).loading();
let sidebar = cx.entity().downgrade();
let active_tab = self.active_tab;
let rows = Rc::new(rows_for(active_tab, cx));
let rows = Rc::new(self.rows_for(active_tab, cx));
let scroll_handle = &self.scroll_handles[active_tab.index()];
v_flex()
@@ -643,8 +396,8 @@ impl Sidebar {
uniform_list(
active_tab.list_id(),
rows.len(),
cx.processor(move |_this, range, _window, cx| {
render_rows(range, rows.as_slice(), cx)
cx.processor(move |this, range, _window, cx| {
this.render_rows(range, rows.as_slice(), cx)
}),
)
.track_scroll(scroll_handle)
@@ -732,7 +485,7 @@ impl Sidebar {
.pb_2()
.track_scroll(&self.community_scroll)
.overflow_y_scroll()
.child(section_row(
.child(self.section_row(
"channels",
"Channels",
channels.len(),
@@ -742,10 +495,10 @@ impl Sidebar {
))
.when(self.channels_open, |this| {
this.children(channels.into_iter().map(|(id, name, private)| {
channel_row(id, name, private, active == Some(id), &community, cx)
self.channel_row(id, name, private, active == Some(id), &community, cx)
}))
})
.child(section_row(
.child(self.section_row(
"admins",
"Admins",
admins.len(),
@@ -757,10 +510,10 @@ impl Sidebar {
this.children(
admins
.iter()
.map(|public_key| member_row("community-admin", *public_key, cx)),
.map(|public_key| self.member_row("community-admin", *public_key, cx)),
)
})
.child(section_row(
.child(self.section_row(
"members",
"Members",
members.len(),
@@ -772,7 +525,7 @@ impl Sidebar {
this.children(
members
.iter()
.map(|public_key| member_row("member", *public_key, cx)),
.map(|public_key| self.member_row("member", *public_key, cx)),
)
});
@@ -796,9 +549,219 @@ impl Sidebar {
.child(Scrollbar::vertical(&self.community_scroll))
.into_any_element()
}
fn recent_communities(&self, cx: &App) -> Vec<Entity<Community>> {
const LIMIT: usize = 3;
let registry = CommunityRegistry::global(cx);
let communities = registry.read(cx).communities();
let recent = AppSettings::get_recent_communities(cx);
let mut rows: Vec<Entity<Community>> = recent
.iter()
.filter_map(|id| {
communities
.iter()
.find(|community| community.read(cx).id().to_hex() == *id)
})
.take(LIMIT)
.cloned()
.collect();
if rows.is_empty() {
rows = communities.iter().take(LIMIT).cloned().collect();
}
rows
}
fn rows_for(&self, tab: SidebarTab, cx: &App) -> Vec<SidebarRow> {
match tab {
SidebarTab::Recents => {
let chat = ChatRegistry::global(cx);
let rooms = chat.read(cx).rooms(&RoomKind::Ongoing, cx);
let registry = CommunityRegistry::global(cx);
let community_count = registry.read(cx).communities().len();
let communities = self.recent_communities(cx);
if communities.is_empty() && rooms.is_empty() {
return vec![SidebarRow::Hint {
text: "Nothing recent yet".into(),
}];
}
let mut rows = Vec::new();
if !communities.is_empty() {
rows.push(SidebarRow::Section {
label: "Communities".into(),
count: community_count,
});
rows.extend(
communities
.into_iter()
.map(|community| SidebarRow::Community { community }),
);
rows.push(SidebarRow::Action {
label: "Show all communities".into(),
tab: SidebarTab::Communities,
});
}
if !rooms.is_empty() {
rows.push(SidebarRow::Section {
label: "Chats".into(),
count: rooms.len(),
});
rows.extend(
rooms
.into_iter()
.take(5)
.map(|room| SidebarRow::Room { room }),
);
rows.push(SidebarRow::Action {
label: "Show all chats".into(),
tab: SidebarTab::Chats,
});
}
rows
}
SidebarTab::Chats => {
let chat = ChatRegistry::global(cx);
let chat = chat.read(cx);
let messages = chat.rooms(&RoomKind::Ongoing, cx);
let mut rows = vec![SidebarRow::Section {
label: "Chats".into(),
count: messages.len(),
}];
if messages.is_empty() {
rows.push(SidebarRow::Hint {
text: "No conversations yet".into(),
});
} else {
rows.extend(messages.into_iter().map(|room| SidebarRow::Room { room }));
}
rows
}
SidebarTab::Communities => {
let registry = CommunityRegistry::global(cx);
let communities = registry.read(cx).communities();
let mut rows = vec![SidebarRow::Section {
label: "Communities".into(),
count: communities.len(),
}];
if communities.is_empty() {
rows.push(SidebarRow::Hint {
text: "No communities yet".into(),
});
} else {
rows.extend(
communities
.iter()
.cloned()
.map(|community| SidebarRow::Community { community }),
);
}
rows
}
}
}
fn render_rows(
&self,
range: Range<usize>,
rows: &[SidebarRow],
cx: &Context<Sidebar>,
) -> Vec<AnyElement> {
rows.get(range.clone())
.into_iter()
.flatten()
.enumerate()
.map(|(offset, row)| {
let index = range.start + offset;
match row {
SidebarRow::Section { label, count } => TreeRow::new(
ElementId::NamedInteger("tree-row".into(), index as u64),
TreeRowKind::Section,
label.clone(),
)
.count(*count)
.into_any_element(),
SidebarRow::Room { room } => {
let name = room.read(cx).display_name(cx);
let picture = room.read(cx).display_image(cx);
let seed = room.read(cx).display_image_seed(cx);
let created_at = room.read(cx).created_at.to_ago();
let room_clone = room.clone();
let handler = cx.listener(move |this, _event, window, cx| {
this.open_room(room_clone.clone(), window, cx);
});
TreeRow::new(
ElementId::NamedInteger("tree-row".into(), index as u64),
TreeRowKind::Room,
name,
)
.avatar(seed)
.picture(picture)
.created_at(created_at)
.on_click(handler)
.into_any_element()
}
SidebarRow::Community { community } => {
let name = community.read(cx).name();
let seed = community.read(cx).id().to_hex();
let picture = community.read(cx).icon();
let community = community.clone();
TreeRow::new(
ElementId::NamedInteger("tree-row".into(), index as u64),
TreeRowKind::Community,
name,
)
.avatar(seed)
.picture(picture)
.on_click(cx.listener(move |this, _event, window, cx| {
this.open_community(community.clone(), window, cx);
}))
.into_any_element()
}
SidebarRow::Action { label, tab } => {
let tab = *tab;
TreeRow::new(
ElementId::NamedInteger("tree-row".into(), index as u64),
TreeRowKind::Action,
label.clone(),
)
.icon(IconName::ArrowRight)
.on_click(cx.listener(move |this, _event, _window, cx| {
this.select_tab(tab, cx);
}))
.into_any_element()
}
SidebarRow::Hint { text } => TreeRow::new(
ElementId::NamedInteger("tree-row".into(), index as u64),
TreeRowKind::Hint,
text.clone(),
)
.into_any_element(),
}
})
.collect()
}
fn section_row(
&self,
id: &'static str,
label: &'static str,
count: usize,
@@ -825,6 +788,7 @@ fn section_row(
}
fn channel_row(
&self,
id: ChannelId,
name: String,
private: bool,
@@ -860,7 +824,7 @@ fn channel_row(
.into_any_element()
}
fn member_row(prefix: &str, public_key: PublicKey, cx: &App) -> AnyElement {
fn member_row(&self, prefix: &str, public_key: PublicKey, cx: &App) -> AnyElement {
let persons = PersonRegistry::global(cx);
let person = persons.read(cx).get(&public_key, cx);
@@ -880,3 +844,97 @@ fn member_row(prefix: &str, public_key: PublicKey, cx: &App) -> AnyElement {
)
.into_any_element()
}
}
impl Panel for Sidebar {
fn panel_id(&self) -> SharedString {
"Sidebar".into()
}
fn title(&self, _cx: &App) -> AnyElement {
SharedString::from("Sidebar").into_any_element()
}
fn closable(&self, _cx: &App) -> bool {
false
}
fn zoomable(&self, _cx: &App) -> bool {
false
}
}
impl EventEmitter<PanelEvent> for Sidebar {}
impl Focusable for Sidebar {
fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
impl Render for Sidebar {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let nostr = NostrRegistry::global(cx);
let logged_in = { nostr.read(cx).current_user().is_some() };
let community = self
.community
.clone()
.and_then(|community| community.upgrade());
v_flex()
.image_cache(retain_all("sidebar"))
.size_full()
.relative()
.gap_2()
.bg(cx.theme().surface_background)
.border_r_1()
.border_color(cx.theme().border_variant)
.when(!logged_in, |this| {
this.relative()
.child(title_bar_drag_handlers(
div()
.id("onboarding-drag")
.absolute()
.top_0()
.left_0()
.h(TABBAR_HEIGHT)
.w_full(),
window,
cx,
))
.child(
v_flex()
.size_full()
.justify_end()
.gap_4()
.p_4()
.child(
v_flex().child(div().font_semibold().child("Coop")).child(
div()
.text_xs()
.text_color(cx.theme().text_muted)
.child("TODO"),
),
)
.child(
Button::new("import-identity")
.label("Import identity")
.primary()
.font_semibold()
.h_8()
.w_full()
.on_click(|_, window, cx| {
import::open(window, cx);
}),
),
)
})
.child(title_bar_drag_handlers(self.render_user(cx), window, cx))
.map(|this| match community {
Some(community) => this.child(self.render_community(community, cx)),
None => this.child(self.render_tabs(cx)),
})
.into_any_element()
}
}
@@ -1,74 +0,0 @@
use gpui::{App, InteractiveElement, IntoElement, ParentElement, Styled, Window, div, svg};
use theme::{ActiveTheme, TABBAR_HEIGHT};
use ui::button::{Button, ButtonVariants};
use ui::{StyledExt, h_flex, title_bar_drag_handlers, v_flex};
use crate::dialogs::import;
const TITLE: &str = "Welcome to Coop!";
const DESCRIPTION: &str = "Chat Freely, Stay Private on Nostr.";
pub(super) fn render(window: &mut Window, cx: &mut App) -> impl IntoElement {
v_flex()
.size_full()
.relative()
.bg(cx.theme().surface_background)
.child(title_bar_drag_handlers(
div()
.id("onboarding-drag")
.absolute()
.top_0()
.left_0()
.h(TABBAR_HEIGHT)
.w_full(),
window,
cx,
))
.child(
v_flex()
.size_full()
.justify_end()
.gap_4()
.p_4()
.child(
h_flex()
.gap_2()
.child(
svg()
.path("brand/coop.svg")
.size_8()
.text_color(cx.theme().icon_muted),
)
.child(
v_flex().child(div().font_semibold().child(TITLE)).child(
div()
.text_xs()
.text_color(cx.theme().text_muted)
.child(DESCRIPTION),
),
),
)
.child(
v_flex()
.gap_2()
.w_full()
.child(
Button::new("join-now")
.label("Join now")
.primary()
.font_semibold()
.h_8()
.w_full(),
)
.child(
Button::new("import-identity")
.label("Import identity")
.secondary()
.font_semibold()
.h_8()
.w_full()
.on_click(|_event, window, cx| import::open(window, cx)),
),
),
)
}