feat: add search and refactor modal (#19)
* add find button to sidebar * update * improve search * add error msg
This commit is contained in:
72
Cargo.lock
generated
72
Cargo.lock
generated
@@ -1136,7 +1136,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collections"
|
name = "collections"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#1a4d7249f6cf59dc37f64c8f745f0dee47a4e9ce"
|
source = "git+https://github.com/zed-industries/zed#afeb3d4fd94fae5cfc635e8a4f6eb56c8aae67fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
@@ -1525,7 +1525,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_refineable"
|
name = "derive_refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#1a4d7249f6cf59dc37f64c8f745f0dee47a4e9ce"
|
source = "git+https://github.com/zed-industries/zed#afeb3d4fd94fae5cfc635e8a4f6eb56c8aae67fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2316,7 +2316,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui"
|
name = "gpui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#1a4d7249f6cf59dc37f64c8f745f0dee47a4e9ce"
|
source = "git+https://github.com/zed-industries/zed#afeb3d4fd94fae5cfc635e8a4f6eb56c8aae67fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"as-raw-xcb-connection",
|
"as-raw-xcb-connection",
|
||||||
@@ -2408,7 +2408,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_macros"
|
name = "gpui_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#1a4d7249f6cf59dc37f64c8f745f0dee47a4e9ce"
|
source = "git+https://github.com/zed-industries/zed#afeb3d4fd94fae5cfc635e8a4f6eb56c8aae67fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2459,9 +2459,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.2"
|
version = "0.15.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
@@ -2632,7 +2632,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "http_client"
|
name = "http_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#1a4d7249f6cf59dc37f64c8f745f0dee47a4e9ce"
|
source = "git+https://github.com/zed-industries/zed#afeb3d4fd94fae5cfc635e8a4f6eb56c8aae67fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -2649,7 +2649,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "http_client_tls"
|
name = "http_client_tls"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#1a4d7249f6cf59dc37f64c8f745f0dee47a4e9ce"
|
source = "git+https://github.com/zed-industries/zed#afeb3d4fd94fae5cfc635e8a4f6eb56c8aae67fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-platform-verifier",
|
"rustls-platform-verifier",
|
||||||
@@ -2952,7 +2952,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.15.2",
|
"hashbrown 0.15.3",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3390,7 +3390,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "media"
|
name = "media"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#1a4d7249f6cf59dc37f64c8f745f0dee47a4e9ce"
|
source = "git+https://github.com/zed-industries/zed#afeb3d4fd94fae5cfc635e8a4f6eb56c8aae67fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bindgen 0.71.1",
|
"bindgen 0.71.1",
|
||||||
@@ -3582,7 +3582,7 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr"
|
name = "nostr"
|
||||||
version = "0.41.0"
|
version = "0.41.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#7edeac7becbec534e89b0e3cc1aedf7f64691e2a"
|
source = "git+https://github.com/rust-nostr/nostr#5a2e66874c6e6cb0f5b8bcb50c1d37aecd7a5d90"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"base64",
|
"base64",
|
||||||
@@ -3607,7 +3607,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-connect"
|
name = "nostr-connect"
|
||||||
version = "0.41.0"
|
version = "0.41.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#7edeac7becbec534e89b0e3cc1aedf7f64691e2a"
|
source = "git+https://github.com/rust-nostr/nostr#5a2e66874c6e6cb0f5b8bcb50c1d37aecd7a5d90"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -3619,7 +3619,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-database"
|
name = "nostr-database"
|
||||||
version = "0.41.0"
|
version = "0.41.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#7edeac7becbec534e89b0e3cc1aedf7f64691e2a"
|
source = "git+https://github.com/rust-nostr/nostr#5a2e66874c6e6cb0f5b8bcb50c1d37aecd7a5d90"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"flatbuffers",
|
"flatbuffers",
|
||||||
"lru",
|
"lru",
|
||||||
@@ -3630,7 +3630,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-lmdb"
|
name = "nostr-lmdb"
|
||||||
version = "0.41.0"
|
version = "0.41.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#7edeac7becbec534e89b0e3cc1aedf7f64691e2a"
|
source = "git+https://github.com/rust-nostr/nostr#5a2e66874c6e6cb0f5b8bcb50c1d37aecd7a5d90"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"heed",
|
"heed",
|
||||||
@@ -3643,7 +3643,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-relay-pool"
|
name = "nostr-relay-pool"
|
||||||
version = "0.41.0"
|
version = "0.41.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#7edeac7becbec534e89b0e3cc1aedf7f64691e2a"
|
source = "git+https://github.com/rust-nostr/nostr#5a2e66874c6e6cb0f5b8bcb50c1d37aecd7a5d90"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"async-wsocket",
|
"async-wsocket",
|
||||||
@@ -3659,7 +3659,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-sdk"
|
name = "nostr-sdk"
|
||||||
version = "0.41.0"
|
version = "0.41.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#7edeac7becbec534e89b0e3cc1aedf7f64691e2a"
|
source = "git+https://github.com/rust-nostr/nostr#5a2e66874c6e6cb0f5b8bcb50c1d37aecd7a5d90"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -4150,9 +4150,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-sys"
|
name = "openssl-sys"
|
||||||
version = "0.9.107"
|
version = "0.9.108"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07"
|
checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -4586,9 +4586,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quinn-udp"
|
name = "quinn-udp"
|
||||||
version = "0.5.11"
|
version = "0.5.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5"
|
checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg_aliases 0.2.1",
|
"cfg_aliases 0.2.1",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -4816,7 +4816,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "refineable"
|
name = "refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#1a4d7249f6cf59dc37f64c8f745f0dee47a4e9ce"
|
source = "git+https://github.com/zed-industries/zed#afeb3d4fd94fae5cfc635e8a4f6eb56c8aae67fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_refineable",
|
"derive_refineable",
|
||||||
"workspace-hack",
|
"workspace-hack",
|
||||||
@@ -4955,7 +4955,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest_client"
|
name = "reqwest_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#1a4d7249f6cf59dc37f64c8f745f0dee47a4e9ce"
|
source = "git+https://github.com/zed-industries/zed#afeb3d4fd94fae5cfc635e8a4f6eb56c8aae67fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -5091,9 +5091,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.0.5"
|
version = "1.0.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
|
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
"errno",
|
"errno",
|
||||||
@@ -5425,7 +5425,7 @@ checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "semantic_version"
|
name = "semantic_version"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#1a4d7249f6cf59dc37f64c8f745f0dee47a4e9ce"
|
source = "git+https://github.com/zed-industries/zed#afeb3d4fd94fae5cfc635e8a4f6eb56c8aae67fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -5542,9 +5542,9 @@ checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.10.8"
|
version = "0.10.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures",
|
"cpufeatures",
|
||||||
@@ -5748,7 +5748,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "sum_tree"
|
name = "sum_tree"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#1a4d7249f6cf59dc37f64c8f745f0dee47a4e9ce"
|
source = "git+https://github.com/zed-industries/zed#afeb3d4fd94fae5cfc635e8a4f6eb56c8aae67fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"log",
|
"log",
|
||||||
@@ -5903,9 +5903,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "synstructure"
|
name = "synstructure"
|
||||||
version = "0.13.1"
|
version = "0.13.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -6015,7 +6015,7 @@ dependencies = [
|
|||||||
"fastrand 2.3.0",
|
"fastrand 2.3.0",
|
||||||
"getrandom 0.3.2",
|
"getrandom 0.3.2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix 1.0.5",
|
"rustix 1.0.7",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -6658,7 +6658,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "util"
|
name = "util"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#1a4d7249f6cf59dc37f64c8f745f0dee47a4e9ce"
|
source = "git+https://github.com/zed-industries/zed#afeb3d4fd94fae5cfc635e8a4f6eb56c8aae67fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-fs",
|
"async-fs",
|
||||||
@@ -7021,18 +7021,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki-root-certs"
|
name = "webpki-root-certs"
|
||||||
version = "0.26.9"
|
version = "0.26.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "180d2741b6115c3d906577e6533ad89472d48d96df00270fccb78233073d77f7"
|
checksum = "c99403924bc5f23afefc319b8ac67ed0e50669f6e52a413314cccb1fdbc93ba0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki-roots"
|
name = "webpki-roots"
|
||||||
version = "0.26.9"
|
version = "0.26.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "29aad86cec885cafd03e8305fd727c418e970a521322c91688414d5b8efba16b"
|
checksum = "37493cadf42a2a939ed404698ded7fb378bf301b5011f973361779a3a74f8c93"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
name = "coop"
|
|
||||||
description = "Coop is a cross-platform Nostr client designed for secure communication focus on simplicity and customizability."
|
|
||||||
product-name = "Coop"
|
|
||||||
version = "0.1.5"
|
|
||||||
category = "SocialNetworking"
|
|
||||||
identifier = "su.reya.coop"
|
|
||||||
resources = ["assets/*/*", "Cargo.toml", "./LICENSE", "./README.md"]
|
|
||||||
binaries = [ { path = "coop", main = true } ]
|
|
||||||
before-packaging-command = "cargo build --release"
|
|
||||||
out-dir = "./target/release"
|
|
||||||
icons = [
|
|
||||||
"crates/coop/resources/32x32.png",
|
|
||||||
"crates/coop/resources/128x128.png",
|
|
||||||
"crates/coop/resources/128x128@2x.png",
|
|
||||||
"crates/coop/resources/app-icon.icns",
|
|
||||||
"crates/coop/resources/app-icon.png",
|
|
||||||
"crates/coop/resources/app-icon.ico",
|
|
||||||
]
|
|
||||||
3
assets/icons/search-fill.svg
Normal file
3
assets/icons/search-fill.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M3 11a8 8 0 1 1 14.162 5.102l3.368 3.368a.75.75 0 1 1-1.06 1.06l-3.368-3.368A8 8 0 0 1 3 11Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 230 B |
@@ -1,3 +1,3 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m20.25 20.25-4.123-4.123m0 0A7.25 7.25 0 1 0 5.873 5.873a7.25 7.25 0 0 0 10.253 10.253Z"/>
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m20 20-3.873-3.873m0 0A7.25 7.25 0 1 0 5.873 5.873a7.25 7.25 0 0 0 10.253 10.253Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 293 B After Width: | Height: | Size: 287 B |
@@ -20,7 +20,7 @@ use ui::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
lru_cache::cache_provider,
|
lru_cache::cache_provider,
|
||||||
views::{
|
views::{
|
||||||
chat, compose, contacts, login, new_account, onboarding, profile, relays, sidebar, welcome,
|
chat, compose, login, new_account, onboarding, profile, relays, search, sidebar, welcome,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,8 +52,9 @@ pub enum PanelKind {
|
|||||||
pub enum ModalKind {
|
pub enum ModalKind {
|
||||||
Profile,
|
Profile,
|
||||||
Compose,
|
Compose,
|
||||||
Contact,
|
Search,
|
||||||
Relay,
|
Relay,
|
||||||
|
Onboarding,
|
||||||
SetupRelay,
|
SetupRelay,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,14 +240,15 @@ impl ChatSpace {
|
|||||||
.child(compose.clone())
|
.child(compose.clone())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ModalKind::Contact => {
|
ModalKind::Search => {
|
||||||
let contacts = contacts::init(window, cx);
|
let search = search::init(window, cx);
|
||||||
|
|
||||||
window.open_modal(cx, move |this, _window, _cx| {
|
window.open_modal(cx, move |modal, _, _| {
|
||||||
this.width(px(MODAL_WIDTH))
|
modal
|
||||||
.title("Contacts")
|
.closable(false)
|
||||||
.child(contacts.clone())
|
.width(px(MODAL_WIDTH))
|
||||||
});
|
.child(search.clone())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
ModalKind::Relay => {
|
ModalKind::Relay => {
|
||||||
let relays = relays::init(window, cx);
|
let relays = relays::init(window, cx);
|
||||||
@@ -266,6 +268,7 @@ impl ChatSpace {
|
|||||||
.child(relays.clone())
|
.child(relays.clone())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ use futures::{select, FutureExt};
|
|||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
use global::constants::APP_NAME;
|
use global::constants::APP_NAME;
|
||||||
use global::{
|
use global::{
|
||||||
constants::{ALL_MESSAGES_SUB_ID, APP_ID, APP_PUBKEY, BOOTSTRAP_RELAYS, NEW_MESSAGE_SUB_ID},
|
constants::{
|
||||||
|
ALL_MESSAGES_SUB_ID, APP_ID, APP_PUBKEY, BOOTSTRAP_RELAYS, NEW_MESSAGE_SUB_ID,
|
||||||
|
SEARCH_RELAYS,
|
||||||
|
},
|
||||||
get_client,
|
get_client,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
@@ -72,6 +75,12 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for relay in SEARCH_RELAYS.into_iter() {
|
||||||
|
if let Err(e) = client.add_relay(relay).await {
|
||||||
|
log::error!("Failed to add relay {}: {}", relay, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Establish connection to bootstrap relays
|
// Establish connection to bootstrap relays
|
||||||
client.connect().await;
|
client.connect().await;
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ impl Compose {
|
|||||||
let user_input = cx.new(|cx| {
|
let user_input = cx.new(|cx| {
|
||||||
TextInput::new(window, cx)
|
TextInput::new(window, cx)
|
||||||
.text_size(ui::Size::Small)
|
.text_size(ui::Size::Small)
|
||||||
.small()
|
|
||||||
.placeholder("npub1...")
|
.placeholder("npub1...")
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -116,10 +115,10 @@ impl Compose {
|
|||||||
contacts,
|
contacts,
|
||||||
selected,
|
selected,
|
||||||
error_message,
|
error_message,
|
||||||
|
subscriptions,
|
||||||
is_loading: false,
|
is_loading: false,
|
||||||
is_submitting: false,
|
is_submitting: false,
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
subscriptions,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,9 +179,10 @@ impl Compose {
|
|||||||
window.close_modal(cx);
|
window.close_modal(cx);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
_ = this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.set_error(Some(e.to_string().into()), cx);
|
this.set_error(Some(e.to_string().into()), cx);
|
||||||
});
|
})
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -341,6 +341,7 @@ impl Render for Compose {
|
|||||||
.gap_1()
|
.gap_1()
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
|
.px_3()
|
||||||
.text_sm()
|
.text_sm()
|
||||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
||||||
.child(DESCRIPTION),
|
.child(DESCRIPTION),
|
||||||
@@ -348,13 +349,14 @@ impl Render for Compose {
|
|||||||
.when_some(self.error_message.read(cx).as_ref(), |this, msg| {
|
.when_some(self.error_message.read(cx).as_ref(), |this, msg| {
|
||||||
this.child(
|
this.child(
|
||||||
div()
|
div()
|
||||||
|
.px_3()
|
||||||
.text_xs()
|
.text_xs()
|
||||||
.text_color(cx.theme().danger)
|
.text_color(cx.theme().danger)
|
||||||
.child(msg.clone()),
|
.child(msg.clone()),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
div().flex().flex_col().child(
|
div().px_3().flex().flex_col().child(
|
||||||
div()
|
div()
|
||||||
.h_10()
|
.h_10()
|
||||||
.border_b_1()
|
.border_b_1()
|
||||||
@@ -372,8 +374,15 @@ impl Render for Compose {
|
|||||||
.flex_col()
|
.flex_col()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.mt_1()
|
.mt_1()
|
||||||
.child(div().text_sm().font_semibold().child("To:"))
|
.child(
|
||||||
.child(self.user_input.clone())
|
div()
|
||||||
|
.px_3()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_2()
|
||||||
|
.child(div().text_sm().font_semibold().child("To:"))
|
||||||
|
.child(self.user_input.clone()),
|
||||||
|
)
|
||||||
.map(|this| {
|
.map(|this| {
|
||||||
let contacts = self.contacts.read(cx).clone();
|
let contacts = self.contacts.read(cx).clone();
|
||||||
let view = cx.entity();
|
let view = cx.entity();
|
||||||
@@ -423,11 +432,10 @@ impl Render for Compose {
|
|||||||
.id(ix)
|
.id(ix)
|
||||||
.w_full()
|
.w_full()
|
||||||
.h_10()
|
.h_10()
|
||||||
.px_2()
|
.px_3()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.rounded(px(cx.theme().radius))
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
@@ -480,7 +488,7 @@ impl Render for Compose {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div().mt_2().child(
|
div().p_3().child(
|
||||||
Button::new("create_dm_btn")
|
Button::new("create_dm_btn")
|
||||||
.label(label)
|
.label(label)
|
||||||
.primary()
|
.primary()
|
||||||
|
|||||||
@@ -1,173 +0,0 @@
|
|||||||
use std::collections::BTreeSet;
|
|
||||||
|
|
||||||
use anyhow::Error;
|
|
||||||
use common::profile::SharedProfile;
|
|
||||||
use global::get_client;
|
|
||||||
use gpui::{
|
|
||||||
div, img, prelude::FluentBuilder, px, uniform_list, AnyElement, App, AppContext, Context,
|
|
||||||
Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement,
|
|
||||||
Render, SharedString, Styled, Task, Window,
|
|
||||||
};
|
|
||||||
use itertools::Itertools;
|
|
||||||
use nostr_sdk::prelude::*;
|
|
||||||
use ui::{
|
|
||||||
button::Button,
|
|
||||||
dock_area::panel::{Panel, PanelEvent},
|
|
||||||
indicator::Indicator,
|
|
||||||
popup_menu::PopupMenu,
|
|
||||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
|
||||||
Sizable,
|
|
||||||
};
|
|
||||||
|
|
||||||
const MIN_HEIGHT: f32 = 280.;
|
|
||||||
|
|
||||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Contacts> {
|
|
||||||
Contacts::new(window, cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Contacts {
|
|
||||||
contacts: Option<Vec<Profile>>,
|
|
||||||
// Panel
|
|
||||||
name: SharedString,
|
|
||||||
closable: bool,
|
|
||||||
zoomable: bool,
|
|
||||||
focus_handle: FocusHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Contacts {
|
|
||||||
pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
|
||||||
cx.new(|cx| Self::view(window, cx))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(_window: &mut Window, cx: &mut Context<Self>) -> Self {
|
|
||||||
cx.spawn(async move |this, cx| {
|
|
||||||
let client = get_client();
|
|
||||||
|
|
||||||
let task: Task<Result<BTreeSet<Profile>, Error>> = cx.background_spawn(async move {
|
|
||||||
let signer = client.signer().await?;
|
|
||||||
let public_key = signer.get_public_key().await?;
|
|
||||||
let profiles = client.database().contacts(public_key).await?;
|
|
||||||
|
|
||||||
Ok(profiles)
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Ok(contacts) = task.await {
|
|
||||||
cx.update(|cx| {
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.contacts = Some(contacts.into_iter().collect_vec());
|
|
||||||
cx.notify();
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
contacts: None,
|
|
||||||
name: "Contacts".into(),
|
|
||||||
closable: true,
|
|
||||||
zoomable: true,
|
|
||||||
focus_handle: cx.focus_handle(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Panel for Contacts {
|
|
||||||
fn panel_id(&self) -> SharedString {
|
|
||||||
"ContactPanel".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn title(&self, _cx: &App) -> AnyElement {
|
|
||||||
self.name.clone().into_any_element()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn closable(&self, _cx: &App) -> bool {
|
|
||||||
self.closable
|
|
||||||
}
|
|
||||||
|
|
||||||
fn zoomable(&self, _cx: &App) -> bool {
|
|
||||||
self.zoomable
|
|
||||||
}
|
|
||||||
|
|
||||||
fn popup_menu(&self, menu: PopupMenu, _cx: &App) -> PopupMenu {
|
|
||||||
menu.track_focus(&self.focus_handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toolbar_buttons(&self, _window: &Window, _cx: &App) -> Vec<Button> {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventEmitter<PanelEvent> for Contacts {}
|
|
||||||
|
|
||||||
impl Focusable for Contacts {
|
|
||||||
fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
|
|
||||||
self.focus_handle.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for Contacts {
|
|
||||||
fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) -> impl IntoElement {
|
|
||||||
let entity = cx.entity().clone();
|
|
||||||
|
|
||||||
div().map(|this| {
|
|
||||||
if let Some(contacts) = self.contacts.clone() {
|
|
||||||
this.child(
|
|
||||||
uniform_list(
|
|
||||||
entity,
|
|
||||||
"contacts",
|
|
||||||
contacts.len(),
|
|
||||||
move |_, range, _window, cx| {
|
|
||||||
let mut items = Vec::with_capacity(contacts.len());
|
|
||||||
|
|
||||||
for ix in range {
|
|
||||||
if let Some(item) = contacts.get(ix) {
|
|
||||||
items.push(
|
|
||||||
div()
|
|
||||||
.w_full()
|
|
||||||
.h_9()
|
|
||||||
.px_2()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_between()
|
|
||||||
.rounded(px(cx.theme().radius))
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.gap_2()
|
|
||||||
.text_xs()
|
|
||||||
.child(
|
|
||||||
div().flex_shrink_0().child(
|
|
||||||
img(item.shared_avatar()).size_6(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(item.shared_name()),
|
|
||||||
)
|
|
||||||
.hover(|this| {
|
|
||||||
this.bg(cx
|
|
||||||
.theme()
|
|
||||||
.base
|
|
||||||
.step(cx, ColorScaleStep::THREE))
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
items
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.min_h(px(MIN_HEIGHT)),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_center()
|
|
||||||
.h_16()
|
|
||||||
.child(Indicator::new().small())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
pub mod chat;
|
pub mod chat;
|
||||||
pub mod compose;
|
pub mod compose;
|
||||||
pub mod contacts;
|
|
||||||
pub mod login;
|
pub mod login;
|
||||||
pub mod new_account;
|
pub mod new_account;
|
||||||
pub mod onboarding;
|
pub mod onboarding;
|
||||||
pub mod profile;
|
pub mod profile;
|
||||||
pub mod relays;
|
pub mod relays;
|
||||||
|
pub mod search;
|
||||||
pub mod sidebar;
|
pub mod sidebar;
|
||||||
pub mod subject;
|
pub mod subject;
|
||||||
pub mod welcome;
|
pub mod welcome;
|
||||||
|
|||||||
357
crates/coop/src/views/search.rs
Normal file
357
crates/coop/src/views/search.rs
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use anyhow::Error;
|
||||||
|
use async_utility::task::spawn;
|
||||||
|
use chats::{
|
||||||
|
room::{Room, RoomKind},
|
||||||
|
ChatRegistry,
|
||||||
|
};
|
||||||
|
use common::profile::SharedProfile;
|
||||||
|
use global::{constants::SEARCH_RELAYS, get_client};
|
||||||
|
use gpui::{
|
||||||
|
div, img, prelude::FluentBuilder, px, relative, uniform_list, App, AppContext, Context, Entity,
|
||||||
|
InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Subscription,
|
||||||
|
Task, Window,
|
||||||
|
};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use nostr_sdk::prelude::*;
|
||||||
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
use ui::{
|
||||||
|
button::{Button, ButtonVariants},
|
||||||
|
indicator::Indicator,
|
||||||
|
input::{InputEvent, TextInput},
|
||||||
|
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||||
|
ContextModal, Disableable, IconName, Sizable,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Search> {
|
||||||
|
Search::new(window, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Search {
|
||||||
|
input: Entity<TextInput>,
|
||||||
|
result: Entity<Vec<Profile>>,
|
||||||
|
error: Entity<Option<SharedString>>,
|
||||||
|
loading: bool,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
subscriptions: SmallVec<[Subscription; 1]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Search {
|
||||||
|
pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||||
|
let result = cx.new(|_| vec![]);
|
||||||
|
let error = cx.new(|_| None);
|
||||||
|
let input = cx.new(|cx| {
|
||||||
|
TextInput::new(window, cx)
|
||||||
|
.text_size(ui::Size::Small)
|
||||||
|
.placeholder("type something...")
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.new(|cx| {
|
||||||
|
let mut subscriptions = smallvec![];
|
||||||
|
|
||||||
|
subscriptions.push(cx.subscribe_in(
|
||||||
|
&input,
|
||||||
|
window,
|
||||||
|
move |this: &mut Search, _, input_event, window, cx| {
|
||||||
|
if let InputEvent::PressEnter = input_event {
|
||||||
|
this.search(window, cx);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
input,
|
||||||
|
result,
|
||||||
|
error,
|
||||||
|
subscriptions,
|
||||||
|
loading: false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
if self.loading {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Show loading spinner
|
||||||
|
self.loading(true, cx);
|
||||||
|
|
||||||
|
// Get search query
|
||||||
|
let query = self.input.read(cx).text();
|
||||||
|
|
||||||
|
let task: Task<Result<Vec<Profile>, Error>> = cx.background_spawn(async move {
|
||||||
|
let client = get_client();
|
||||||
|
|
||||||
|
let filter = Filter::new()
|
||||||
|
.kind(Kind::Metadata)
|
||||||
|
.search(query.to_lowercase())
|
||||||
|
.limit(10);
|
||||||
|
|
||||||
|
let events = client
|
||||||
|
.fetch_events_from(SEARCH_RELAYS, filter, Duration::from_secs(3))
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.unique_by(|event| event.pubkey)
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
|
let mut users = vec![];
|
||||||
|
let (tx, rx) = smol::channel::bounded::<Profile>(events.len());
|
||||||
|
|
||||||
|
spawn(async move {
|
||||||
|
for event in events.into_iter() {
|
||||||
|
let metadata = Metadata::from_json(event.content).unwrap_or_default();
|
||||||
|
|
||||||
|
if let Some(target) = metadata.nip05.as_ref() {
|
||||||
|
if let Ok(verify) = nip05::verify(&event.pubkey, target, None).await {
|
||||||
|
if verify {
|
||||||
|
_ = tx.send(Profile::new(event.pubkey, metadata)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
while let Ok(profile) = rx.recv().await {
|
||||||
|
users.push(profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(users)
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.spawn_in(window, async move |this, cx| match task.await {
|
||||||
|
Ok(users) => {
|
||||||
|
cx.update(|_, cx| {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.loading(false, cx);
|
||||||
|
this.result.update(cx, |this, cx| {
|
||||||
|
*this = users;
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
cx.update(|_, cx| {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.loading(false, cx);
|
||||||
|
this.error.update(cx, |this, cx| {
|
||||||
|
*this = Some(error.to_string().into());
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chat(&mut self, to: Profile, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let public_key = to.public_key();
|
||||||
|
|
||||||
|
let event: Task<Result<Event, anyhow::Error>> = cx.background_spawn(async move {
|
||||||
|
let client = get_client();
|
||||||
|
let signer = client.signer().await?;
|
||||||
|
// [IMPORTANT]
|
||||||
|
// Make sure this event is never send,
|
||||||
|
// this event existed just use for convert to Coop's Room later.
|
||||||
|
let event = EventBuilder::private_msg_rumor(public_key, "")
|
||||||
|
.sign(&signer)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(event)
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
|
if let Ok(event) = event.await {
|
||||||
|
cx.update(|window, cx| {
|
||||||
|
let chats = ChatRegistry::global(cx);
|
||||||
|
let room = Room::new(&event).kind(RoomKind::Ongoing);
|
||||||
|
|
||||||
|
chats.update(cx, |chats, cx| {
|
||||||
|
match chats.push(room, cx) {
|
||||||
|
Ok(_) => {
|
||||||
|
// TODO: automatically open newly created chat panel
|
||||||
|
window.close_modal(cx);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.error.update(cx, |this, cx| {
|
||||||
|
*this = Some(e.to_string().into());
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn loading(&mut self, status: bool, cx: &mut Context<Self>) {
|
||||||
|
self.loading = status;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for Search {
|
||||||
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let mute_color = cx.theme().base.step(cx, ColorScaleStep::NINE);
|
||||||
|
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_3()
|
||||||
|
.mt_3()
|
||||||
|
.child(
|
||||||
|
div().px_3().child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.gap_1()
|
||||||
|
.items_center()
|
||||||
|
.child(self.input.clone())
|
||||||
|
.child(
|
||||||
|
Button::new("find")
|
||||||
|
.icon(IconName::Search)
|
||||||
|
.ghost()
|
||||||
|
.disabled(self.loading)
|
||||||
|
.on_click(
|
||||||
|
cx.listener(move |this, _, window, cx| this.search(window, cx)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.when_some(self.error.read(cx).as_ref(), |this, error| {
|
||||||
|
this.child(
|
||||||
|
div()
|
||||||
|
.px_3()
|
||||||
|
.text_xs()
|
||||||
|
.text_color(cx.theme().danger)
|
||||||
|
.child(error.clone()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.child(div().map(|this| {
|
||||||
|
let result = self.result.read(cx).clone();
|
||||||
|
|
||||||
|
if self.loading {
|
||||||
|
this.h_32()
|
||||||
|
.w_full()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.justify_center()
|
||||||
|
.child(Indicator::new().small())
|
||||||
|
} else if result.is_empty() {
|
||||||
|
this.h_32()
|
||||||
|
.w_full()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.justify_center()
|
||||||
|
.text_sm()
|
||||||
|
.text_color(mute_color)
|
||||||
|
.child("No one with that query could be found.")
|
||||||
|
} else {
|
||||||
|
this.child(
|
||||||
|
uniform_list(
|
||||||
|
cx.entity(),
|
||||||
|
"find-result",
|
||||||
|
result.len(),
|
||||||
|
move |_, range, _window, cx| {
|
||||||
|
let mut items = Vec::new();
|
||||||
|
|
||||||
|
for ix in range {
|
||||||
|
let item = result.get(ix).cloned().unwrap();
|
||||||
|
|
||||||
|
items.push(
|
||||||
|
div()
|
||||||
|
.id(ix)
|
||||||
|
.group("")
|
||||||
|
.w_full()
|
||||||
|
.h_12()
|
||||||
|
.px_2()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.justify_between()
|
||||||
|
.rounded(px(cx.theme().radius))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
|
img(item.shared_avatar())
|
||||||
|
.size_8()
|
||||||
|
.flex_shrink_0(),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_sm()
|
||||||
|
.line_height(relative(1.2))
|
||||||
|
.child(item.shared_name()),
|
||||||
|
)
|
||||||
|
.when_some(
|
||||||
|
item.metadata().nip05,
|
||||||
|
|this, nip05| {
|
||||||
|
this.child(
|
||||||
|
div()
|
||||||
|
.text_xs()
|
||||||
|
.text_color(mute_color)
|
||||||
|
.child(nip05),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.invisible()
|
||||||
|
.group_hover("", |this| this.visible())
|
||||||
|
.child(
|
||||||
|
Button::new(ix)
|
||||||
|
.icon(IconName::ArrowRight)
|
||||||
|
.label("Chat")
|
||||||
|
.xsmall()
|
||||||
|
.primary()
|
||||||
|
.reverse()
|
||||||
|
.on_click(cx.listener(
|
||||||
|
move |this, _, window, cx| {
|
||||||
|
this.chat(
|
||||||
|
item.clone(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.hover(|this| {
|
||||||
|
this.bg(cx
|
||||||
|
.theme()
|
||||||
|
.base
|
||||||
|
.step(cx, ColorScaleStep::THREE))
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
items
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.min_h(px(150.)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,7 +50,13 @@ impl RenderOnce for SidebarButton {
|
|||||||
self.base
|
self.base
|
||||||
.id(self.label.clone())
|
.id(self.label.clone())
|
||||||
.rounded(px(cx.theme().radius))
|
.rounded(px(cx.theme().radius))
|
||||||
.when_some(self.icon, |this, icon| this.child(icon))
|
.when_some(self.icon, |this, icon| {
|
||||||
|
this.child(
|
||||||
|
div()
|
||||||
|
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
||||||
|
.child(icon),
|
||||||
|
)
|
||||||
|
})
|
||||||
.child(self.label.clone())
|
.child(self.label.clone())
|
||||||
.hover(|this| this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE)))
|
.hover(|this| this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE)))
|
||||||
.on_click(move |ev, window, cx| handler(ev, window, cx))
|
.on_click(move |ev, window, cx| handler(ev, window, cx))
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ use ui::{
|
|||||||
panel::{Panel, PanelEvent},
|
panel::{Panel, PanelEvent},
|
||||||
},
|
},
|
||||||
popup_menu::{PopupMenu, PopupMenuExt},
|
popup_menu::{PopupMenu, PopupMenuExt},
|
||||||
scroll::ScrollbarAxis,
|
|
||||||
skeleton::Skeleton,
|
skeleton::Skeleton,
|
||||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||||
IconName, Sizable, StyledExt,
|
IconName, Sizable, StyledExt,
|
||||||
@@ -266,26 +265,25 @@ impl Render for Sidebar {
|
|||||||
.gap_1()
|
.gap_1()
|
||||||
.text_sm()
|
.text_sm()
|
||||||
.font_medium()
|
.font_medium()
|
||||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
|
||||||
.child(
|
.child(
|
||||||
SidebarButton::new("New Message")
|
SidebarButton::new("Find")
|
||||||
.icon(IconName::PlusCircleFill)
|
.icon(IconName::Search)
|
||||||
.on_click(cx.listener(|_, _, window, cx| {
|
.on_click(cx.listener(|_, _, window, cx| {
|
||||||
window.dispatch_action(
|
window.dispatch_action(
|
||||||
Box::new(ToggleModal {
|
Box::new(ToggleModal {
|
||||||
modal: ModalKind::Compose,
|
modal: ModalKind::Search,
|
||||||
}),
|
}),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
SidebarButton::new("Contacts")
|
SidebarButton::new("New Chat")
|
||||||
.icon(IconName::AddressBook)
|
.icon(IconName::PlusCircleFill)
|
||||||
.on_click(cx.listener(|_, _, window, cx| {
|
.on_click(cx.listener(|_, _, window, cx| {
|
||||||
window.dispatch_action(
|
window.dispatch_action(
|
||||||
Box::new(ToggleModal {
|
Box::new(ToggleModal {
|
||||||
modal: ModalKind::Contact,
|
modal: ModalKind::Compose,
|
||||||
}),
|
}),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
@@ -313,9 +311,9 @@ impl Render for Sidebar {
|
|||||||
.tooltip("Toggle chat folders")
|
.tooltip("Toggle chat folders")
|
||||||
.map(|this| {
|
.map(|this| {
|
||||||
if self.split_into_folders {
|
if self.split_into_folders {
|
||||||
this.icon(IconName::ToggleFill)
|
this.icon(IconName::FilterFill)
|
||||||
} else {
|
} else {
|
||||||
this.icon(IconName::Toggle)
|
this.icon(IconName::Filter)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.small()
|
.small()
|
||||||
|
|||||||
@@ -3,14 +3,16 @@ pub const APP_ID: &str = "su.reya.coop";
|
|||||||
pub const APP_PUBKEY: &str = "b1813fb01274b32cc5db6d1198e7c79dda0fb430899f63c7064f651a41d44f2b";
|
pub const APP_PUBKEY: &str = "b1813fb01274b32cc5db6d1198e7c79dda0fb430899f63c7064f651a41d44f2b";
|
||||||
|
|
||||||
/// Bootstrap relays
|
/// Bootstrap relays
|
||||||
pub const BOOTSTRAP_RELAYS: [&str; 5] = [
|
pub const BOOTSTRAP_RELAYS: [&str; 4] = [
|
||||||
"wss://relay.damus.io",
|
"wss://relay.damus.io",
|
||||||
"wss://relay.primal.net",
|
"wss://relay.primal.net",
|
||||||
"wss://user.kindpag.es",
|
"wss://user.kindpag.es",
|
||||||
"wss://relaydiscovery.com",
|
"wss://relaydiscovery.com",
|
||||||
"wss://purplepag.es",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/// Search relays
|
||||||
|
pub const SEARCH_RELAYS: [&str; 1] = ["wss://relay.nostr.band"];
|
||||||
|
|
||||||
/// Subscriptions
|
/// Subscriptions
|
||||||
pub const NEW_MESSAGE_SUB_ID: &str = "listen_new_giftwraps";
|
pub const NEW_MESSAGE_SUB_ID: &str = "listen_new_giftwraps";
|
||||||
pub const ALL_MESSAGES_SUB_ID: &str = "listen_all_giftwraps";
|
pub const ALL_MESSAGES_SUB_ID: &str = "listen_all_giftwraps";
|
||||||
|
|||||||
@@ -354,7 +354,7 @@ impl RenderOnce for Button {
|
|||||||
// Normal Button
|
// Normal Button
|
||||||
match self.size {
|
match self.size {
|
||||||
Size::Size(size) => this.px(size * 0.2),
|
Size::Size(size) => this.px(size * 0.2),
|
||||||
Size::XSmall => this.h_6().px_0p5(),
|
Size::XSmall => this.h_6().px_1p5(),
|
||||||
Size::Small => this.h_7().px_2(),
|
Size::Small => this.h_7().px_2(),
|
||||||
Size::Large => this.h_10().px_3(),
|
Size::Large => this.h_10().px_3(),
|
||||||
_ => this.h_9().px_2(),
|
_ => this.h_9().px_2(),
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ pub enum IconName {
|
|||||||
Relays,
|
Relays,
|
||||||
ResizeCorner,
|
ResizeCorner,
|
||||||
Search,
|
Search,
|
||||||
|
SearchFill,
|
||||||
Settings,
|
Settings,
|
||||||
SortAscending,
|
SortAscending,
|
||||||
SortDescending,
|
SortDescending,
|
||||||
@@ -128,6 +129,7 @@ impl IconName {
|
|||||||
Self::Relays => "icons/relays.svg",
|
Self::Relays => "icons/relays.svg",
|
||||||
Self::ResizeCorner => "icons/resize-corner.svg",
|
Self::ResizeCorner => "icons/resize-corner.svg",
|
||||||
Self::Search => "icons/search.svg",
|
Self::Search => "icons/search.svg",
|
||||||
|
Self::SearchFill => "icons/search-fill.svg",
|
||||||
Self::Settings => "icons/settings.svg",
|
Self::Settings => "icons/settings.svg",
|
||||||
Self::SortAscending => "icons/sort-ascending.svg",
|
Self::SortAscending => "icons/sort-ascending.svg",
|
||||||
Self::SortDescending => "icons/sort-descending.svg",
|
Self::SortDescending => "icons/sort-descending.svg",
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
|
use std::{rc::Rc, time::Duration};
|
||||||
|
|
||||||
|
use gpui::{
|
||||||
|
actions, anchored, div, point, prelude::FluentBuilder, px, relative, Animation,
|
||||||
|
AnimationExt as _, AnyElement, App, Bounds, ClickEvent, Div, FocusHandle, InteractiveElement,
|
||||||
|
IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point, RenderOnce, SharedString,
|
||||||
|
Styled, Window,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
animation::cubic_bezier,
|
animation::cubic_bezier,
|
||||||
button::{Button, ButtonCustomVariant, ButtonVariants as _},
|
button::{Button, ButtonCustomVariant, ButtonVariants as _},
|
||||||
theme::{scale::ColorScaleStep, ActiveTheme as _},
|
theme::{scale::ColorScaleStep, ActiveTheme as _},
|
||||||
v_flex, ContextModal, IconName, Sizable as _, StyledExt,
|
v_flex, ContextModal, IconName, StyledExt,
|
||||||
};
|
};
|
||||||
use gpui::{
|
|
||||||
actions, anchored, div, point, prelude::FluentBuilder, px, Animation, AnimationExt as _,
|
|
||||||
AnyElement, App, Bounds, ClickEvent, Div, FocusHandle, InteractiveElement, IntoElement,
|
|
||||||
KeyBinding, MouseButton, ParentElement, Pixels, Point, RenderOnce, SharedString, Styled,
|
|
||||||
Window,
|
|
||||||
};
|
|
||||||
use std::{rc::Rc, time::Duration};
|
|
||||||
|
|
||||||
actions!(modal, [Escape]);
|
actions!(modal, [Escape]);
|
||||||
|
|
||||||
@@ -185,51 +187,52 @@ impl RenderOnce for Modal {
|
|||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
.absolute()
|
.absolute()
|
||||||
.occlude()
|
.occlude()
|
||||||
.relative()
|
|
||||||
.left(x)
|
.left(x)
|
||||||
.top(y)
|
.top(y)
|
||||||
.w(self.width)
|
.w(self.width)
|
||||||
.when_some(self.max_width, |this, w| this.max_w(w))
|
.when_some(self.max_width, |this, w| this.max_w(w))
|
||||||
.px_4()
|
.when_some(self.title, |this, title| {
|
||||||
.pb_4()
|
this.child(
|
||||||
.child(
|
div()
|
||||||
div()
|
.h_12()
|
||||||
.h_12()
|
.px_3()
|
||||||
.mb_2()
|
.mb_2()
|
||||||
.border_b_1()
|
.flex()
|
||||||
.border_color(cx.theme().base.step(cx, ColorScaleStep::SIX))
|
.items_center()
|
||||||
.flex()
|
.font_semibold()
|
||||||
.items_center()
|
.border_b_1()
|
||||||
.justify_between()
|
.border_color(cx.theme().base.step(cx, ColorScaleStep::SIX))
|
||||||
.when_some(self.title, |this, title| {
|
.line_height(relative(1.))
|
||||||
this.child(div().font_semibold().child(title))
|
.child(title),
|
||||||
})
|
)
|
||||||
.when(self.closable, |this| {
|
})
|
||||||
this.child(
|
.when(self.closable, |this| {
|
||||||
Button::new(SharedString::from(format!(
|
this.child(
|
||||||
"modal-close-{layer_ix}"
|
Button::new(SharedString::from(format!(
|
||||||
)))
|
"modal-close-{layer_ix}"
|
||||||
.small()
|
)))
|
||||||
.icon(IconName::CloseCircleFill)
|
.icon(IconName::CloseCircleFill)
|
||||||
.custom(
|
.absolute()
|
||||||
ButtonCustomVariant::new(window, cx)
|
.top_1p5()
|
||||||
.foreground(
|
.right_2()
|
||||||
cx.theme()
|
.custom(
|
||||||
.base
|
ButtonCustomVariant::new(window, cx)
|
||||||
.step(cx, ColorScaleStep::NINE),
|
.foreground(
|
||||||
)
|
cx.theme().base.step(cx, ColorScaleStep::NINE),
|
||||||
.color(cx.theme().transparent)
|
|
||||||
.hover(cx.theme().transparent)
|
|
||||||
.active(cx.theme().transparent)
|
|
||||||
.border(cx.theme().transparent),
|
|
||||||
)
|
)
|
||||||
.on_click(move |_, window, cx| {
|
.color(cx.theme().transparent)
|
||||||
on_close(&ClickEvent::default(), window, cx);
|
.hover(cx.theme().transparent)
|
||||||
window.close_modal(cx);
|
.active(cx.theme().transparent)
|
||||||
}),
|
.border(cx.theme().transparent),
|
||||||
)
|
)
|
||||||
}),
|
.on_click(
|
||||||
)
|
move |_, window, cx| {
|
||||||
|
on_close(&ClickEvent::default(), window, cx);
|
||||||
|
window.close_modal(cx);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
.child(self.content)
|
.child(self.content)
|
||||||
.children(self.footer)
|
.children(self.footer)
|
||||||
.when(self.keyboard, |this| {
|
.when(self.keyboard, |this| {
|
||||||
|
|||||||
Reference in New Issue
Block a user