refactor ui (#17)
* wip: redesign sidebar * wip: adjust dpi * update * update * refactor modal * fix modal
76
Cargo.lock
generated
@@ -149,9 +149,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.97"
|
version = "1.0.98"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
|
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arbitrary"
|
name = "arbitrary"
|
||||||
@@ -1135,7 +1135,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collections"
|
name = "collections"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#b864a9b0ae633006a44c02b723fe6fad07e84b93"
|
source = "git+https://github.com/zed-industries/zed#a7a7335da4ebeb6ebd5c644f108edf2441f7d77b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
@@ -1524,7 +1524,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#b864a9b0ae633006a44c02b723fe6fad07e84b93"
|
source = "git+https://github.com/zed-industries/zed#a7a7335da4ebeb6ebd5c644f108edf2441f7d77b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2306,7 +2306,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui"
|
name = "gpui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#b864a9b0ae633006a44c02b723fe6fad07e84b93"
|
source = "git+https://github.com/zed-industries/zed#a7a7335da4ebeb6ebd5c644f108edf2441f7d77b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"as-raw-xcb-connection",
|
"as-raw-xcb-connection",
|
||||||
@@ -2397,7 +2397,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#b864a9b0ae633006a44c02b723fe6fad07e84b93"
|
source = "git+https://github.com/zed-industries/zed#a7a7335da4ebeb6ebd5c644f108edf2441f7d77b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2413,9 +2413,9 @@ checksum = "d196ffc1627db18a531359249b2bf8416178d84b729f3cebeb278f285fb9b58c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.4.8"
|
version = "0.4.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2"
|
checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atomic-waker",
|
"atomic-waker",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -2621,7 +2621,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#b864a9b0ae633006a44c02b723fe6fad07e84b93"
|
source = "git+https://github.com/zed-industries/zed#a7a7335da4ebeb6ebd5c644f108edf2441f7d77b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -2638,7 +2638,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#b864a9b0ae633006a44c02b723fe6fad07e84b93"
|
source = "git+https://github.com/zed-industries/zed#a7a7335da4ebeb6ebd5c644f108edf2441f7d77b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-platform-verifier",
|
"rustls-platform-verifier",
|
||||||
@@ -3176,9 +3176,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.171"
|
version = "0.2.172"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
|
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libfuzzer-sys"
|
name = "libfuzzer-sys"
|
||||||
@@ -3379,7 +3379,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "media"
|
name = "media"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#b864a9b0ae633006a44c02b723fe6fad07e84b93"
|
source = "git+https://github.com/zed-industries/zed#a7a7335da4ebeb6ebd5c644f108edf2441f7d77b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bindgen 0.71.1",
|
"bindgen 0.71.1",
|
||||||
@@ -3576,8 +3576,8 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr"
|
name = "nostr"
|
||||||
version = "0.40.0"
|
version = "0.41.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#f4f3f50aa637b3d288a6f1cf762260005b7b498a"
|
source = "git+https://github.com/rust-nostr/nostr#48de47bc7d6283e37e2e980f13608c3ed63286d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"base64",
|
"base64",
|
||||||
@@ -3601,8 +3601,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-connect"
|
name = "nostr-connect"
|
||||||
version = "0.40.0"
|
version = "0.41.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#f4f3f50aa637b3d288a6f1cf762260005b7b498a"
|
source = "git+https://github.com/rust-nostr/nostr#48de47bc7d6283e37e2e980f13608c3ed63286d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -3613,8 +3613,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-database"
|
name = "nostr-database"
|
||||||
version = "0.40.0"
|
version = "0.41.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#f4f3f50aa637b3d288a6f1cf762260005b7b498a"
|
source = "git+https://github.com/rust-nostr/nostr#48de47bc7d6283e37e2e980f13608c3ed63286d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"flatbuffers",
|
"flatbuffers",
|
||||||
"lru",
|
"lru",
|
||||||
@@ -3624,8 +3624,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-lmdb"
|
name = "nostr-lmdb"
|
||||||
version = "0.40.0"
|
version = "0.41.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#f4f3f50aa637b3d288a6f1cf762260005b7b498a"
|
source = "git+https://github.com/rust-nostr/nostr#48de47bc7d6283e37e2e980f13608c3ed63286d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"heed",
|
"heed",
|
||||||
@@ -3637,8 +3637,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-relay-pool"
|
name = "nostr-relay-pool"
|
||||||
version = "0.40.0"
|
version = "0.41.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#f4f3f50aa637b3d288a6f1cf762260005b7b498a"
|
source = "git+https://github.com/rust-nostr/nostr#48de47bc7d6283e37e2e980f13608c3ed63286d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"async-wsocket",
|
"async-wsocket",
|
||||||
@@ -3654,8 +3654,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-sdk"
|
name = "nostr-sdk"
|
||||||
version = "0.40.0"
|
version = "0.41.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#f4f3f50aa637b3d288a6f1cf762260005b7b498a"
|
source = "git+https://github.com/rust-nostr/nostr#48de47bc7d6283e37e2e980f13608c3ed63286d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -4464,9 +4464,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.94"
|
version = "1.0.95"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
|
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -4729,9 +4729,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ravif"
|
name = "ravif"
|
||||||
version = "0.11.11"
|
version = "0.11.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6"
|
checksum = "d6a5f31fcf7500f9401fea858ea4ab5525c99f2322cfcee732c0e6c74208c0c6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"avif-serialize",
|
"avif-serialize",
|
||||||
"imgref",
|
"imgref",
|
||||||
@@ -4813,7 +4813,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "refineable"
|
name = "refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#b864a9b0ae633006a44c02b723fe6fad07e84b93"
|
source = "git+https://github.com/zed-industries/zed#a7a7335da4ebeb6ebd5c644f108edf2441f7d77b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_refineable",
|
"derive_refineable",
|
||||||
"workspace-hack",
|
"workspace-hack",
|
||||||
@@ -4951,7 +4951,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#b864a9b0ae633006a44c02b723fe6fad07e84b93"
|
source = "git+https://github.com/zed-industries/zed#a7a7335da4ebeb6ebd5c644f108edf2441f7d77b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -5421,7 +5421,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#b864a9b0ae633006a44c02b723fe6fad07e84b93"
|
source = "git+https://github.com/zed-industries/zed#a7a7335da4ebeb6ebd5c644f108edf2441f7d77b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -5715,18 +5715,18 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strum"
|
name = "strum"
|
||||||
version = "0.26.3"
|
version = "0.27.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strum_macros"
|
name = "strum_macros"
|
||||||
version = "0.26.4"
|
version = "0.27.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
|
checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -5744,7 +5744,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#b864a9b0ae633006a44c02b723fe6fad07e84b93"
|
source = "git+https://github.com/zed-industries/zed#a7a7335da4ebeb6ebd5c644f108edf2441f7d77b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"log",
|
"log",
|
||||||
@@ -6646,7 +6646,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#b864a9b0ae633006a44c02b723fe6fad07e84b93"
|
source = "git+https://github.com/zed-industries/zed#a7a7335da4ebeb6ebd5c644f108edf2441f7d77b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-fs",
|
"async-fs",
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
name = "coop"
|
name = "coop"
|
||||||
description = "Coop is a cross-platform Nostr client designed for secure communication focus on simplicity and customizability."
|
description = "Coop is a cross-platform Nostr client designed for secure communication focus on simplicity and customizability."
|
||||||
product-name = "Coop"
|
product-name = "Coop"
|
||||||
|
version = "0.1.4"
|
||||||
|
category = "SocialNetworking"
|
||||||
identifier = "su.reya.coop"
|
identifier = "su.reya.coop"
|
||||||
resources = ["assets/*/*", "Cargo.toml", "./LICENSE", "./README.md"]
|
resources = ["assets/*/*", "Cargo.toml", "./LICENSE", "./README.md"]
|
||||||
|
binaries = [ { path = "coop", main = true } ]
|
||||||
|
before-packaging-command = "cargo build --release"
|
||||||
|
out-dir = "./target/release"
|
||||||
icons = [
|
icons = [
|
||||||
"crates/coop/resources/32x32.png",
|
"crates/coop/resources/32x32.png",
|
||||||
"crates/coop/resources/128x128.png",
|
"crates/coop/resources/128x128.png",
|
||||||
@@ -11,8 +16,3 @@ icons = [
|
|||||||
"crates/coop/resources/app-icon.png",
|
"crates/coop/resources/app-icon.png",
|
||||||
"crates/coop/resources/app-icon.ico",
|
"crates/coop/resources/app-icon.ico",
|
||||||
]
|
]
|
||||||
before-packaging-command = "cargo build --release"
|
|
||||||
out-dir = "./target/release"
|
|
||||||
binaries = [
|
|
||||||
{ path = "coop", main = true },
|
|
||||||
]
|
|
||||||
|
|||||||
3
assets/icons/address-book.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 stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19.25 21.25H6c-.69 0-1.25-.56-1.25-1.25M11.5 9.25h1M4.75 20V4.75a2 2 0 0 1 2-2h12.5v16H6c-.69 0-1.25.56-1.25 1.25Zm5-6.25s0-1.5 2.25-1.5 2.25 1.5 2.25 1.5h-4.5ZM13 9.25a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 404 B |
1
assets/icons/arrows-in.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" viewBox="0 0 256 256"><path d="M213.66,53.66,163.31,104H192a8,8,0,0,1,0,16H144a8,8,0,0,1-8-8V64a8,8,0,0,1,16,0V92.69l50.34-50.35a8,8,0,0,1,11.32,11.32ZM112,136H64a8,8,0,0,0,0,16H92.69L42.34,202.34a8,8,0,0,0,11.32,11.32L104,163.31V192a8,8,0,0,0,16,0V144A8,8,0,0,0,112,136Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 370 B |
1
assets/icons/caret-down-fill.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,48,88H208a8,8,0,0,1,5.66,13.66Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 218 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="m8 10 3.293 3.293a1 1 0 0 0 1.414 0L16 10"/>
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m20 9-6.586 6.586a2 2 0 0 1-2.828 0L4 9"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 247 B After Width: | Height: | Size: 245 B |
1
assets/icons/caret-right.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M181.66,133.66l-80,80a8,8,0,0,1-11.32-11.32L164.69,128,90.34,53.66a8,8,0,0,1,11.32-11.32l80,80A8,8,0,0,1,181.66,133.66Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 249 B |
1
assets/icons/caret-up.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M213.66,165.66a8,8,0,0,1-11.32,0L128,91.31,53.66,165.66a8,8,0,0,1-11.32-11.32l80-80a8,8,0,0,1,11.32,0l80,80A8,8,0,0,1,213.66,165.66Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 262 B |
1
assets/icons/check-circle-fill.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm45.66,85.66-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35a8,8,0,0,1,11.32,11.32Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 298 B |
1
assets/icons/check-circle.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 369 B |
1
assets/icons/check.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 245 B |
@@ -1,3 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
|
||||||
<path fill="currentColor" d="M9.8 10.25c-1.052 0-1.633 1.221-.97 2.038l2.2 2.707c.5.616 1.44.616 1.94 0l2.2-2.707c.664-.817.082-2.038-.97-2.038H9.8Z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 257 B |
@@ -1,3 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path fill="#000" fill-rule="evenodd" d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2Zm3.58 7.975a.75.75 0 0 0-1.16-.95l-3.976 4.859L9.03 12.47a.75.75 0 0 0-1.06 1.06l2 2a.75.75 0 0 0 1.11-.055l4.5-5.5Z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 365 B |
|
Before Width: | Height: | Size: 429 B After Width: | Height: | Size: 429 B |
3
assets/icons/close-circle.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" fill-rule="evenodd" d="M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm7.53-3.53a.75.75 0 0 0-1.06 1.06L10.94 12l-2.47 2.47a.75.75 0 1 0 1.06 1.06L12 13.06l2.47 2.47a.75.75 0 1 0 1.06-1.06L13.06 12l2.47-2.47a.75.75 0 0 0-1.06-1.06L12 10.94 9.53 8.47Z" clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 429 B |
@@ -1,3 +1 @@
|
|||||||
<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="currentColor" viewBox="0 0 256 256"><path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"></path></svg>
|
||||||
<path fill="currentColor" fill-rule="evenodd" d="M4.116 4.116a1.25 1.25 0 0 1 1.768 0L12 10.232l6.116-6.116a1.25 1.25 0 0 1 1.768 1.768L13.768 12l6.116 6.116a1.25 1.25 0 0 1-1.768 1.768L12 13.768l-6.116 6.116a1.25 1.25 0 0 1-1.768-1.768L10.232 12 4.116 5.884a1.25 1.25 0 0 1 0-1.768Z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 412 B After Width: | Height: | Size: 314 B |
@@ -1,3 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path fill="#000" fill-rule="evenodd" d="M19.432 2.738c.505.54.728 1.327.443 2.133-.606 1.713-1.798 3.124-2.797 4.087a15.74 15.74 0 0 1-1.045.921l.137.1c.93.684 1.416 1.975.757 3.118-1.221 2.12-4.356 5.803-11.192 5.803a.753.753 0 0 1-.15-.015A32.702 32.702 0 0 0 5.5 21.25a.75.75 0 0 1-1.5 0c0-4.43.821-8.93 2.909-12.485 2.106-3.587 5.49-6.182 10.492-6.749a2.404 2.404 0 0 1 2.031.722Z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 522 B |
@@ -1,3 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
|
||||||
<path fill="currentColor" fill-rule="evenodd" d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10ZM8.405 10.2a.75.75 0 0 1-1.12.263l-2.428-1.82a.707.707 0 0 1-.204-.92 8.496 8.496 0 0 1 8.675-4.12c.409.064.653.475.553.877l-.77 3.084a.75.75 0 0 1-.545.546l-3.226.81a.75.75 0 0 0-.487.39l-.448.889Zm6.805 6.385a.75.75 0 0 1-.671.415h-2.135a.75.75 0 0 1-.624-.334l-1.436-2.153a.75.75 0 0 1 .095-.948l.433-.431a.75.75 0 0 1 .577-.217l1.403.09a.75.75 0 0 1 .37.125l2.233 1.5a.75.75 0 0 1 .252.958l-.498.995Z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 654 B |
@@ -1,3 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path fill="#000" d="M4.75 3A2.75 2.75 0 0 0 2 5.75v11.5A2.75 2.75 0 0 0 4.75 20h14.5A2.75 2.75 0 0 0 22 17.25v-8.5A2.75 2.75 0 0 0 19.25 6h-6.18a1.25 1.25 0 0 1-1.04-.557l-.812-1.218A2.75 2.75 0 0 0 8.93 3H4.75Z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 329 B |
@@ -1,3 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
|
||||||
<path fill="#000" fill-rule="evenodd" d="M4.75 3A2.75 2.75 0 0 0 2 5.75v11.927A2.323 2.323 0 0 0 4.323 20h13.682a2.75 2.75 0 0 0 2.638-1.974l1.7-5.782A1.75 1.75 0 0 0 21 10.032V8.75A2.75 2.75 0 0 0 18.25 6h-5.715a.25.25 0 0 1-.208-.111l-1.11-1.664A2.75 2.75 0 0 0 8.93 3H4.75Zm14.75 7V8.75c0-.69-.56-1.25-1.25-1.25h-5.715a1.75 1.75 0 0 1-1.456-.78L9.97 5.058A1.25 1.25 0 0 0 8.93 4.5H4.75c-.69 0-1.25.56-1.25 1.25v11.927a.823.823 0 0 0 1.613.232l1.745-5.935A2.75 2.75 0 0 1 9.496 10H19.5Z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 617 B |
@@ -1,3 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path fill="#000" d="M3.999 7a4 4 0 1 1 8 0 4 4 0 0 1-8 0Zm9.499.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0ZM7.999 12c-1.765 0-3.236.635-4.365 1.72-1.117 1.074-1.868 2.557-2.282 4.225C.932 19.64 2.351 21 3.9 21h8.197c1.55 0 2.968-1.361 2.548-3.055-.413-1.668-1.164-3.151-2.281-4.225-1.13-1.085-2.6-1.72-4.365-1.72Zm6.174.715c1.21 1.337 1.983 3.011 2.414 4.749.231.934.167 1.79-.103 2.536h3.86c1.538 0 2.996-1.365 2.51-3.075C22.06 14.14 20.103 12 16.997 12c-1.08 0-2.023.26-2.825.715Z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 595 B |
@@ -1,3 +0,0 @@
|
|||||||
<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="M17.75 19.25h2.596c1.163 0 2.106-1.001 1.788-2.12-.733-2.573-2.465-4.38-5.134-4.38-.446 0-.866.05-1.26.147M11.25 7a3.25 3.25 0 1 1-6.5 0 3.25 3.25 0 0 1 6.5 0Zm8.5.5a2.75 2.75 0 1 1-5.5 0 2.75 2.75 0 0 1 5.5 0ZM2.08 18.126c.78-3.14 2.78-5.376 5.92-5.376s5.14 2.237 5.918 5.376c.28 1.128-.658 2.124-1.82 2.124H3.901c-1.162 0-2.1-.996-1.82-2.124Z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 550 B |
1
assets/icons/mailbox-fill.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M104,152a8,8,0,0,1-8,8H56a8,8,0,0,1,0-16H96A8,8,0,0,1,104,152ZM168,32h24a8,8,0,0,0,0-16H160a8,8,0,0,0-8,8V56h16Zm72,84v60a16,16,0,0,1-16,16H136v32a8,8,0,0,1-16,0V192H32a16,16,0,0,1-16-16V116A60.07,60.07,0,0,1,76,56h76v88a8,8,0,0,0,16,0V56h12A60.07,60.07,0,0,1,240,116Zm-120,0a44,44,0,0,0-88,0v60h88Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 429 B |
@@ -1,4 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
|
||||||
<path fill="currentColor" fill-rule="evenodd" d="M3 13.745V5.75A2.75 2.75 0 0 1 5.75 3h12.5A2.75 2.75 0 0 1 21 5.75v12.5A2.75 2.75 0 0 1 18.25 21H5.75A2.75 2.75 0 0 1 3 18.25M5.75 4.5h12.5c.69 0 1.25.56 1.25 1.25V13h-3.57a.75.75 0 0 0-.737.61 3.251 3.251 0 0 1-6.386 0A.75.75 0 0 0 8.07 13H4.5V5.75c0-.69.56-1.25 1.25-1.25Z" clip-rule="evenodd"/>
|
|
||||||
<path fill="currentColor" d="M3 18.25v-4.505"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 502 B |
1
assets/icons/plus-circle-fill.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M128,24A104,104,0,1,0,232,128,104.13,104.13,0,0,0,128,24Zm40,112H136v32a8,8,0,0,1-16,0V136H88a8,8,0,0,1,0-16h32V88a8,8,0,0,1,16,0v32h32a8,8,0,0,1,0,16Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 281 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="M12 19.25V13m0 0 2.5 2.5M12 13l-2.5 2.5m-2.125 3.75H4.75a2 2 0 0 1-2-2V5.75a2 2 0 0 1 2-2h4.18a2 2 0 0 1 1.664.89l1.11 1.665a1 1 0 0 0 .831.445h6.715a2 2 0 0 1 2 2v8.5a2 2 0 0 1-2 2h-2.625"/>
|
<path fill="currentColor" fill-rule="evenodd" d="M2 4.75A2.75 2.75 0 0 1 4.75 2h8.5A2.75 2.75 0 0 1 16 4.75V8h3.25A2.75 2.75 0 0 1 22 10.75v8.5A2.75 2.75 0 0 1 19.25 22h-8.5A2.75 2.75 0 0 1 8 19.25V16H4.75A2.75 2.75 0 0 1 2 13.25v-8.5ZM14.5 8V4.75c0-.69-.56-1.25-1.25-1.25h-8.5c-.69 0-1.25.56-1.25 1.25v5.991l.983-.644a2.75 2.75 0 0 1 3.033.012l.5.334A2.75 2.75 0 0 1 10.75 8h3.75ZM5 6.25a1.25 1.25 0 1 1 2.5 0 1.25 1.25 0 0 1-2.5 0Zm8.39 6.292a.75.75 0 0 1 .766.027l2.8 1.8a.75.75 0 0 1 0 1.262l-2.8 1.8A.75.75 0 0 1 13 16.8v-3.6a.75.75 0 0 1 .39-.658Z" clip-rule="evenodd"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 394 B After Width: | Height: | Size: 682 B |
1
assets/icons/users-three-fill.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M64.12,147.8a4,4,0,0,1-4,4.2H16a8,8,0,0,1-7.8-6.17,8.35,8.35,0,0,1,1.62-6.93A67.79,67.79,0,0,1,37,117.51a40,40,0,1,1,66.46-35.8,3.94,3.94,0,0,1-2.27,4.18A64.08,64.08,0,0,0,64,144C64,145.28,64,146.54,64.12,147.8Zm182-8.91A67.76,67.76,0,0,0,219,117.51a40,40,0,1,0-66.46-35.8,3.94,3.94,0,0,0,2.27,4.18A64.08,64.08,0,0,1,192,144c0,1.28,0,2.54-.12,3.8a4,4,0,0,0,4,4.2H240a8,8,0,0,0,7.8-6.17A8.33,8.33,0,0,0,246.17,138.89Zm-89,43.18a48,48,0,1,0-58.37,0A72.13,72.13,0,0,0,65.07,212,8,8,0,0,0,72,224H184a8,8,0,0,0,6.93-12A72.15,72.15,0,0,0,157.19,182.07Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 676 B |
@@ -152,7 +152,9 @@ impl Room {
|
|||||||
/// The Profile of the first member in the room
|
/// The Profile of the first member in the room
|
||||||
pub fn first_member(&self, cx: &App) -> Profile {
|
pub fn first_member(&self, cx: &App) -> Profile {
|
||||||
let account = Account::global(cx).read(cx);
|
let account = Account::global(cx).read(cx);
|
||||||
let profile = account.profile.clone().unwrap();
|
let Some(profile) = account.profile.clone() else {
|
||||||
|
return self.profile_by_pubkey(&self.members[0], cx);
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(public_key) = self
|
if let Some(public_key) = self
|
||||||
.members
|
.members
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ impl SharedProfile for Profile {
|
|||||||
.filter(|picture| !picture.is_empty())
|
.filter(|picture| !picture.is_empty())
|
||||||
.map(|picture| {
|
.map(|picture| {
|
||||||
format!(
|
format!(
|
||||||
"{}/?url={}&w=100&h=100&fit=cover&mask=circle&n=-1",
|
"{}/?url={}&w=100&h=100&fit=cover&mask=circle&n=-1&default=npub1zfss807aer0j26mwp2la0ume0jqde3823rmu97ra6sgyyg956e0s6xw445.blossom.band/c30703b48f511c293a9003be8100cdad37b8798b77a1dc3ec6eb8a20443d5dea.png",
|
||||||
IMAGE_SERVICE, picture
|
IMAGE_SERVICE, picture
|
||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
|
|||||||
@@ -1,31 +1,47 @@
|
|||||||
use account::Account;
|
use account::Account;
|
||||||
use common::profile::SharedProfile;
|
|
||||||
use global::get_client;
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, img, impl_internal_actions, prelude::FluentBuilder, px, App, AppContext, Axis,
|
div, impl_internal_actions, prelude::FluentBuilder, px, App, AppContext, Axis, Context, Entity,
|
||||||
Context, Entity, InteractiveElement, IntoElement, ParentElement, Render, Styled, Subscription,
|
InteractiveElement, IntoElement, ParentElement, Render, Styled, Subscription, Window,
|
||||||
Task, Window,
|
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use ui::{
|
use ui::{
|
||||||
button::{Button, ButtonRounded, ButtonVariants},
|
button::{Button, ButtonVariants},
|
||||||
dock_area::{dock::DockPlacement, panel::PanelView, DockArea, DockItem},
|
dock_area::{dock::DockPlacement, panel::PanelView, DockArea, DockItem},
|
||||||
popup_menu::PopupMenuExt,
|
theme::{ActiveTheme, Appearance, Theme},
|
||||||
theme::{scale::ColorScaleStep, ActiveTheme, Appearance, Theme},
|
ContextModal, IconName, Root, Sizable, TitleBar,
|
||||||
ContextModal, Icon, IconName, Root, Sizable, TitleBar,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::views::{chat, contacts, profile, relays, settings, welcome};
|
use crate::views::{chat, compose, contacts, profile, relays, welcome};
|
||||||
use crate::views::{onboarding, sidebar};
|
use crate::views::{onboarding, sidebar};
|
||||||
|
|
||||||
|
const MODAL_WIDTH: f32 = 420.;
|
||||||
|
const SIDEBAR_WIDTH: f32 = 280.;
|
||||||
|
|
||||||
|
impl_internal_actions!(dock, [AddPanel, ToggleModal]);
|
||||||
|
|
||||||
|
pub fn init(window: &mut Window, cx: &mut App) -> Entity<ChatSpace> {
|
||||||
|
ChatSpace::new(window, cx)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
||||||
pub enum PanelKind {
|
pub enum PanelKind {
|
||||||
Room(u64),
|
Room(u64),
|
||||||
|
// More kind will be added here
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
pub enum ModalKind {
|
||||||
Profile,
|
Profile,
|
||||||
Contacts,
|
Compose,
|
||||||
Settings,
|
Contact,
|
||||||
|
Relay,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
pub struct ToggleModal {
|
||||||
|
pub modal: ModalKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
||||||
@@ -40,16 +56,6 @@ impl AddPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dock actions
|
|
||||||
impl_internal_actions!(dock, [AddPanel]);
|
|
||||||
|
|
||||||
// Account actions
|
|
||||||
actions!(account, [Logout]);
|
|
||||||
|
|
||||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<ChatSpace> {
|
|
||||||
ChatSpace::new(window, cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ChatSpace {
|
pub struct ChatSpace {
|
||||||
titlebar: bool,
|
titlebar: bool,
|
||||||
dock: Entity<DockArea>,
|
dock: Entity<DockArea>,
|
||||||
@@ -61,13 +67,11 @@ impl ChatSpace {
|
|||||||
pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||||
let account = Account::global(cx);
|
let account = Account::global(cx);
|
||||||
let dock = cx.new(|cx| DockArea::new(window, cx));
|
let dock = cx.new(|cx| DockArea::new(window, cx));
|
||||||
let titlebar = false;
|
|
||||||
|
|
||||||
cx.new(|cx| {
|
cx.new(|cx| {
|
||||||
let mut this = Self {
|
let mut subscriptions = smallvec![];
|
||||||
dock,
|
|
||||||
titlebar,
|
subscriptions.push(cx.observe_in(
|
||||||
subscriptions: smallvec![cx.observe_in(
|
|
||||||
&account,
|
&account,
|
||||||
window,
|
window,
|
||||||
|this: &mut ChatSpace, account, window, cx| {
|
|this: &mut ChatSpace, account, window, cx| {
|
||||||
@@ -77,7 +81,12 @@ impl ChatSpace {
|
|||||||
this.open_onboarding(window, cx);
|
this.open_onboarding(window, cx);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)],
|
));
|
||||||
|
|
||||||
|
let mut this = Self {
|
||||||
|
dock,
|
||||||
|
subscriptions,
|
||||||
|
titlebar: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if Account::global(cx).read(cx).profile.is_some() {
|
if Account::global(cx).read(cx).profile.is_some() {
|
||||||
@@ -135,7 +144,7 @@ impl ChatSpace {
|
|||||||
);
|
);
|
||||||
|
|
||||||
self.dock.update(cx, |this, cx| {
|
self.dock.update(cx, |this, cx| {
|
||||||
this.set_left_dock(left, Some(px(260.)), true, window, cx);
|
this.set_left_dock(left, Some(px(SIDEBAR_WIDTH)), true, window, cx);
|
||||||
this.set_center(center, window, cx);
|
this.set_center(center, window, cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -165,74 +174,6 @@ impl ChatSpace {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_account_btn(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
|
||||||
Button::new("account")
|
|
||||||
.ghost()
|
|
||||||
.xsmall()
|
|
||||||
.reverse()
|
|
||||||
.icon(Icon::new(IconName::ChevronDownSmall))
|
|
||||||
.when_some(
|
|
||||||
Account::global(cx).read(cx).profile.as_ref(),
|
|
||||||
|this, profile| this.child(img(profile.shared_avatar()).size_5()),
|
|
||||||
)
|
|
||||||
.popup_menu(move |this, _, _cx| {
|
|
||||||
this.menu(
|
|
||||||
"Profile",
|
|
||||||
Box::new(AddPanel::new(PanelKind::Profile, DockPlacement::Right)),
|
|
||||||
)
|
|
||||||
.menu(
|
|
||||||
"Contacts",
|
|
||||||
Box::new(AddPanel::new(PanelKind::Contacts, DockPlacement::Right)),
|
|
||||||
)
|
|
||||||
.menu(
|
|
||||||
"Settings",
|
|
||||||
Box::new(AddPanel::new(PanelKind::Settings, DockPlacement::Center)),
|
|
||||||
)
|
|
||||||
.separator()
|
|
||||||
.menu("Change account", Box::new(Logout))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_relays_btn(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
|
||||||
Button::new("relays")
|
|
||||||
.xsmall()
|
|
||||||
.ghost()
|
|
||||||
.icon(IconName::Relays)
|
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
|
||||||
this.render_edit_relays(window, cx);
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_edit_relays(&self, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
let relays = relays::init(window, cx);
|
|
||||||
|
|
||||||
window.open_modal(cx, move |this, window, cx| {
|
|
||||||
let is_loading = relays.read(cx).loading();
|
|
||||||
|
|
||||||
this.width(px(420.))
|
|
||||||
.title("Edit your Messaging Relays")
|
|
||||||
.child(relays.clone())
|
|
||||||
.footer(
|
|
||||||
div()
|
|
||||||
.p_2()
|
|
||||||
.border_t_1()
|
|
||||||
.border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE))
|
|
||||||
.child(
|
|
||||||
Button::new("update_inbox_relays_btn")
|
|
||||||
.label("Update")
|
|
||||||
.primary()
|
|
||||||
.bold()
|
|
||||||
.rounded(ButtonRounded::Large)
|
|
||||||
.w_full()
|
|
||||||
.loading(is_loading)
|
|
||||||
.on_click(window.listener_for(&relays, |this, _, window, cx| {
|
|
||||||
this.update(window, cx);
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_panel_action(&mut self, action: &AddPanel, window: &mut Window, cx: &mut Context<Self>) {
|
fn on_panel_action(&mut self, action: &AddPanel, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
match &action.panel {
|
match &action.panel {
|
||||||
PanelKind::Room(id) => {
|
PanelKind::Room(id) => {
|
||||||
@@ -246,49 +187,55 @@ impl ChatSpace {
|
|||||||
Err(e) => window.push_notification(e.to_string(), cx),
|
Err(e) => window.push_notification(e.to_string(), cx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PanelKind::Profile => {
|
|
||||||
let panel = profile::init(window, cx);
|
|
||||||
|
|
||||||
self.dock.update(cx, |dock_area, cx| {
|
|
||||||
dock_area.add_panel(panel, action.position, window, cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
PanelKind::Contacts => {
|
|
||||||
let panel = Arc::new(contacts::init(window, cx));
|
|
||||||
|
|
||||||
self.dock.update(cx, |dock_area, cx| {
|
|
||||||
dock_area.add_panel(panel, action.position, window, cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
PanelKind::Settings => {
|
|
||||||
let panel = Arc::new(settings::init(window, cx));
|
|
||||||
|
|
||||||
self.dock.update(cx, |dock_area, cx| {
|
|
||||||
dock_area.add_panel(panel, action.position, window, cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_logout_action(&mut self, _action: &Logout, window: &mut Window, cx: &mut Context<Self>) {
|
fn on_modal_action(
|
||||||
let client = get_client();
|
&mut self,
|
||||||
let reset: Task<Result<(), anyhow::Error>> = cx.background_spawn(async move {
|
action: &ToggleModal,
|
||||||
client.reset().await;
|
window: &mut Window,
|
||||||
Ok(())
|
cx: &mut Context<Self>,
|
||||||
});
|
) {
|
||||||
|
match action.modal {
|
||||||
|
ModalKind::Profile => {
|
||||||
|
let profile = profile::init(window, cx);
|
||||||
|
|
||||||
cx.spawn_in(window, async move |_, cx| {
|
window.open_modal(cx, move |modal, _, _| {
|
||||||
if reset.await.is_ok() {
|
modal
|
||||||
cx.update(|_, cx| {
|
.title("Profile")
|
||||||
Account::global(cx).update(cx, |this, cx| {
|
.width(px(MODAL_WIDTH))
|
||||||
this.profile = None;
|
.child(profile.clone())
|
||||||
cx.notify();
|
})
|
||||||
|
}
|
||||||
|
ModalKind::Compose => {
|
||||||
|
let compose = compose::init(window, cx);
|
||||||
|
|
||||||
|
window.open_modal(cx, move |modal, _, _| {
|
||||||
|
modal
|
||||||
|
.title("Direct Messages")
|
||||||
|
.width(px(MODAL_WIDTH))
|
||||||
|
.child(compose.clone())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ModalKind::Contact => {
|
||||||
|
let contacts = contacts::init(window, cx);
|
||||||
|
|
||||||
|
window.open_modal(cx, move |this, _window, _cx| {
|
||||||
|
this.width(px(MODAL_WIDTH))
|
||||||
|
.title("Contacts")
|
||||||
|
.child(contacts.clone())
|
||||||
});
|
});
|
||||||
})
|
}
|
||||||
.ok();
|
ModalKind::Relay => {
|
||||||
|
let relays = relays::init(window, cx);
|
||||||
|
|
||||||
|
window.open_modal(cx, move |this, _, _| {
|
||||||
|
this.width(px(MODAL_WIDTH))
|
||||||
|
.title("Edit your Messaging Relays")
|
||||||
|
.child(relays.clone())
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,9 +266,7 @@ impl Render for ChatSpace {
|
|||||||
.justify_end()
|
.justify_end()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.px_2()
|
.px_2()
|
||||||
.child(self.render_appearance_btn(cx))
|
.child(self.render_appearance_btn(cx)),
|
||||||
.child(self.render_relays_btn(cx))
|
|
||||||
.child(self.render_account_btn(cx)),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -334,6 +279,6 @@ impl Render for ChatSpace {
|
|||||||
.children(modal_layer)
|
.children(modal_layer)
|
||||||
// Actions
|
// Actions
|
||||||
.on_action(cx.listener(Self::on_panel_action))
|
.on_action(cx.listener(Self::on_panel_action))
|
||||||
.on_action(cx.listener(Self::on_logout_action))
|
.on_action(cx.listener(Self::on_modal_action))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ fn main() {
|
|||||||
}),
|
}),
|
||||||
window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
|
window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
|
||||||
None,
|
None,
|
||||||
size(px(900.0), px(680.0)),
|
size(px(920.0), px(700.0)),
|
||||||
cx,
|
cx,
|
||||||
))),
|
))),
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ use smallvec::{smallvec, SmallVec};
|
|||||||
use smol::fs;
|
use smol::fs;
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
use ui::{
|
use ui::{
|
||||||
button::{Button, ButtonRounded, ButtonVariants},
|
button::{Button, ButtonVariants},
|
||||||
dock_area::panel::{Panel, PanelEvent},
|
dock_area::panel::{Panel, PanelEvent},
|
||||||
input::{InputEvent, TextInput},
|
input::{InputEvent, TextInput},
|
||||||
notification::Notification,
|
notification::Notification,
|
||||||
popup_menu::PopupMenu,
|
popup_menu::PopupMenu,
|
||||||
text::RichText,
|
text::RichText,
|
||||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||||
v_flex, ContextModal, Icon, IconName, Sizable, StyledExt,
|
v_flex, ContextModal, Disableable, Icon, IconName, Size, StyledExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ALERT: &str = "has not set up Messaging (DM) Relays, so they will NOT receive your messages.";
|
const ALERT: &str = "has not set up Messaging (DM) Relays, so they will NOT receive your messages.";
|
||||||
@@ -58,8 +58,7 @@ impl Chat {
|
|||||||
let attaches = cx.new(|_| None);
|
let attaches = cx.new(|_| None);
|
||||||
let input = cx.new(|cx| {
|
let input = cx.new(|cx| {
|
||||||
TextInput::new(window, cx)
|
TextInput::new(window, cx)
|
||||||
.appearance(false)
|
.text_size(Size::Small)
|
||||||
.text_size(ui::Size::Small)
|
|
||||||
.placeholder("Message...")
|
.placeholder("Message...")
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -343,11 +342,12 @@ impl Chat {
|
|||||||
|
|
||||||
div()
|
div()
|
||||||
.group("")
|
.group("")
|
||||||
|
.w_full()
|
||||||
.relative()
|
.relative()
|
||||||
.flex()
|
.flex()
|
||||||
.gap_3()
|
.gap_3()
|
||||||
.w_full()
|
.px_3()
|
||||||
.p_2()
|
.py_2()
|
||||||
.map(|this| match message {
|
.map(|this| match message {
|
||||||
RoomMessage::User(item) => {
|
RoomMessage::User(item) => {
|
||||||
let text = text_data
|
let text = text_data
|
||||||
@@ -355,6 +355,7 @@ impl Chat {
|
|||||||
.or_insert_with(|| RichText::new(item.content.to_owned(), &item.mentions));
|
.or_insert_with(|| RichText::new(item.content.to_owned(), &item.mentions));
|
||||||
|
|
||||||
this.hover(|this| this.bg(cx.theme().accent.step(cx, ColorScaleStep::ONE)))
|
this.hover(|this| this.bg(cx.theme().accent.step(cx, ColorScaleStep::ONE)))
|
||||||
|
.text_sm()
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.absolute()
|
.absolute()
|
||||||
@@ -379,19 +380,18 @@ impl Chat {
|
|||||||
.flex()
|
.flex()
|
||||||
.items_baseline()
|
.items_baseline()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.text_xs()
|
|
||||||
.child(
|
.child(
|
||||||
div().font_semibold().child(item.author.shared_name()),
|
div().font_semibold().child(item.author.shared_name()),
|
||||||
)
|
)
|
||||||
.child(div().child(item.ago()).text_color(
|
.child(
|
||||||
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
|
div()
|
||||||
)),
|
.text_color(
|
||||||
|
cx.theme().base.step(cx, ColorScaleStep::NINE),
|
||||||
)
|
)
|
||||||
.child(div().text_sm().child(text.element(
|
.child(item.ago()),
|
||||||
"body".into(),
|
),
|
||||||
window,
|
)
|
||||||
cx,
|
.child(text.element("body".into(), window, cx)),
|
||||||
))),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
RoomMessage::System(content) => this
|
RoomMessage::System(content) => this
|
||||||
@@ -407,7 +407,7 @@ impl Chat {
|
|||||||
.group_hover("", |this| this.bg(cx.theme().danger)),
|
.group_hover("", |this| this.bg(cx.theme().danger)),
|
||||||
)
|
)
|
||||||
.child(img("brand/avatar.png").size_8().flex_shrink_0())
|
.child(img("brand/avatar.png").size_8().flex_shrink_0())
|
||||||
.text_xs()
|
.text_sm()
|
||||||
.text_color(cx.theme().danger)
|
.text_color(cx.theme().danger)
|
||||||
.child(content.clone()),
|
.child(content.clone()),
|
||||||
RoomMessage::Announcement => this
|
RoomMessage::Announcement => this
|
||||||
@@ -419,12 +419,12 @@ impl Chat {
|
|||||||
.justify_center()
|
.justify_center()
|
||||||
.text_center()
|
.text_center()
|
||||||
.text_xs()
|
.text_xs()
|
||||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
.text_color(cx.theme().base.step(cx, ColorScaleStep::NINE))
|
||||||
.line_height(relative(1.3))
|
.line_height(relative(1.3))
|
||||||
.child(
|
.child(
|
||||||
svg()
|
svg()
|
||||||
.path("brand/coop.svg")
|
.path("brand/coop.svg")
|
||||||
.size_8()
|
.size_10()
|
||||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::THREE)),
|
.text_color(cx.theme().base.step(cx, ColorScaleStep::THREE)),
|
||||||
)
|
)
|
||||||
.child(ROOM_DESCRIPTION),
|
.child(ROOM_DESCRIPTION),
|
||||||
@@ -444,7 +444,7 @@ impl Panel for Chat {
|
|||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_1()
|
.gap_1p5()
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
@@ -459,7 +459,7 @@ impl Panel for Chat {
|
|||||||
.map(|(ix, facepill)| {
|
.map(|(ix, facepill)| {
|
||||||
div()
|
div()
|
||||||
.when(ix > 0, |div| div.ml_neg_1())
|
.when(ix > 0, |div| div.ml_neg_1())
|
||||||
.child(img(facepill).size_4())
|
.child(img(facepill).size_5())
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -491,7 +491,7 @@ impl Render for Chat {
|
|||||||
.size_full()
|
.size_full()
|
||||||
.child(list(self.list_state.clone()).flex_1())
|
.child(list(self.list_state.clone()).flex_1())
|
||||||
.child(
|
.child(
|
||||||
div().flex_shrink_0().p_2().child(
|
div().flex_shrink_0().px_3().py_2().child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
@@ -539,7 +539,6 @@ impl Render for Chat {
|
|||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.w_full()
|
.w_full()
|
||||||
.h_9()
|
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
@@ -550,30 +549,10 @@ impl Render for Chat {
|
|||||||
.on_click(cx.listener(move |this, _, window, cx| {
|
.on_click(cx.listener(move |this, _, window, cx| {
|
||||||
this.upload_media(window, cx);
|
this.upload_media(window, cx);
|
||||||
}))
|
}))
|
||||||
|
.disabled(self.is_uploading)
|
||||||
.loading(self.is_uploading),
|
.loading(self.is_uploading),
|
||||||
)
|
)
|
||||||
.child(
|
.child(self.input.clone()),
|
||||||
div()
|
|
||||||
.flex_1()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
|
|
||||||
.rounded(px(cx.theme().radius))
|
|
||||||
.pl_2()
|
|
||||||
.pr_1()
|
|
||||||
.child(self.input.clone())
|
|
||||||
.child(
|
|
||||||
Button::new("send")
|
|
||||||
.ghost()
|
|
||||||
.xsmall()
|
|
||||||
.bold()
|
|
||||||
.rounded(ButtonRounded::Medium)
|
|
||||||
.label("SEND")
|
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
|
||||||
this.send_message(window, cx)
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ use common::{profile::SharedProfile, random_name};
|
|||||||
use global::get_client;
|
use global::get_client;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, img, impl_internal_actions, prelude::FluentBuilder, px, relative, uniform_list, App,
|
div, img, impl_internal_actions, prelude::FluentBuilder, px, relative, uniform_list, App,
|
||||||
AppContext, ClickEvent, Context, Div, Entity, FocusHandle, InteractiveElement, IntoElement,
|
AppContext, Context, Entity, FocusHandle, InteractiveElement, IntoElement, ParentElement,
|
||||||
ParentElement, Render, RenderOnce, SharedString, StatefulInteractiveElement, Styled,
|
Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task, TextAlign,
|
||||||
Subscription, Task, TextAlign, Window,
|
Window,
|
||||||
};
|
};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@@ -17,18 +17,18 @@ use smallvec::{smallvec, SmallVec};
|
|||||||
use smol::Timer;
|
use smol::Timer;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeSet, HashSet},
|
collections::{BTreeSet, HashSet},
|
||||||
rc::Rc,
|
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use ui::{
|
use ui::{
|
||||||
button::{Button, ButtonRounded},
|
button::{Button, ButtonVariants},
|
||||||
input::{InputEvent, TextInput},
|
input::{InputEvent, TextInput},
|
||||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||||
ContextModal, Icon, IconName, Sizable, Size, StyledExt,
|
ContextModal, Disableable, Icon, IconName, Sizable, Size, StyledExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DESCRIPTION: &str =
|
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Compose> {
|
||||||
"Start a conversation with someone using their npub or NIP-05 (like foo@bar.com).";
|
cx.new(|cx| Compose::new(window, cx))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
||||||
struct SelectContact(PublicKey);
|
struct SelectContact(PublicKey);
|
||||||
@@ -58,7 +58,7 @@ impl Compose {
|
|||||||
let name = random_name(2);
|
let name = random_name(2);
|
||||||
let mut input = TextInput::new(window, cx)
|
let mut input = TextInput::new(window, cx)
|
||||||
.appearance(false)
|
.appearance(false)
|
||||||
.text_size(Size::XSmall);
|
.text_size(Size::Small);
|
||||||
|
|
||||||
input.set_placeholder("Family... . (Optional)");
|
input.set_placeholder("Family... . (Optional)");
|
||||||
input.set_text(name, window, cx);
|
input.set_text(name, window, cx);
|
||||||
@@ -193,18 +193,6 @@ impl Compose {
|
|||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn label(&self, _window: &Window, cx: &App) -> SharedString {
|
|
||||||
if self.selected.read(cx).len() > 1 {
|
|
||||||
"Create Group DM".into()
|
|
||||||
} else {
|
|
||||||
"Create DM".into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_submitting(&self) -> bool {
|
|
||||||
self.is_submitting
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn add(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let client = get_client();
|
let client = get_client();
|
||||||
let content = self.user_input.read(cx).text().to_string();
|
let content = self.user_input.read(cx).text().to_string();
|
||||||
@@ -336,6 +324,15 @@ impl Compose {
|
|||||||
|
|
||||||
impl Render for Compose {
|
impl Render for Compose {
|
||||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
const DESCRIPTION: &str =
|
||||||
|
"Start a conversation with someone using their npub or NIP-05 (like foo@bar.com).";
|
||||||
|
|
||||||
|
let label: SharedString = if self.selected.read(cx).len() > 1 {
|
||||||
|
"Create Group DM".into()
|
||||||
|
} else {
|
||||||
|
"Create DM".into()
|
||||||
|
};
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
.on_action(cx.listener(Self::on_action_select))
|
.on_action(cx.listener(Self::on_action_select))
|
||||||
@@ -344,15 +341,13 @@ impl Render for Compose {
|
|||||||
.gap_1()
|
.gap_1()
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.px_2()
|
.text_sm()
|
||||||
.text_xs()
|
|
||||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
||||||
.child(DESCRIPTION),
|
.child(DESCRIPTION),
|
||||||
)
|
)
|
||||||
.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_2()
|
|
||||||
.text_xs()
|
.text_xs()
|
||||||
.text_color(cx.theme().danger)
|
.text_color(cx.theme().danger)
|
||||||
.child(msg.clone()),
|
.child(msg.clone()),
|
||||||
@@ -362,13 +357,12 @@ impl Render for Compose {
|
|||||||
div().flex().flex_col().child(
|
div().flex().flex_col().child(
|
||||||
div()
|
div()
|
||||||
.h_10()
|
.h_10()
|
||||||
.px_2()
|
|
||||||
.border_b_1()
|
.border_b_1()
|
||||||
.border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE))
|
.border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE))
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(div().text_xs().font_semibold().child("Title:"))
|
.child(div().pb_0p5().text_sm().font_semibold().child("Title:"))
|
||||||
.child(self.title_input.clone()),
|
.child(self.title_input.clone()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -377,25 +371,9 @@ impl Render for Compose {
|
|||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.child(div().px_2().text_xs().font_semibold().child("To:"))
|
.mt_1()
|
||||||
.child(
|
.child(div().text_sm().font_semibold().child("To:"))
|
||||||
div()
|
.child(self.user_input.clone())
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.gap_2()
|
|
||||||
.px_2()
|
|
||||||
.child(
|
|
||||||
Button::new("add_user_to_compose_btn")
|
|
||||||
.icon(IconName::Plus)
|
|
||||||
.small()
|
|
||||||
.rounded(ButtonRounded::Size(px(9999.)))
|
|
||||||
.loading(self.is_loading)
|
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
|
||||||
this.add(window, cx);
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.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();
|
||||||
@@ -444,30 +422,35 @@ impl Render for Compose {
|
|||||||
div()
|
div()
|
||||||
.id(ix)
|
.id(ix)
|
||||||
.w_full()
|
.w_full()
|
||||||
.h_9()
|
.h_10()
|
||||||
.px_2()
|
.px_2()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
|
.rounded(px(cx.theme().radius))
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_2()
|
.gap_3()
|
||||||
.text_xs()
|
.text_sm()
|
||||||
.child(div().flex_shrink_0().child(
|
.child(
|
||||||
img(item.shared_avatar()).size_6(),
|
img(item.shared_avatar())
|
||||||
))
|
.size_7()
|
||||||
|
.flex_shrink_0(),
|
||||||
|
)
|
||||||
.child(item.shared_name()),
|
.child(item.shared_name()),
|
||||||
)
|
)
|
||||||
.when(is_select, |this| {
|
.when(is_select, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
Icon::new(IconName::CircleCheck)
|
Icon::new(IconName::CheckCircleFill)
|
||||||
.size_3()
|
.small()
|
||||||
.text_color(cx.theme().base.step(
|
.text_color(
|
||||||
|
cx.theme().accent.step(
|
||||||
cx,
|
cx,
|
||||||
ColorScaleStep::TWELVE,
|
ColorScaleStep::NINE,
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.hover(|this| {
|
.hover(|this| {
|
||||||
@@ -490,71 +473,22 @@ impl Render for Compose {
|
|||||||
items
|
items
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.min_h(px(250.)),
|
.pb_4()
|
||||||
|
.min_h(px(280.)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Handler = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>;
|
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
pub struct ComposeButton {
|
|
||||||
base: Div,
|
|
||||||
label: SharedString,
|
|
||||||
handler: Handler,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ComposeButton {
|
|
||||||
pub fn new(label: impl Into<SharedString>) -> Self {
|
|
||||||
Self {
|
|
||||||
base: div(),
|
|
||||||
label: label.into(),
|
|
||||||
handler: Rc::new(|_, _, _| {}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_click(
|
|
||||||
mut self,
|
|
||||||
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
|
||||||
) -> Self {
|
|
||||||
self.handler = Rc::new(handler);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for ComposeButton {
|
|
||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
||||||
let handler = self.handler.clone();
|
|
||||||
|
|
||||||
self.base
|
|
||||||
.id("compose")
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.gap_2()
|
|
||||||
.px_1()
|
|
||||||
.h_7()
|
|
||||||
.text_xs()
|
|
||||||
.font_semibold()
|
|
||||||
.rounded(px(cx.theme().radius))
|
|
||||||
.hover(|this| this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE)))
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div().mt_2().child(
|
||||||
.size_6()
|
Button::new("create_dm_btn")
|
||||||
.flex()
|
.label(label)
|
||||||
.items_center()
|
.primary()
|
||||||
.justify_center()
|
.w_full()
|
||||||
.rounded_full()
|
.loading(self.is_submitting)
|
||||||
.bg(cx.theme().accent.step(cx, ColorScaleStep::NINE))
|
.disabled(self.is_submitting)
|
||||||
.child(
|
.on_click(cx.listener(|this, _, window, cx| this.compose(window, cx))),
|
||||||
Icon::new(IconName::ComposeFill)
|
|
||||||
.small()
|
|
||||||
.text_color(cx.theme().base.darken(cx)),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(self.label.clone())
|
|
||||||
.on_click(move |ev, window, cx| handler(ev, window, cx))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,8 @@ use ui::{
|
|||||||
Sizable,
|
Sizable,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const MIN_HEIGHT: f32 = 280.;
|
||||||
|
|
||||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Contacts> {
|
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Contacts> {
|
||||||
Contacts::new(window, cx)
|
Contacts::new(window, cx)
|
||||||
}
|
}
|
||||||
@@ -108,19 +110,20 @@ impl Focusable for Contacts {
|
|||||||
|
|
||||||
impl Render for Contacts {
|
impl Render for Contacts {
|
||||||
fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
div().size_full().pt_2().px_2().map(|this| {
|
let entity = cx.entity().clone();
|
||||||
|
|
||||||
|
div().map(|this| {
|
||||||
if let Some(contacts) = self.contacts.clone() {
|
if let Some(contacts) = self.contacts.clone() {
|
||||||
this.child(
|
this.child(
|
||||||
uniform_list(
|
uniform_list(
|
||||||
cx.entity().clone(),
|
entity,
|
||||||
"contacts",
|
"contacts",
|
||||||
contacts.len(),
|
contacts.len(),
|
||||||
move |_, range, _window, cx| {
|
move |_, range, _window, cx| {
|
||||||
let mut items = Vec::new();
|
let mut items = Vec::with_capacity(contacts.len());
|
||||||
|
|
||||||
for ix in range {
|
for ix in range {
|
||||||
let item = contacts.get(ix).unwrap().clone();
|
if let Some(item) = contacts.get(ix) {
|
||||||
|
|
||||||
items.push(
|
items.push(
|
||||||
div()
|
div()
|
||||||
.w_full()
|
.w_full()
|
||||||
@@ -137,22 +140,26 @@ impl Render for Contacts {
|
|||||||
.gap_2()
|
.gap_2()
|
||||||
.text_xs()
|
.text_xs()
|
||||||
.child(
|
.child(
|
||||||
div()
|
div().flex_shrink_0().child(
|
||||||
.flex_shrink_0()
|
img(item.shared_avatar()).size_6(),
|
||||||
.child(img(item.shared_avatar()).size_6()),
|
),
|
||||||
)
|
)
|
||||||
.child(item.shared_name()),
|
.child(item.shared_name()),
|
||||||
)
|
)
|
||||||
.hover(|this| {
|
.hover(|this| {
|
||||||
this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
|
this.bg(cx
|
||||||
|
.theme()
|
||||||
|
.base
|
||||||
|
.step(cx, ColorScaleStep::THREE))
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
items
|
items
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.h_full(),
|
.min_h(px(MIN_HEIGHT)),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
this.flex()
|
this.flex()
|
||||||
|
|||||||
@@ -17,13 +17,9 @@ use ui::{
|
|||||||
notification::Notification,
|
notification::Notification,
|
||||||
popup_menu::PopupMenu,
|
popup_menu::PopupMenu,
|
||||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||||
ContextModal, Disableable, Icon, IconName, Sizable, Size, StyledExt,
|
ContextModal, Disableable, Sizable, Size, StyledExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::chat_space::ChatSpace;
|
|
||||||
|
|
||||||
use super::onboarding;
|
|
||||||
|
|
||||||
const INPUT_INVALID: &str = "You must provide a valid Private Key or Bunker.";
|
const INPUT_INVALID: &str = "You must provide a valid Private Key or Bunker.";
|
||||||
|
|
||||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Login> {
|
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Login> {
|
||||||
@@ -60,12 +56,12 @@ impl Login {
|
|||||||
|
|
||||||
let key_input = cx.new(|cx| {
|
let key_input = cx.new(|cx| {
|
||||||
TextInput::new(window, cx)
|
TextInput::new(window, cx)
|
||||||
.text_size(Size::XSmall)
|
.text_size(Size::Small)
|
||||||
.placeholder("nsec... or bunker://...")
|
.placeholder("nsec... or bunker://...")
|
||||||
});
|
});
|
||||||
|
|
||||||
let connect_relay = cx.new(|cx| {
|
let connect_relay = cx.new(|cx| {
|
||||||
let mut input = TextInput::new(window, cx).text_size(Size::XSmall).small();
|
let mut input = TextInput::new(window, cx).text_size(Size::Small).small();
|
||||||
input.set_text("wss://relay.nsec.app", window, cx);
|
input.set_text("wss://relay.nsec.app", window, cx);
|
||||||
input
|
input
|
||||||
});
|
});
|
||||||
@@ -233,11 +229,6 @@ impl Login {
|
|||||||
self.is_logging_in = status;
|
self.is_logging_in = status;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn back(&self, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
let panel = onboarding::init(window, cx);
|
|
||||||
ChatSpace::set_center_panel(panel, window, cx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Panel for Login {
|
impl Panel for Login {
|
||||||
@@ -299,14 +290,13 @@ impl Render for Login {
|
|||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_center()
|
.text_center()
|
||||||
.text_lg()
|
.text_xl()
|
||||||
.font_semibold()
|
.font_semibold()
|
||||||
.line_height(relative(1.2))
|
.line_height(relative(1.3))
|
||||||
.child("Welcome Back!"),
|
.child("Welcome Back!"),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_sm()
|
|
||||||
.text_color(
|
.text_color(
|
||||||
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
|
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
|
||||||
)
|
)
|
||||||
@@ -365,7 +355,6 @@ impl Render for Login {
|
|||||||
.text_center()
|
.text_center()
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_sm()
|
|
||||||
.font_semibold()
|
.font_semibold()
|
||||||
.line_height(relative(1.2))
|
.line_height(relative(1.2))
|
||||||
.text_color(
|
.text_color(
|
||||||
@@ -375,7 +364,7 @@ impl Render for Login {
|
|||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_xs()
|
.text_sm()
|
||||||
.text_color(
|
.text_color(
|
||||||
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
|
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
|
||||||
)
|
)
|
||||||
@@ -424,17 +413,5 @@ impl Render for Login {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
|
||||||
div().absolute().left_2().top_10().w_16().child(
|
|
||||||
Button::new("back")
|
|
||||||
.label("Back")
|
|
||||||
.icon(Icon::new(IconName::ArrowLeft))
|
|
||||||
.ghost()
|
|
||||||
.small()
|
|
||||||
.on_click(cx.listener(move |this, _, window, cx| {
|
|
||||||
this.back(window, cx);
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
pub mod chat;
|
pub mod chat;
|
||||||
|
pub mod compose;
|
||||||
pub mod contacts;
|
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 settings;
|
|
||||||
pub mod sidebar;
|
pub mod sidebar;
|
||||||
pub mod welcome;
|
pub mod welcome;
|
||||||
|
|||||||
@@ -19,10 +19,6 @@ use ui::{
|
|||||||
Disableable, Icon, IconName, Sizable, Size, StyledExt,
|
Disableable, Icon, IconName, Sizable, Size, StyledExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::chat_space::ChatSpace;
|
|
||||||
|
|
||||||
use super::onboarding;
|
|
||||||
|
|
||||||
const STEAM_ID_DESCRIPTION: &str =
|
const STEAM_ID_DESCRIPTION: &str =
|
||||||
"Steam ID is used to get your currently playing game and update your status.";
|
"Steam ID is used to get your currently playing game and update your status.";
|
||||||
|
|
||||||
@@ -52,26 +48,26 @@ impl NewAccount {
|
|||||||
fn view(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
fn view(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
let name_input = cx.new(|cx| {
|
let name_input = cx.new(|cx| {
|
||||||
TextInput::new(window, cx)
|
TextInput::new(window, cx)
|
||||||
.text_size(Size::XSmall)
|
.text_size(Size::Small)
|
||||||
.placeholder("Alice")
|
.placeholder("Alice")
|
||||||
});
|
});
|
||||||
|
|
||||||
let avatar_input = cx.new(|cx| {
|
let avatar_input = cx.new(|cx| {
|
||||||
TextInput::new(window, cx)
|
TextInput::new(window, cx)
|
||||||
.text_size(Size::XSmall)
|
.text_size(Size::Small)
|
||||||
.small()
|
.small()
|
||||||
.placeholder("https://example.com/avatar.jpg")
|
.placeholder("https://example.com/avatar.jpg")
|
||||||
});
|
});
|
||||||
|
|
||||||
let steam_input = cx.new(|cx| {
|
let steam_input = cx.new(|cx| {
|
||||||
TextInput::new(window, cx)
|
TextInput::new(window, cx)
|
||||||
.text_size(Size::XSmall)
|
.text_size(Size::Small)
|
||||||
.placeholder("76561199810385277")
|
.placeholder("76561199810385277")
|
||||||
});
|
});
|
||||||
|
|
||||||
let bio_input = cx.new(|cx| {
|
let bio_input = cx.new(|cx| {
|
||||||
TextInput::new(window, cx)
|
TextInput::new(window, cx)
|
||||||
.text_size(Size::XSmall)
|
.text_size(Size::Small)
|
||||||
.multi_line()
|
.multi_line()
|
||||||
.placeholder("A short introduce about you.")
|
.placeholder("A short introduce about you.")
|
||||||
});
|
});
|
||||||
@@ -188,11 +184,6 @@ impl NewAccount {
|
|||||||
self.is_uploading = status;
|
self.is_uploading = status;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn back(&self, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
let panel = onboarding::init(window, cx);
|
|
||||||
ChatSpace::set_center_panel(panel, window, cx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Panel for NewAccount {
|
impl Panel for NewAccount {
|
||||||
@@ -238,13 +229,13 @@ impl Render for NewAccount {
|
|||||||
.flex_col()
|
.flex_col()
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.gap_8()
|
.gap_10()
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_center()
|
.text_center()
|
||||||
.text_lg()
|
.text_lg()
|
||||||
.font_semibold()
|
.font_semibold()
|
||||||
.line_height(relative(1.2))
|
.line_height(relative(1.3))
|
||||||
.child("Create New Account"),
|
.child("Create New Account"),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
@@ -295,7 +286,7 @@ impl Render for NewAccount {
|
|||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.text_xs()
|
.text_sm()
|
||||||
.child("Name *:")
|
.child("Name *:")
|
||||||
.child(self.name_input.clone()),
|
.child(self.name_input.clone()),
|
||||||
)
|
)
|
||||||
@@ -304,7 +295,7 @@ impl Render for NewAccount {
|
|||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.text_xs()
|
.text_sm()
|
||||||
.child("Bio:")
|
.child("Bio:")
|
||||||
.child(self.bio_input.clone()),
|
.child(self.bio_input.clone()),
|
||||||
)
|
)
|
||||||
@@ -313,7 +304,7 @@ impl Render for NewAccount {
|
|||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.text_xs()
|
.text_sm()
|
||||||
.child("Steam ID:")
|
.child("Steam ID:")
|
||||||
.child(self.steam_input.clone())
|
.child(self.steam_input.clone())
|
||||||
.child(
|
.child(
|
||||||
@@ -341,17 +332,5 @@ impl Render for NewAccount {
|
|||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
|
||||||
div().absolute().left_2().top_10().w_16().child(
|
|
||||||
Button::new("back")
|
|
||||||
.label("Back")
|
|
||||||
.icon(Icon::new(IconName::ArrowLeft))
|
|
||||||
.ghost()
|
|
||||||
.small()
|
|
||||||
.on_click(cx.listener(move |this, _, window, cx| {
|
|
||||||
this.back(window, cx);
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use super::{login, new_account};
|
|||||||
|
|
||||||
const LOGO_URL: &str = "brand/coop.svg";
|
const LOGO_URL: &str = "brand/coop.svg";
|
||||||
const TITLE: &str = "Welcome to Coop!";
|
const TITLE: &str = "Welcome to Coop!";
|
||||||
const SUBTITLE: &str = "a Nostr client for secure communication.";
|
const SUBTITLE: &str = "Secure Communication on Nostr.";
|
||||||
|
|
||||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Onboarding> {
|
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Onboarding> {
|
||||||
Onboarding::new(window, cx)
|
Onboarding::new(window, cx)
|
||||||
@@ -97,7 +97,7 @@ impl Render for Onboarding {
|
|||||||
.flex_col()
|
.flex_col()
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.gap_8()
|
.gap_10()
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
@@ -107,7 +107,7 @@ impl Render for Onboarding {
|
|||||||
.child(
|
.child(
|
||||||
svg()
|
svg()
|
||||||
.path(LOGO_URL)
|
.path(LOGO_URL)
|
||||||
.size_12()
|
.size_16()
|
||||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::THREE)),
|
.text_color(cx.theme().base.step(cx, ColorScaleStep::THREE)),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
@@ -115,14 +115,13 @@ impl Render for Onboarding {
|
|||||||
.text_center()
|
.text_center()
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_lg()
|
.text_xl()
|
||||||
.font_semibold()
|
.font_semibold()
|
||||||
.line_height(relative(1.2))
|
.line_height(relative(1.3))
|
||||||
.child(TITLE),
|
.child(TITLE),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_sm()
|
|
||||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
||||||
.child(SUBTITLE),
|
.child(SUBTITLE),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,46 +2,38 @@ use async_utility::task::spawn;
|
|||||||
use common::nip96_upload;
|
use common::nip96_upload;
|
||||||
use global::{constants::IMAGE_SERVICE, get_client};
|
use global::{constants::IMAGE_SERVICE, get_client};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, img, prelude::FluentBuilder, AnyElement, App, AppContext, Context, Entity, EventEmitter,
|
div, img, prelude::FluentBuilder, px, App, AppContext, Context, Entity, Flatten, IntoElement,
|
||||||
Flatten, FocusHandle, Focusable, IntoElement, ParentElement, PathPromptOptions, Render,
|
ParentElement, PathPromptOptions, Render, Styled, Task, Window,
|
||||||
SharedString, Styled, Task, Window,
|
|
||||||
};
|
};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use smol::fs;
|
use smol::fs;
|
||||||
use std::{str::FromStr, sync::Arc, time::Duration};
|
use std::{str::FromStr, time::Duration};
|
||||||
use ui::{
|
use ui::{
|
||||||
button::{Button, ButtonVariants},
|
button::{Button, ButtonVariants},
|
||||||
dock_area::panel::{Panel, PanelEvent},
|
|
||||||
input::TextInput,
|
input::TextInput,
|
||||||
popup_menu::PopupMenu,
|
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||||
ContextModal, Disableable, Sizable, Size,
|
ContextModal, Disableable, IconName, Sizable, Size,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(window: &mut Window, cx: &mut App) -> Arc<Entity<Profile>> {
|
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Profile> {
|
||||||
Arc::new(Profile::new(window, cx))
|
Profile::new(window, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Profile {
|
pub struct Profile {
|
||||||
profile: Option<Metadata>,
|
profile: Option<Metadata>,
|
||||||
// Form
|
|
||||||
name_input: Entity<TextInput>,
|
name_input: Entity<TextInput>,
|
||||||
avatar_input: Entity<TextInput>,
|
avatar_input: Entity<TextInput>,
|
||||||
bio_input: Entity<TextInput>,
|
bio_input: Entity<TextInput>,
|
||||||
website_input: Entity<TextInput>,
|
website_input: Entity<TextInput>,
|
||||||
is_loading: bool,
|
is_loading: bool,
|
||||||
is_submitting: bool,
|
is_submitting: bool,
|
||||||
// Panel
|
|
||||||
name: SharedString,
|
|
||||||
focus_handle: FocusHandle,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Profile {
|
impl Profile {
|
||||||
pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||||
let window_handle = window.window_handle();
|
|
||||||
|
|
||||||
let name_input = cx.new(|cx| {
|
let name_input = cx.new(|cx| {
|
||||||
TextInput::new(window, cx)
|
TextInput::new(window, cx)
|
||||||
.text_size(Size::XSmall)
|
.text_size(Size::Small)
|
||||||
.placeholder("Alice")
|
.placeholder("Alice")
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -54,13 +46,13 @@ impl Profile {
|
|||||||
|
|
||||||
let website_input = cx.new(|cx| {
|
let website_input = cx.new(|cx| {
|
||||||
TextInput::new(window, cx)
|
TextInput::new(window, cx)
|
||||||
.text_size(Size::XSmall)
|
.text_size(Size::Small)
|
||||||
.placeholder("https://your-website.com")
|
.placeholder("https://your-website.com")
|
||||||
});
|
});
|
||||||
|
|
||||||
let bio_input = cx.new(|cx| {
|
let bio_input = cx.new(|cx| {
|
||||||
TextInput::new(window, cx)
|
TextInput::new(window, cx)
|
||||||
.text_size(Size::XSmall)
|
.text_size(Size::Small)
|
||||||
.multi_line()
|
.multi_line()
|
||||||
.placeholder("A short introduce about you.")
|
.placeholder("A short introduce about you.")
|
||||||
});
|
});
|
||||||
@@ -74,8 +66,6 @@ impl Profile {
|
|||||||
profile: None,
|
profile: None,
|
||||||
is_loading: false,
|
is_loading: false,
|
||||||
is_submitting: false,
|
is_submitting: false,
|
||||||
name: "Profile".into(),
|
|
||||||
focus_handle: cx.focus_handle(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let task: Task<Result<Option<Metadata>, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<Option<Metadata>, Error>> = cx.background_spawn(async move {
|
||||||
@@ -89,10 +79,10 @@ impl Profile {
|
|||||||
Ok(metadata)
|
Ok(metadata)
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
if let Ok(Some(metadata)) = task.await {
|
if let Ok(Some(metadata)) = task.await {
|
||||||
_ = cx.update_window(window_handle, |_, window, cx| {
|
cx.update(|window, cx| {
|
||||||
_ = this.update(cx, |this: &mut Profile, cx| {
|
this.update(cx, |this: &mut Profile, cx| {
|
||||||
this.avatar_input.update(cx, |this, cx| {
|
this.avatar_input.update(cx, |this, cx| {
|
||||||
if let Some(avatar) = metadata.picture.as_ref() {
|
if let Some(avatar) = metadata.picture.as_ref() {
|
||||||
this.set_text(avatar, window, cx);
|
this.set_text(avatar, window, cx);
|
||||||
@@ -114,9 +104,12 @@ impl Profile {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.profile = Some(metadata);
|
this.profile = Some(metadata);
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
})
|
||||||
});
|
.ok();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
@@ -127,7 +120,6 @@ impl Profile {
|
|||||||
|
|
||||||
fn upload(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn upload(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let avatar_input = self.avatar_input.downgrade();
|
let avatar_input = self.avatar_input.downgrade();
|
||||||
let window_handle = window.window_handle();
|
|
||||||
let paths = cx.prompt_for_paths(PathPromptOptions {
|
let paths = cx.prompt_for_paths(PathPromptOptions {
|
||||||
files: true,
|
files: true,
|
||||||
directories: false,
|
directories: false,
|
||||||
@@ -137,7 +129,7 @@ impl Profile {
|
|||||||
// Show loading spinner
|
// Show loading spinner
|
||||||
self.set_loading(true, cx);
|
self.set_loading(true, cx);
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
match Flatten::flatten(paths.await.map_err(|e| e.into())) {
|
match Flatten::flatten(paths.await.map_err(|e| e.into())) {
|
||||||
Ok(Some(mut paths)) => {
|
Ok(Some(mut paths)) => {
|
||||||
let path = paths.pop().unwrap();
|
let path = paths.pop().unwrap();
|
||||||
@@ -153,32 +145,33 @@ impl Profile {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if let Ok(url) = rx.await {
|
if let Ok(url) = rx.await {
|
||||||
cx.update_window(window_handle, |_, window, cx| {
|
cx.update(|window, cx| {
|
||||||
// Stop loading spinner
|
// Stop loading spinner
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.set_loading(false, cx);
|
this.set_loading(false, cx);
|
||||||
})
|
})
|
||||||
.unwrap();
|
.ok();
|
||||||
|
|
||||||
// Set avatar input
|
// Set avatar input
|
||||||
avatar_input
|
avatar_input
|
||||||
.update(cx, |this, cx| {
|
.update(cx, |this, cx| {
|
||||||
this.set_text(url.to_string(), window, cx);
|
this.set_text(url.to_string(), window, cx);
|
||||||
})
|
})
|
||||||
.unwrap();
|
.ok();
|
||||||
})
|
})
|
||||||
.unwrap();
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
|
cx.update(|_, cx| {
|
||||||
// Stop loading spinner
|
// Stop loading spinner
|
||||||
if let Some(view) = this.upgrade() {
|
this.update(cx, |this, cx| {
|
||||||
cx.update_entity(&view, |this, cx| {
|
|
||||||
this.set_loading(false, cx);
|
this.set_loading(false, cx);
|
||||||
})
|
})
|
||||||
.unwrap();
|
.ok();
|
||||||
}
|
})
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
Err(_) => {}
|
Err(_) => {}
|
||||||
}
|
}
|
||||||
@@ -186,11 +179,6 @@ impl Profile {
|
|||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_loading(&mut self, status: bool, cx: &mut Context<Self>) {
|
|
||||||
self.is_loading = status;
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn submit(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn submit(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
// Show loading spinner
|
// Show loading spinner
|
||||||
self.set_submitting(true, cx);
|
self.set_submitting(true, cx);
|
||||||
@@ -216,82 +204,57 @@ impl Profile {
|
|||||||
new_metadata = new_metadata.website(url);
|
new_metadata = new_metadata.website(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
let window_handle = window.window_handle();
|
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
|
||||||
let client = get_client();
|
let client = get_client();
|
||||||
let (tx, rx) = oneshot::channel::<EventId>();
|
_ = client.set_metadata(&new_metadata).await?;
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
Ok(())
|
||||||
if let Ok(output) = client.set_metadata(&new_metadata).await {
|
});
|
||||||
_ = tx.send(output.val);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
if rx.await.is_ok() {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
cx.update_window(window_handle, |_, window, cx| {
|
if task.await.is_ok() {
|
||||||
|
cx.update(|window, cx| {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.set_submitting(false, cx);
|
this.set_submitting(false, cx);
|
||||||
window.push_notification("Your profile has been updated successfully", cx);
|
window.push_notification("Your profile has been updated successfully", cx);
|
||||||
})
|
})
|
||||||
.unwrap()
|
.ok();
|
||||||
})
|
})
|
||||||
.unwrap();
|
.ok();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_loading(&mut self, status: bool, cx: &mut Context<Self>) {
|
||||||
|
self.is_loading = status;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
fn set_submitting(&mut self, status: bool, cx: &mut Context<Self>) {
|
fn set_submitting(&mut self, status: bool, cx: &mut Context<Self>) {
|
||||||
self.is_submitting = status;
|
self.is_submitting = status;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Panel for Profile {
|
|
||||||
fn panel_id(&self) -> SharedString {
|
|
||||||
"ProfilePanel".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn title(&self, _cx: &App) -> AnyElement {
|
|
||||||
self.name.clone().into_any_element()
|
|
||||||
}
|
|
||||||
|
|
||||||
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 Profile {}
|
|
||||||
|
|
||||||
impl Focusable for Profile {
|
|
||||||
fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
|
|
||||||
self.focus_handle.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for Profile {
|
impl Render for Profile {
|
||||||
fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
div()
|
div()
|
||||||
.size_full()
|
.size_full()
|
||||||
.px_2()
|
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.gap_3()
|
.gap_3()
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
|
.w_full()
|
||||||
|
.h_32()
|
||||||
|
.bg(cx.theme().base.step(cx, ColorScaleStep::TWO))
|
||||||
|
.rounded(px(cx.theme().radius))
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_end()
|
.justify_center()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.w_full()
|
|
||||||
.h_24()
|
|
||||||
.map(|this| {
|
.map(|this| {
|
||||||
let picture = self.avatar_input.read(cx).text();
|
let picture = self.avatar_input.read(cx).text();
|
||||||
|
|
||||||
@@ -309,32 +272,25 @@ impl Render for Profile {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.gap_1()
|
|
||||||
.items_center()
|
|
||||||
.w_full()
|
|
||||||
.child(self.avatar_input.clone())
|
|
||||||
.child(
|
.child(
|
||||||
Button::new("upload")
|
Button::new("upload")
|
||||||
.label("Upload")
|
.icon(IconName::Upload)
|
||||||
|
.label("Change")
|
||||||
.ghost()
|
.ghost()
|
||||||
.small()
|
.small()
|
||||||
.disabled(self.is_submitting)
|
.disabled(self.is_loading || self.is_submitting)
|
||||||
.loading(self.is_loading)
|
.loading(self.is_loading)
|
||||||
.on_click(cx.listener(move |this, _, window, cx| {
|
.on_click(cx.listener(move |this, _, window, cx| {
|
||||||
this.upload(window, cx);
|
this.upload(window, cx);
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.text_xs()
|
.text_sm()
|
||||||
.child("Name:")
|
.child("Name:")
|
||||||
.child(self.name_input.clone()),
|
.child(self.name_input.clone()),
|
||||||
)
|
)
|
||||||
@@ -343,26 +299,25 @@ impl Render for Profile {
|
|||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.text_xs()
|
.text_sm()
|
||||||
.child("Bio:")
|
.child("Website:")
|
||||||
.child(self.bio_input.clone()),
|
.child(self.website_input.clone()),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.text_xs()
|
.text_sm()
|
||||||
.child("Website:")
|
.child("Bio:")
|
||||||
.child(self.website_input.clone()),
|
.child(self.bio_input.clone()),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div().flex().items_center().justify_end().child(
|
div().mt_2().w_full().child(
|
||||||
Button::new("submit")
|
Button::new("submit")
|
||||||
.label("Update")
|
.label("Update")
|
||||||
.primary()
|
.primary()
|
||||||
.small()
|
.disabled(self.is_loading || self.is_submitting)
|
||||||
.disabled(self.is_loading)
|
|
||||||
.loading(self.is_submitting)
|
.loading(self.is_submitting)
|
||||||
.on_click(cx.listener(move |this, _, window, cx| {
|
.on_click(cx.listener(move |this, _, window, cx| {
|
||||||
this.submit(window, cx);
|
this.submit(window, cx);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use global::{constants::NEW_MESSAGE_SUB_ID, get_client};
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
div, prelude::FluentBuilder, px, uniform_list, App, AppContext, Context, Entity, FocusHandle,
|
div, prelude::FluentBuilder, px, uniform_list, App, AppContext, Context, Entity, FocusHandle,
|
||||||
InteractiveElement, IntoElement, ParentElement, Render, Styled, Subscription, Task, TextAlign,
|
InteractiveElement, IntoElement, ParentElement, Render, Styled, Subscription, Task, TextAlign,
|
||||||
Window,
|
UniformList, Window,
|
||||||
};
|
};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
@@ -11,10 +11,11 @@ use ui::{
|
|||||||
button::{Button, ButtonVariants},
|
button::{Button, ButtonVariants},
|
||||||
input::{InputEvent, TextInput},
|
input::{InputEvent, TextInput},
|
||||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||||
ContextModal, IconName, Sizable,
|
ContextModal, Disableable, IconName, Sizable,
|
||||||
};
|
};
|
||||||
|
|
||||||
const MESSAGE: &str = "In order to receive messages from others, you need to setup Messaging Relays. You can use the recommend relays or add more.";
|
const MIN_HEIGHT: f32 = 200.0;
|
||||||
|
const MESSAGE: &str = "In order to receive messages from others, you need to setup at least one Messaging Relay. You can use the recommend relays or add more.";
|
||||||
const HELP_TEXT: &str = "Please add some relays.";
|
const HELP_TEXT: &str = "Please add some relays.";
|
||||||
|
|
||||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Relays> {
|
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Relays> {
|
||||||
@@ -32,7 +33,12 @@ pub struct Relays {
|
|||||||
|
|
||||||
impl Relays {
|
impl Relays {
|
||||||
pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||||
let client = get_client();
|
let input = cx.new(|cx| {
|
||||||
|
TextInput::new(window, cx)
|
||||||
|
.text_size(ui::Size::XSmall)
|
||||||
|
.small()
|
||||||
|
.placeholder("wss://example.com")
|
||||||
|
});
|
||||||
|
|
||||||
let relays = cx.new(|cx| {
|
let relays = cx.new(|cx| {
|
||||||
let relays = vec![
|
let relays = vec![
|
||||||
@@ -41,6 +47,7 @@ impl Relays {
|
|||||||
];
|
];
|
||||||
|
|
||||||
let task: Task<Result<Vec<RelayUrl>, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<Vec<RelayUrl>, Error>> = cx.background_spawn(async move {
|
||||||
|
let client = get_client();
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let public_key = signer.get_public_key().await?;
|
let public_key = signer.get_public_key().await?;
|
||||||
|
|
||||||
@@ -82,13 +89,6 @@ impl Relays {
|
|||||||
relays
|
relays
|
||||||
});
|
});
|
||||||
|
|
||||||
let input = cx.new(|cx| {
|
|
||||||
TextInput::new(window, cx)
|
|
||||||
.text_size(ui::Size::XSmall)
|
|
||||||
.small()
|
|
||||||
.placeholder("wss://example.com")
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.new(|cx| {
|
cx.new(|cx| {
|
||||||
let mut subscriptions = smallvec![];
|
let mut subscriptions = smallvec![];
|
||||||
|
|
||||||
@@ -113,11 +113,9 @@ impl Relays {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn update(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
// Show loading spinner
|
|
||||||
self.set_loading(true, cx);
|
self.set_loading(true, cx);
|
||||||
|
|
||||||
let relays = self.relays.read(cx).clone();
|
let relays = self.relays.read(cx).clone();
|
||||||
|
|
||||||
let task: Task<Result<EventId, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<EventId, Error>> = cx.background_spawn(async move {
|
||||||
let client = get_client();
|
let client = get_client();
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
@@ -189,10 +187,6 @@ impl Relays {
|
|||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn loading(&self) -> bool {
|
|
||||||
self.is_loading
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_loading(&mut self, status: bool, cx: &mut Context<Self>) {
|
fn set_loading(&mut self, status: bool, cx: &mut Context<Self>) {
|
||||||
self.is_loading = status;
|
self.is_loading = status;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -225,59 +219,17 @@ impl Relays {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for Relays {
|
fn render_list(
|
||||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
&mut self,
|
||||||
div()
|
relays: Vec<RelayUrl>,
|
||||||
.track_focus(&self.focus_handle)
|
_window: &mut Window,
|
||||||
.flex()
|
cx: &mut Context<Self>,
|
||||||
.flex_col()
|
) -> UniformList {
|
||||||
.gap_2()
|
|
||||||
.w_full()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.px_2()
|
|
||||||
.text_xs()
|
|
||||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
|
||||||
.child(MESSAGE),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.px_2()
|
|
||||||
.w_full()
|
|
||||||
.flex()
|
|
||||||
.flex_col()
|
|
||||||
.gap_2()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.w_full()
|
|
||||||
.gap_2()
|
|
||||||
.child(self.input.clone())
|
|
||||||
.child(
|
|
||||||
Button::new("add_relay_btn")
|
|
||||||
.icon(IconName::Plus)
|
|
||||||
.small()
|
|
||||||
.rounded(px(cx.theme().radius))
|
|
||||||
.on_click(
|
|
||||||
cx.listener(|this, _, window, cx| this.add(window, cx)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.map(|this| {
|
|
||||||
let view = cx.entity();
|
let view = cx.entity();
|
||||||
let relays = self.relays.read(cx).clone();
|
|
||||||
let total = relays.len();
|
let total = relays.len();
|
||||||
|
|
||||||
if !relays.is_empty() {
|
uniform_list(view, "relays", total, move |_, range, _window, cx| {
|
||||||
this.child(
|
|
||||||
uniform_list(
|
|
||||||
view,
|
|
||||||
"relays",
|
|
||||||
total,
|
|
||||||
move |_, range, _window, cx| {
|
|
||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
|
|
||||||
for ix in range {
|
for ix in range {
|
||||||
@@ -293,10 +245,7 @@ impl Render for Relays {
|
|||||||
.items_center()
|
.items_center()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.rounded(px(cx.theme().radius))
|
.rounded(px(cx.theme().radius))
|
||||||
.bg(cx
|
.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
|
||||||
.theme()
|
|
||||||
.base
|
|
||||||
.step(cx, ColorScaleStep::THREE))
|
|
||||||
.text_xs()
|
.text_xs()
|
||||||
.child(item)
|
.child(item)
|
||||||
.child(
|
.child(
|
||||||
@@ -305,36 +254,101 @@ impl Render for Relays {
|
|||||||
.xsmall()
|
.xsmall()
|
||||||
.ghost()
|
.ghost()
|
||||||
.invisible()
|
.invisible()
|
||||||
.group_hover("", |this| {
|
.group_hover("", |this| this.visible())
|
||||||
this.visible()
|
.on_click(cx.listener(move |this, _, window, cx| {
|
||||||
})
|
|
||||||
.on_click(cx.listener(
|
|
||||||
move |this, _, window, cx| {
|
|
||||||
this.remove(ix, window, cx)
|
this.remove(ix, window, cx)
|
||||||
},
|
})),
|
||||||
)),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
items
|
items
|
||||||
},
|
})
|
||||||
)
|
|
||||||
.w_full()
|
.w_full()
|
||||||
.min_h(px(120.)),
|
.min_h(px(MIN_HEIGHT))
|
||||||
)
|
}
|
||||||
} else {
|
|
||||||
this.h_20()
|
fn render_empty(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
div()
|
||||||
|
.h_20()
|
||||||
.mb_2()
|
.mb_2()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.text_xs()
|
.text_sm()
|
||||||
.text_align(TextAlign::Center)
|
.text_align(TextAlign::Center)
|
||||||
.child(HELP_TEXT)
|
.child(HELP_TEXT)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for Relays {
|
||||||
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
div()
|
||||||
|
.track_focus(&self.focus_handle)
|
||||||
|
.w_full()
|
||||||
|
.h_full()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.justify_between()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex_1()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_sm()
|
||||||
|
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
||||||
|
.child(MESSAGE),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.w_full()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_3()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.w_full()
|
||||||
|
.gap_2()
|
||||||
|
.child(self.input.clone())
|
||||||
|
.child(
|
||||||
|
Button::new("add_relay_btn")
|
||||||
|
.icon(IconName::Plus)
|
||||||
|
.label("Add")
|
||||||
|
.small()
|
||||||
|
.ghost()
|
||||||
|
.rounded(px(cx.theme().radius))
|
||||||
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
|
this.add(window, cx)
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.map(|this| {
|
||||||
|
let relays = self.relays.read(cx).clone();
|
||||||
|
|
||||||
|
if !relays.is_empty() {
|
||||||
|
this.child(self.render_list(relays, window, cx))
|
||||||
|
} else {
|
||||||
|
this.child(self.render_empty(window, cx))
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("submti")
|
||||||
|
.label("Update")
|
||||||
|
.primary()
|
||||||
|
.w_full()
|
||||||
|
.loading(self.is_loading)
|
||||||
|
.disabled(self.is_loading)
|
||||||
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
|
this.update(window, cx);
|
||||||
|
})),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
use gpui::{
|
|
||||||
div, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
|
||||||
IntoElement, ParentElement, Render, SharedString, Styled, Window,
|
|
||||||
};
|
|
||||||
use ui::{
|
|
||||||
button::Button,
|
|
||||||
dock_area::panel::{Panel, PanelEvent},
|
|
||||||
popup_menu::PopupMenu,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Settings> {
|
|
||||||
Settings::new(window, cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Settings {
|
|
||||||
name: SharedString,
|
|
||||||
closable: bool,
|
|
||||||
zoomable: bool,
|
|
||||||
focus_handle: FocusHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Settings {
|
|
||||||
pub fn new(_window: &mut Window, cx: &mut App) -> Entity<Self> {
|
|
||||||
cx.new(|cx| Self {
|
|
||||||
name: "Settings".into(),
|
|
||||||
closable: true,
|
|
||||||
zoomable: true,
|
|
||||||
focus_handle: cx.focus_handle(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Panel for Settings {
|
|
||||||
fn panel_id(&self) -> SharedString {
|
|
||||||
"SettingsPanel".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 Settings {}
|
|
||||||
|
|
||||||
impl Focusable for Settings {
|
|
||||||
fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
|
|
||||||
self.focus_handle.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for Settings {
|
|
||||||
fn render(&mut self, _window: &mut gpui::Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
|
||||||
div()
|
|
||||||
.size_full()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_center()
|
|
||||||
.child("Settings")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
58
crates/coop/src/views/sidebar/button.rs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use gpui::{
|
||||||
|
div, prelude::FluentBuilder, px, App, ClickEvent, Div, InteractiveElement, IntoElement,
|
||||||
|
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window,
|
||||||
|
};
|
||||||
|
use ui::{
|
||||||
|
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||||
|
Icon,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Handler = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>;
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct SidebarButton {
|
||||||
|
base: Div,
|
||||||
|
label: SharedString,
|
||||||
|
icon: Option<Icon>,
|
||||||
|
handler: Handler,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SidebarButton {
|
||||||
|
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||||
|
Self {
|
||||||
|
base: div().flex().items_center().gap_3().px_3().h_8(),
|
||||||
|
label: label.into(),
|
||||||
|
icon: None,
|
||||||
|
handler: Rc::new(|_, _, _| {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
|
||||||
|
self.icon = Some(icon.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_click(
|
||||||
|
mut self,
|
||||||
|
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||||
|
) -> Self {
|
||||||
|
self.handler = Rc::new(handler);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for SidebarButton {
|
||||||
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
let handler = self.handler.clone();
|
||||||
|
|
||||||
|
self.base
|
||||||
|
.id(self.label.clone())
|
||||||
|
.rounded(px(cx.theme().radius))
|
||||||
|
.when_some(self.icon, |this, icon| this.child(icon))
|
||||||
|
.child(self.label.clone())
|
||||||
|
.hover(|this| this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE)))
|
||||||
|
.on_click(move |ev, window, cx| handler(ev, window, cx))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,21 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, prelude::FluentBuilder, px, App, ClickEvent, Img, InteractiveElement, IntoElement,
|
div, percentage, prelude::FluentBuilder, px, App, ClickEvent, Div, Img, InteractiveElement,
|
||||||
ParentElement as _, RenderOnce, SharedString, StatefulInteractiveElement, Styled as _, Window,
|
IntoElement, ParentElement as _, RenderOnce, SharedString, StatefulInteractiveElement, Styled,
|
||||||
|
Window,
|
||||||
};
|
};
|
||||||
use ui::{
|
use ui::{
|
||||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||||
Collapsible, Icon, IconName, StyledExt,
|
Collapsible, Icon, IconName, Sizable, StyledExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
type Handler = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>;
|
type Handler = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>;
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct Parent {
|
pub struct Parent {
|
||||||
|
base: Div,
|
||||||
icon: Option<Icon>,
|
icon: Option<Icon>,
|
||||||
active_icon: Option<Icon>,
|
|
||||||
label: SharedString,
|
label: SharedString,
|
||||||
items: Vec<Folder>,
|
items: Vec<Folder>,
|
||||||
collapsed: bool,
|
collapsed: bool,
|
||||||
@@ -24,9 +25,9 @@ pub struct Parent {
|
|||||||
impl Parent {
|
impl Parent {
|
||||||
pub fn new(label: impl Into<SharedString>) -> Self {
|
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
base: div().flex().flex_col().gap_2(),
|
||||||
label: label.into(),
|
label: label.into(),
|
||||||
icon: None,
|
icon: None,
|
||||||
active_icon: None,
|
|
||||||
items: Vec::new(),
|
items: Vec::new(),
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
handler: Rc::new(|_, _, _| {}),
|
handler: Rc::new(|_, _, _| {}),
|
||||||
@@ -38,11 +39,6 @@ impl Parent {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn active_icon(mut self, icon: impl Into<Icon>) -> Self {
|
|
||||||
self.active_icon = Some(icon.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn collapsed(mut self, collapsed: bool) -> Self {
|
pub fn collapsed(mut self, collapsed: bool) -> Self {
|
||||||
self.collapsed = collapsed;
|
self.collapsed = collapsed;
|
||||||
self
|
self
|
||||||
@@ -83,10 +79,7 @@ impl RenderOnce for Parent {
|
|||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let handler = self.handler.clone();
|
let handler = self.handler.clone();
|
||||||
|
|
||||||
div()
|
self.base
|
||||||
.flex()
|
|
||||||
.flex_col()
|
|
||||||
.gap_1()
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.id(self.label.clone())
|
.id(self.label.clone())
|
||||||
@@ -94,36 +87,37 @@ impl RenderOnce for Parent {
|
|||||||
.items_center()
|
.items_center()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.px_2()
|
.px_2()
|
||||||
.h_6()
|
.h_8()
|
||||||
.rounded(px(cx.theme().radius))
|
.rounded(px(cx.theme().radius))
|
||||||
.text_xs()
|
.text_sm()
|
||||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
||||||
.font_semibold()
|
.font_medium()
|
||||||
.when_some(self.icon, |this, icon| {
|
.child(
|
||||||
this.map(|this| {
|
Icon::new(IconName::CaretDown)
|
||||||
if self.collapsed {
|
.xsmall()
|
||||||
this.child(icon.size_4())
|
.when(self.collapsed, |this| this.rotate(percentage(270. / 360.))),
|
||||||
} else {
|
)
|
||||||
this.when_some(self.active_icon, |this, icon| {
|
.child(
|
||||||
this.child(icon.size_4())
|
div()
|
||||||
})
|
.flex()
|
||||||
}
|
.items_center()
|
||||||
})
|
.gap_2()
|
||||||
})
|
.when_some(self.icon, |this, icon| this.child(icon.small()))
|
||||||
.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)),
|
||||||
)
|
)
|
||||||
.when(!self.collapsed, |this| {
|
.when(!self.collapsed, |this| {
|
||||||
this.child(div().flex().flex_col().gap_1().pl_3().children(self.items))
|
this.child(div().flex().flex_col().gap_2().pl_3().children(self.items))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct Folder {
|
pub struct Folder {
|
||||||
|
base: Div,
|
||||||
icon: Option<Icon>,
|
icon: Option<Icon>,
|
||||||
active_icon: Option<Icon>,
|
|
||||||
label: SharedString,
|
label: SharedString,
|
||||||
items: Vec<FolderItem>,
|
items: Vec<FolderItem>,
|
||||||
collapsed: bool,
|
collapsed: bool,
|
||||||
@@ -133,9 +127,9 @@ pub struct Folder {
|
|||||||
impl Folder {
|
impl Folder {
|
||||||
pub fn new(label: impl Into<SharedString>) -> Self {
|
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
base: div().flex().flex_col().gap_2(),
|
||||||
label: label.into(),
|
label: label.into(),
|
||||||
icon: None,
|
icon: None,
|
||||||
active_icon: None,
|
|
||||||
items: Vec::new(),
|
items: Vec::new(),
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
handler: Rc::new(|_, _, _| {}),
|
handler: Rc::new(|_, _, _| {}),
|
||||||
@@ -147,11 +141,6 @@ impl Folder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn active_icon(mut self, icon: impl Into<Icon>) -> Self {
|
|
||||||
self.active_icon = Some(icon.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn collapsed(mut self, collapsed: bool) -> Self {
|
pub fn collapsed(mut self, collapsed: bool) -> Self {
|
||||||
self.collapsed = collapsed;
|
self.collapsed = collapsed;
|
||||||
self
|
self
|
||||||
@@ -186,10 +175,7 @@ impl RenderOnce for Folder {
|
|||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let handler = self.handler.clone();
|
let handler = self.handler.clone();
|
||||||
|
|
||||||
div()
|
self.base
|
||||||
.flex()
|
|
||||||
.flex_col()
|
|
||||||
.gap_1()
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.id(self.label.clone())
|
.id(self.label.clone())
|
||||||
@@ -197,23 +183,24 @@ impl RenderOnce for Folder {
|
|||||||
.items_center()
|
.items_center()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.px_2()
|
.px_2()
|
||||||
.h_6()
|
.h_8()
|
||||||
.rounded(px(cx.theme().radius))
|
.rounded(px(cx.theme().radius))
|
||||||
.text_xs()
|
.text_sm()
|
||||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
||||||
.font_semibold()
|
.font_medium()
|
||||||
.when_some(self.icon, |this, icon| {
|
.child(
|
||||||
this.map(|this| {
|
Icon::new(IconName::CaretDown)
|
||||||
if self.collapsed {
|
.xsmall()
|
||||||
this.child(icon.size_4())
|
.when(self.collapsed, |this| this.rotate(percentage(270. / 360.))),
|
||||||
} else {
|
)
|
||||||
this.when_some(self.active_icon, |this, icon| {
|
.child(
|
||||||
this.child(icon.size_4())
|
div()
|
||||||
})
|
.flex()
|
||||||
}
|
.items_center()
|
||||||
})
|
.gap_2()
|
||||||
})
|
.when_some(self.icon, |this, icon| this.child(icon.small()))
|
||||||
.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)),
|
||||||
)
|
)
|
||||||
@@ -226,6 +213,7 @@ impl RenderOnce for Folder {
|
|||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct FolderItem {
|
pub struct FolderItem {
|
||||||
ix: usize,
|
ix: usize,
|
||||||
|
base: Div,
|
||||||
img: Option<Img>,
|
img: Option<Img>,
|
||||||
label: Option<SharedString>,
|
label: Option<SharedString>,
|
||||||
description: Option<SharedString>,
|
description: Option<SharedString>,
|
||||||
@@ -236,6 +224,7 @@ impl FolderItem {
|
|||||||
pub fn new(ix: usize) -> Self {
|
pub fn new(ix: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
ix,
|
ix,
|
||||||
|
base: div().h_8().w_full().px_2(),
|
||||||
img: None,
|
img: None,
|
||||||
label: None,
|
label: None,
|
||||||
description: None,
|
description: None,
|
||||||
@@ -271,15 +260,12 @@ impl RenderOnce for FolderItem {
|
|||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let handler = self.handler.clone();
|
let handler = self.handler.clone();
|
||||||
|
|
||||||
div()
|
self.base
|
||||||
.id(self.ix)
|
.id(self.ix)
|
||||||
.h_6()
|
|
||||||
.px_2()
|
|
||||||
.w_full()
|
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.text_xs()
|
.text_sm()
|
||||||
.rounded(px(cx.theme().radius))
|
.rounded(px(cx.theme().radius))
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
@@ -291,19 +277,21 @@ impl RenderOnce for FolderItem {
|
|||||||
.font_medium()
|
.font_medium()
|
||||||
.map(|this| {
|
.map(|this| {
|
||||||
if let Some(img) = self.img {
|
if let Some(img) = self.img {
|
||||||
this.child(img.size_4().flex_shrink_0())
|
this.child(img.size_5().flex_shrink_0())
|
||||||
} else {
|
} else {
|
||||||
this.child(
|
this.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.items_center()
|
.items_center()
|
||||||
.size_4()
|
.size_5()
|
||||||
.rounded_full()
|
.rounded_full()
|
||||||
.bg(cx.theme().accent.step(cx, ColorScaleStep::THREE))
|
.bg(cx.theme().accent.step(cx, ColorScaleStep::THREE))
|
||||||
.child(Icon::new(IconName::GroupFill).size_2().text_color(
|
.child(
|
||||||
|
Icon::new(IconName::UsersThreeFill).xsmall().text_color(
|
||||||
cx.theme().accent.step(cx, ColorScaleStep::TWELVE),
|
cx.theme().accent.step(cx, ColorScaleStep::TWELVE),
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -313,11 +301,12 @@ impl RenderOnce for FolderItem {
|
|||||||
this.child(
|
this.child(
|
||||||
div()
|
div()
|
||||||
.flex_shrink_0()
|
.flex_shrink_0()
|
||||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
.text_xs()
|
||||||
|
.text_color(cx.theme().base.step(cx, ColorScaleStep::TEN))
|
||||||
.child(description),
|
.child(description),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.hover(|this| this.bg(cx.theme().base.step(cx, ColorScaleStep::FOUR)))
|
.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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,37 @@
|
|||||||
|
use account::Account;
|
||||||
|
use button::SidebarButton;
|
||||||
use chats::{
|
use chats::{
|
||||||
room::{Room, RoomKind},
|
room::{Room, RoomKind},
|
||||||
ChatRegistry,
|
ChatRegistry,
|
||||||
};
|
};
|
||||||
use compose::{Compose, ComposeButton};
|
use common::profile::SharedProfile;
|
||||||
use folder::{Folder, FolderItem, Parent};
|
use folder::{Folder, FolderItem, Parent};
|
||||||
|
use global::get_client;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, img, prelude::FluentBuilder, px, AnyElement, App, AppContext, Context, Entity,
|
actions, div, img, prelude::FluentBuilder, AnyElement, App, AppContext, Context, Entity,
|
||||||
EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement, Render, SharedString, Styled,
|
EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render,
|
||||||
Window,
|
SharedString, Styled, Task, Window,
|
||||||
};
|
};
|
||||||
use ui::{
|
use ui::{
|
||||||
button::{Button, ButtonRounded, ButtonVariants},
|
button::{Button, ButtonVariants},
|
||||||
dock_area::panel::{Panel, PanelEvent},
|
dock_area::{
|
||||||
popup_menu::PopupMenu,
|
dock::DockPlacement,
|
||||||
|
panel::{Panel, PanelEvent},
|
||||||
|
},
|
||||||
|
popup_menu::{PopupMenu, PopupMenuExt},
|
||||||
scroll::ScrollbarAxis,
|
scroll::ScrollbarAxis,
|
||||||
skeleton::Skeleton,
|
skeleton::Skeleton,
|
||||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||||
ContextModal, Disableable, IconName, StyledExt,
|
IconName, Sizable, StyledExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::chat_space::{AddPanel, PanelKind};
|
use crate::chat_space::{AddPanel, ModalKind, PanelKind, ToggleModal};
|
||||||
|
|
||||||
mod compose;
|
mod button;
|
||||||
mod folder;
|
mod folder;
|
||||||
|
|
||||||
|
actions!(profile, [Logout]);
|
||||||
|
|
||||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Sidebar> {
|
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Sidebar> {
|
||||||
Sidebar::new(window, cx)
|
Sidebar::new(window, cx)
|
||||||
}
|
}
|
||||||
@@ -55,49 +63,6 @@ impl Sidebar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_compose(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
let compose = cx.new(|cx| Compose::new(window, cx));
|
|
||||||
|
|
||||||
window.open_modal(cx, move |modal, window, cx| {
|
|
||||||
let label = compose.read(cx).label(window, cx);
|
|
||||||
let is_submitting = compose.read(cx).is_submitting();
|
|
||||||
|
|
||||||
modal
|
|
||||||
.title("Direct Messages")
|
|
||||||
.width(px(420.))
|
|
||||||
.child(compose.clone())
|
|
||||||
.footer(
|
|
||||||
div()
|
|
||||||
.p_2()
|
|
||||||
.border_t_1()
|
|
||||||
.border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE))
|
|
||||||
.child(
|
|
||||||
Button::new("create_dm_btn")
|
|
||||||
.label(label)
|
|
||||||
.primary()
|
|
||||||
.bold()
|
|
||||||
.rounded(ButtonRounded::Large)
|
|
||||||
.w_full()
|
|
||||||
.loading(is_submitting)
|
|
||||||
.disabled(is_submitting)
|
|
||||||
.on_click(window.listener_for(&compose, |this, _, window, cx| {
|
|
||||||
this.compose(window, cx)
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_room(&self, id: u64, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
window.dispatch_action(
|
|
||||||
Box::new(AddPanel::new(
|
|
||||||
PanelKind::Room(id),
|
|
||||||
ui::dock_area::dock::DockPlacement::Center,
|
|
||||||
)),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ongoing(&mut self, cx: &mut Context<Self>) {
|
fn ongoing(&mut self, cx: &mut Context<Self>) {
|
||||||
self.ongoing = !self.ongoing;
|
self.ongoing = !self.ongoing;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -118,7 +83,6 @@ impl Sidebar {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn render_skeleton(&self, total: i32) -> impl IntoIterator<Item = impl IntoElement> {
|
fn render_skeleton(&self, total: i32) -> impl IntoIterator<Item = impl IntoElement> {
|
||||||
(0..total).map(|_| {
|
(0..total).map(|_| {
|
||||||
div()
|
div()
|
||||||
@@ -148,8 +112,11 @@ impl Sidebar {
|
|||||||
.description(ago)
|
.description(ago)
|
||||||
.img(img)
|
.img(img)
|
||||||
.on_click({
|
.on_click({
|
||||||
cx.listener(move |this, _, window, cx| {
|
cx.listener(move |_, _, window, cx| {
|
||||||
this.open_room(id, window, cx);
|
window.dispatch_action(
|
||||||
|
Box::new(AddPanel::new(PanelKind::Room(id), DockPlacement::Center)),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -158,6 +125,28 @@ impl Sidebar {
|
|||||||
|
|
||||||
items
|
items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_logout(&mut self, _: &Logout, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let task: Task<Result<(), anyhow::Error>> = cx.background_spawn(async move {
|
||||||
|
let client = get_client();
|
||||||
|
_ = client.reset().await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.spawn_in(window, async move |_, cx| {
|
||||||
|
if task.await.is_ok() {
|
||||||
|
cx.update(|_, cx| {
|
||||||
|
Account::global(cx).update(cx, |this, cx| {
|
||||||
|
this.profile = None;
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Panel for Sidebar {
|
impl Panel for Sidebar {
|
||||||
@@ -188,7 +177,9 @@ impl Focusable for Sidebar {
|
|||||||
|
|
||||||
impl Render for Sidebar {
|
impl Render for Sidebar {
|
||||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let account = Account::global(cx).read(cx).profile.as_ref();
|
||||||
let registry = ChatRegistry::global(cx).read(cx);
|
let registry = ChatRegistry::global(cx).read(cx);
|
||||||
|
|
||||||
let rooms = registry.rooms(cx);
|
let rooms = registry.rooms(cx);
|
||||||
let loading = registry.loading();
|
let loading = registry.loading();
|
||||||
|
|
||||||
@@ -198,17 +189,102 @@ impl Render for Sidebar {
|
|||||||
|
|
||||||
div()
|
div()
|
||||||
.scrollable(cx.entity_id(), ScrollbarAxis::Vertical)
|
.scrollable(cx.entity_id(), ScrollbarAxis::Vertical)
|
||||||
|
.on_action(cx.listener(Self::on_logout))
|
||||||
.size_full()
|
.size_full()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.gap_3()
|
.gap_3()
|
||||||
|
.pt_1()
|
||||||
.px_2()
|
.px_2()
|
||||||
.py_3()
|
.pb_2()
|
||||||
.child(ComposeButton::new("New Message").on_click(cx.listener(
|
.when_some(account, |this, profile| {
|
||||||
|this, _, window, cx| {
|
this.child(
|
||||||
this.render_compose(window, cx);
|
div()
|
||||||
},
|
.h_7()
|
||||||
)))
|
.px_1p5()
|
||||||
|
.flex()
|
||||||
|
.justify_between()
|
||||||
|
.items_center()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_2()
|
||||||
|
.text_sm()
|
||||||
|
.font_semibold()
|
||||||
|
.child(img(profile.shared_avatar()).size_7())
|
||||||
|
.child(profile.shared_name()),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("user_dropdown")
|
||||||
|
.icon(IconName::Ellipsis)
|
||||||
|
.small()
|
||||||
|
.ghost()
|
||||||
|
.popup_menu(|this, _window, _cx| {
|
||||||
|
this.menu(
|
||||||
|
"Profile",
|
||||||
|
Box::new(ToggleModal {
|
||||||
|
modal: ModalKind::Profile,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.menu(
|
||||||
|
"Relays",
|
||||||
|
Box::new(ToggleModal {
|
||||||
|
modal: ModalKind::Relay,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.separator()
|
||||||
|
.menu("Logout", Box::new(Logout))
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_1()
|
||||||
|
.text_sm()
|
||||||
|
.font_medium()
|
||||||
|
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
||||||
|
.child(
|
||||||
|
SidebarButton::new("New Message")
|
||||||
|
.icon(IconName::PlusCircleFill)
|
||||||
|
.on_click(cx.listener(|_, _, window, cx| {
|
||||||
|
window.dispatch_action(
|
||||||
|
Box::new(ToggleModal {
|
||||||
|
modal: ModalKind::Compose,
|
||||||
|
}),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
SidebarButton::new("Contacts")
|
||||||
|
.icon(IconName::AddressBook)
|
||||||
|
.on_click(cx.listener(|_, _, window, cx| {
|
||||||
|
window.dispatch_action(
|
||||||
|
Box::new(ToggleModal {
|
||||||
|
modal: ModalKind::Contact,
|
||||||
|
}),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.px_2()
|
||||||
|
.text_xs()
|
||||||
|
.font_semibold()
|
||||||
|
.text_color(cx.theme().base.step(cx, ColorScaleStep::NINE))
|
||||||
|
.child("Messages"),
|
||||||
|
)
|
||||||
.map(|this| {
|
.map(|this| {
|
||||||
if loading {
|
if loading {
|
||||||
this.children(self.render_skeleton(6))
|
this.children(self.render_skeleton(6))
|
||||||
@@ -216,8 +292,7 @@ impl Render for Sidebar {
|
|||||||
this.when_some(ongoing, |this, rooms| {
|
this.when_some(ongoing, |this, rooms| {
|
||||||
this.child(
|
this.child(
|
||||||
Folder::new("Ongoing")
|
Folder::new("Ongoing")
|
||||||
.icon(IconName::FolderFill)
|
.icon(IconName::Folder)
|
||||||
.active_icon(IconName::FolderOpenFill)
|
|
||||||
.collapsed(self.ongoing)
|
.collapsed(self.ongoing)
|
||||||
.on_click(cx.listener(move |this, _, _, cx| {
|
.on_click(cx.listener(move |this, _, _, cx| {
|
||||||
this.ongoing(cx);
|
this.ongoing(cx);
|
||||||
@@ -227,8 +302,7 @@ impl Render for Sidebar {
|
|||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
Parent::new("Incoming")
|
Parent::new("Incoming")
|
||||||
.icon(IconName::FolderFill)
|
.icon(IconName::Folder)
|
||||||
.active_icon(IconName::FolderOpenFill)
|
|
||||||
.collapsed(self.incoming)
|
.collapsed(self.incoming)
|
||||||
.on_click(cx.listener(move |this, _, _, cx| {
|
.on_click(cx.listener(move |this, _, _, cx| {
|
||||||
this.incoming(cx);
|
this.incoming(cx);
|
||||||
@@ -236,8 +310,7 @@ impl Render for Sidebar {
|
|||||||
.when_some(trusted, |this, rooms| {
|
.when_some(trusted, |this, rooms| {
|
||||||
this.child(
|
this.child(
|
||||||
Folder::new("Trusted")
|
Folder::new("Trusted")
|
||||||
.icon(IconName::FolderFill)
|
.icon(IconName::Folder)
|
||||||
.active_icon(IconName::FolderOpenFill)
|
|
||||||
.collapsed(self.trusted)
|
.collapsed(self.trusted)
|
||||||
.on_click(cx.listener(move |this, _, _, cx| {
|
.on_click(cx.listener(move |this, _, _, cx| {
|
||||||
this.trusted(cx);
|
this.trusted(cx);
|
||||||
@@ -248,8 +321,7 @@ impl Render for Sidebar {
|
|||||||
.when_some(unknown, |this, rooms| {
|
.when_some(unknown, |this, rooms| {
|
||||||
this.child(
|
this.child(
|
||||||
Folder::new("Unknown")
|
Folder::new("Unknown")
|
||||||
.icon(IconName::FolderFill)
|
.icon(IconName::Folder)
|
||||||
.active_icon(IconName::FolderOpenFill)
|
|
||||||
.collapsed(self.unknown)
|
.collapsed(self.unknown)
|
||||||
.on_click(cx.listener(move |this, _, _, cx| {
|
.on_click(cx.listener(move |this, _, _, cx| {
|
||||||
this.unknown(cx);
|
this.unknown(cx);
|
||||||
@@ -259,6 +331,7 @@ impl Render for Sidebar {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ impl Panel for Welcome {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn title(&self, _cx: &App) -> AnyElement {
|
fn title(&self, _cx: &App) -> AnyElement {
|
||||||
self.name.clone().into_any_element()
|
"👋".into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn closable(&self, _cx: &App) -> bool {
|
fn closable(&self, _cx: &App) -> bool {
|
||||||
@@ -92,8 +92,8 @@ impl Render for Welcome {
|
|||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.child("coop on nostr.")
|
.child("coop on nostr.")
|
||||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::FOUR))
|
.text_color(cx.theme().base.step(cx, ColorScaleStep::NINE))
|
||||||
.font_black()
|
.font_semibold()
|
||||||
.text_sm(),
|
.text_sm(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -357,7 +357,7 @@ impl RenderOnce for Button {
|
|||||||
Size::XSmall => this.h_6().px_0p5(),
|
Size::XSmall => this.h_6().px_0p5(),
|
||||||
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_8().px_3(),
|
_ => this.h_9().px_2(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -437,7 +437,7 @@ impl RenderOnce for Button {
|
|||||||
.id("label")
|
.id("label")
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.text_xs()
|
.text_sm()
|
||||||
.map(|this| match self.size {
|
.map(|this| match self.size {
|
||||||
Size::XSmall => this.gap_0p5(),
|
Size::XSmall => this.gap_0p5(),
|
||||||
Size::Small => this.gap_1(),
|
Size::Small => this.gap_1(),
|
||||||
@@ -451,7 +451,6 @@ impl RenderOnce for Button {
|
|||||||
.when(self.loading, |this| {
|
.when(self.loading, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
Indicator::new()
|
Indicator::new()
|
||||||
.with_size(self.size)
|
|
||||||
.when_some(self.loading_icon, |this, icon| this.icon(icon)),
|
.when_some(self.loading_icon, |this, icon| this.icon(icon)),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -407,19 +407,19 @@ impl TabPanel {
|
|||||||
let build_popup_menu = move |this, cx: &App| view.read(cx).popup_menu(this, cx);
|
let build_popup_menu = move |this, cx: &App| view.read(cx).popup_menu(this, cx);
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
.gap_1()
|
||||||
.occlude()
|
.occlude()
|
||||||
.items_center()
|
.items_center()
|
||||||
.children(
|
.children(
|
||||||
self.toolbar_buttons(window, cx)
|
self.toolbar_buttons(window, cx)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|btn| btn.xsmall().ghost()),
|
.map(|btn| btn.small().ghost()),
|
||||||
)
|
)
|
||||||
.when(self.is_zoomed, |this| {
|
.when(self.is_zoomed, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
Button::new("zoom")
|
Button::new("zoom")
|
||||||
.icon(IconName::Minimize)
|
.icon(IconName::ArrowIn)
|
||||||
.xsmall()
|
.small()
|
||||||
.ghost()
|
.ghost()
|
||||||
.tooltip("Zoom Out")
|
.tooltip("Zoom Out")
|
||||||
.on_click(cx.listener(|view, _, window, cx| {
|
.on_click(cx.listener(|view, _, window, cx| {
|
||||||
@@ -430,7 +430,7 @@ impl TabPanel {
|
|||||||
.child(
|
.child(
|
||||||
Button::new("menu")
|
Button::new("menu")
|
||||||
.icon(IconName::Ellipsis)
|
.icon(IconName::Ellipsis)
|
||||||
.xsmall()
|
.small()
|
||||||
.ghost()
|
.ghost()
|
||||||
.popup_menu({
|
.popup_menu({
|
||||||
let zoomable = state.zoomable;
|
let zoomable = state.zoomable;
|
||||||
@@ -451,7 +451,7 @@ impl TabPanel {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_dock_toggle_button(
|
fn _render_dock_toggle_button(
|
||||||
&self,
|
&self,
|
||||||
placement: DockPlacement,
|
placement: DockPlacement,
|
||||||
_window: &mut Window,
|
_window: &mut Window,
|
||||||
@@ -517,7 +517,7 @@ impl TabPanel {
|
|||||||
Some(
|
Some(
|
||||||
Button::new(SharedString::from(format!("toggle-dock:{:?}", placement)))
|
Button::new(SharedString::from(format!("toggle-dock:{:?}", placement)))
|
||||||
.icon(icon)
|
.icon(icon)
|
||||||
.xsmall()
|
.small()
|
||||||
.ghost()
|
.ghost()
|
||||||
.tooltip(match is_open {
|
.tooltip(match is_open {
|
||||||
true => "Collapse",
|
true => "Collapse",
|
||||||
@@ -547,9 +547,6 @@ impl TabPanel {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let panel_style = dock_area.read(cx).panel_style;
|
let panel_style = dock_area.read(cx).panel_style;
|
||||||
let left_dock_button = self.render_dock_toggle_button(DockPlacement::Left, window, cx);
|
|
||||||
let bottom_dock_button = self.render_dock_toggle_button(DockPlacement::Bottom, window, cx);
|
|
||||||
let right_dock_button = self.render_dock_toggle_button(DockPlacement::Right, window, cx);
|
|
||||||
|
|
||||||
if self.panels.len() == 1 && panel_style == PanelStyle::Default {
|
if self.panels.len() == 1 && panel_style == PanelStyle::Default {
|
||||||
let panel = self.panels.first().unwrap();
|
let panel = self.panels.first().unwrap();
|
||||||
@@ -565,21 +562,6 @@ impl TabPanel {
|
|||||||
.h(px(30.))
|
.h(px(30.))
|
||||||
.py_2()
|
.py_2()
|
||||||
.px_3()
|
.px_3()
|
||||||
.when(left_dock_button.is_some(), |this| this.pl_2())
|
|
||||||
.when(right_dock_button.is_some(), |this| this.pr_2())
|
|
||||||
.when(
|
|
||||||
left_dock_button.is_some() || bottom_dock_button.is_some(),
|
|
||||||
|this| {
|
|
||||||
this.child(
|
|
||||||
h_flex()
|
|
||||||
.flex_shrink_0()
|
|
||||||
.mr_1()
|
|
||||||
.gap_1()
|
|
||||||
.children(left_dock_button)
|
|
||||||
.children(bottom_dock_button),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.id("tab")
|
.id("tab")
|
||||||
@@ -612,8 +594,7 @@ impl TabPanel {
|
|||||||
.flex_shrink_0()
|
.flex_shrink_0()
|
||||||
.ml_1()
|
.ml_1()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(self.render_toolbar(state, window, cx))
|
.child(self.render_toolbar(state, window, cx)),
|
||||||
.children(right_dock_button),
|
|
||||||
)
|
)
|
||||||
.into_any_element();
|
.into_any_element();
|
||||||
}
|
}
|
||||||
@@ -622,22 +603,6 @@ impl TabPanel {
|
|||||||
|
|
||||||
TabBar::new("tab-bar")
|
TabBar::new("tab-bar")
|
||||||
.track_scroll(self.tab_bar_scroll_handle.clone())
|
.track_scroll(self.tab_bar_scroll_handle.clone())
|
||||||
.when(
|
|
||||||
left_dock_button.is_some() || bottom_dock_button.is_some(),
|
|
||||||
|this| {
|
|
||||||
this.prefix(
|
|
||||||
h_flex()
|
|
||||||
.items_center()
|
|
||||||
.top_0()
|
|
||||||
// Right -1 for avoid border overlap with the first tab
|
|
||||||
.right(-px(1.))
|
|
||||||
.h_full()
|
|
||||||
.px_2()
|
|
||||||
.children(left_dock_button)
|
|
||||||
.children(bottom_dock_button),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.children(self.panels.iter().enumerate().filter_map(|(ix, panel)| {
|
.children(self.panels.iter().enumerate().filter_map(|(ix, panel)| {
|
||||||
let mut active = state.active_panel.as_ref() == Some(panel);
|
let mut active = state.active_panel.as_ref() == Some(panel);
|
||||||
let disabled = self.is_collapsed;
|
let disabled = self.is_collapsed;
|
||||||
@@ -723,8 +688,7 @@ impl TabPanel {
|
|||||||
.h_full()
|
.h_full()
|
||||||
.px_2()
|
.px_2()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(self.render_toolbar(state, window, cx))
|
.child(self.render_toolbar(state, window, cx)),
|
||||||
.when_some(right_dock_button, |this, btn| this.child(btn)),
|
|
||||||
)
|
)
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -661,9 +661,9 @@ where
|
|||||||
Some(icon) => icon,
|
Some(icon) => icon,
|
||||||
None => {
|
None => {
|
||||||
if self.open {
|
if self.open {
|
||||||
IconName::ChevronUp
|
IconName::CaretUp
|
||||||
} else {
|
} else {
|
||||||
IconName::ChevronDown
|
IconName::CaretDown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,61 +10,37 @@ use gpui::{
|
|||||||
|
|
||||||
#[derive(IntoElement, Clone)]
|
#[derive(IntoElement, Clone)]
|
||||||
pub enum IconName {
|
pub enum IconName {
|
||||||
ALargeSmall,
|
AddressBook,
|
||||||
|
ArrowIn,
|
||||||
ArrowDown,
|
ArrowDown,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
ArrowUp,
|
ArrowUp,
|
||||||
ArrowUpCircle,
|
ArrowUpCircle,
|
||||||
Asterisk,
|
|
||||||
Bell,
|
Bell,
|
||||||
BookOpen,
|
CaretUp,
|
||||||
Bot,
|
CaretDown,
|
||||||
BubbleFill,
|
CaretDownFill,
|
||||||
Calendar,
|
CaretRight,
|
||||||
ChartPie,
|
|
||||||
Check,
|
Check,
|
||||||
ChevronDown,
|
CheckCircle,
|
||||||
ChevronDownSmall,
|
CheckCircleFill,
|
||||||
ChevronLeft,
|
|
||||||
ChevronRight,
|
|
||||||
ChevronUp,
|
|
||||||
ChevronsUpDown,
|
|
||||||
CircleCheck,
|
|
||||||
CircleUser,
|
|
||||||
CircleX,
|
|
||||||
Close,
|
Close,
|
||||||
|
CloseCircle,
|
||||||
|
CloseCircleFill,
|
||||||
Copy,
|
Copy,
|
||||||
ComposeFill,
|
|
||||||
Dash,
|
|
||||||
Delete,
|
|
||||||
Ellipsis,
|
Ellipsis,
|
||||||
EllipsisVertical,
|
|
||||||
Eye,
|
Eye,
|
||||||
EyeOff,
|
EyeOff,
|
||||||
Frame,
|
|
||||||
Folder,
|
Folder,
|
||||||
FolderFill,
|
FolderFill,
|
||||||
FolderOpenFill,
|
|
||||||
GalleryVerticalEnd,
|
|
||||||
GitHub,
|
|
||||||
Globe,
|
|
||||||
Group,
|
|
||||||
GroupFill,
|
|
||||||
Heart,
|
|
||||||
HeartOff,
|
|
||||||
Inbox,
|
Inbox,
|
||||||
Info,
|
Info,
|
||||||
LayoutDashboard,
|
|
||||||
Loader,
|
Loader,
|
||||||
LoaderCircle,
|
LoaderCircle,
|
||||||
Map,
|
MailboxFill,
|
||||||
Maximize,
|
|
||||||
Menu,
|
Menu,
|
||||||
Minimize,
|
|
||||||
Minus,
|
|
||||||
Moon,
|
Moon,
|
||||||
Relays,
|
|
||||||
Palette,
|
Palette,
|
||||||
PanelBottom,
|
PanelBottom,
|
||||||
PanelBottomOpen,
|
PanelBottomOpen,
|
||||||
@@ -75,20 +51,19 @@ pub enum IconName {
|
|||||||
PanelRightClose,
|
PanelRightClose,
|
||||||
PanelRightOpen,
|
PanelRightOpen,
|
||||||
Plus,
|
Plus,
|
||||||
|
PlusCircleFill,
|
||||||
|
Relays,
|
||||||
|
ResizeCorner,
|
||||||
Search,
|
Search,
|
||||||
Settings,
|
Settings,
|
||||||
Settings2,
|
|
||||||
SortAscending,
|
SortAscending,
|
||||||
SortDescending,
|
SortDescending,
|
||||||
SquareTerminal,
|
|
||||||
Star,
|
|
||||||
StarOff,
|
|
||||||
Sun,
|
Sun,
|
||||||
ThumbsDown,
|
ThumbsDown,
|
||||||
ThumbsUp,
|
ThumbsUp,
|
||||||
TriangleAlert,
|
TriangleAlert,
|
||||||
Upload,
|
Upload,
|
||||||
ResizeCorner,
|
UsersThreeFill,
|
||||||
WindowClose,
|
WindowClose,
|
||||||
WindowMaximize,
|
WindowMaximize,
|
||||||
WindowMinimize,
|
WindowMinimize,
|
||||||
@@ -98,61 +73,37 @@ pub enum IconName {
|
|||||||
impl IconName {
|
impl IconName {
|
||||||
pub fn path(self) -> SharedString {
|
pub fn path(self) -> SharedString {
|
||||||
match self {
|
match self {
|
||||||
Self::ALargeSmall => "icons/a-large-small.svg",
|
Self::AddressBook => "icons/address-book.svg",
|
||||||
|
Self::ArrowIn => "icons/arrows-in.svg",
|
||||||
Self::ArrowDown => "icons/arrow-down.svg",
|
Self::ArrowDown => "icons/arrow-down.svg",
|
||||||
Self::ArrowLeft => "icons/arrow-left.svg",
|
Self::ArrowLeft => "icons/arrow-left.svg",
|
||||||
Self::ArrowRight => "icons/arrow-right.svg",
|
Self::ArrowRight => "icons/arrow-right.svg",
|
||||||
Self::ArrowUp => "icons/arrow-up.svg",
|
Self::ArrowUp => "icons/arrow-up.svg",
|
||||||
Self::ArrowUpCircle => "icons/arrow-up-circle.svg",
|
Self::ArrowUpCircle => "icons/arrow-up-circle.svg",
|
||||||
Self::Asterisk => "icons/asterisk.svg",
|
|
||||||
Self::Bell => "icons/bell.svg",
|
Self::Bell => "icons/bell.svg",
|
||||||
Self::BookOpen => "icons/book-open.svg",
|
Self::CaretRight => "icons/caret-right.svg",
|
||||||
Self::Bot => "icons/bot.svg",
|
Self::CaretUp => "icons/caret-up.svg",
|
||||||
Self::BubbleFill => "icons/bubble-fill.svg",
|
Self::CaretDown => "icons/caret-down.svg",
|
||||||
Self::Calendar => "icons/calendar.svg",
|
Self::CaretDownFill => "icons/caret-down-fill.svg",
|
||||||
Self::ChartPie => "icons/chart-pie.svg",
|
|
||||||
Self::Check => "icons/check.svg",
|
Self::Check => "icons/check.svg",
|
||||||
Self::ChevronDown => "icons/chevron-down.svg",
|
Self::CheckCircle => "icons/check-circle.svg",
|
||||||
Self::ChevronDownSmall => "icons/chevron-down-small.svg",
|
Self::CheckCircleFill => "icons/check-circle-fill.svg",
|
||||||
Self::ChevronLeft => "icons/chevron-left.svg",
|
|
||||||
Self::ChevronRight => "icons/chevron-right.svg",
|
|
||||||
Self::ChevronUp => "icons/chevron-up.svg",
|
|
||||||
Self::ChevronsUpDown => "icons/chevrons-up-down.svg",
|
|
||||||
Self::CircleCheck => "icons/circle-check.svg",
|
|
||||||
Self::CircleUser => "icons/circle-user.svg",
|
|
||||||
Self::CircleX => "icons/circle-x.svg",
|
|
||||||
Self::Close => "icons/close.svg",
|
Self::Close => "icons/close.svg",
|
||||||
|
Self::CloseCircle => "icons/close-circle.svg",
|
||||||
|
Self::CloseCircleFill => "icons/close-circle-fill.svg",
|
||||||
Self::Copy => "icons/copy.svg",
|
Self::Copy => "icons/copy.svg",
|
||||||
Self::ComposeFill => "icons/compose-fill.svg",
|
|
||||||
Self::Dash => "icons/dash.svg",
|
|
||||||
Self::Delete => "icons/delete.svg",
|
|
||||||
Self::Ellipsis => "icons/ellipsis.svg",
|
Self::Ellipsis => "icons/ellipsis.svg",
|
||||||
Self::EllipsisVertical => "icons/ellipsis-vertical.svg",
|
|
||||||
Self::Eye => "icons/eye.svg",
|
Self::Eye => "icons/eye.svg",
|
||||||
Self::EyeOff => "icons/eye-off.svg",
|
Self::EyeOff => "icons/eye-off.svg",
|
||||||
Self::Frame => "icons/frame.svg",
|
|
||||||
Self::Folder => "icons/folder.svg",
|
Self::Folder => "icons/folder.svg",
|
||||||
Self::FolderFill => "icons/folder-fill.svg",
|
Self::FolderFill => "icons/folder-fill.svg",
|
||||||
Self::FolderOpenFill => "icons/folder-open-fill.svg",
|
|
||||||
Self::GalleryVerticalEnd => "icons/gallery-vertical-end.svg",
|
|
||||||
Self::GitHub => "icons/github.svg",
|
|
||||||
Self::Globe => "icons/globe.svg",
|
|
||||||
Self::Group => "icons/group.svg",
|
|
||||||
Self::GroupFill => "icons/group-fill.svg",
|
|
||||||
Self::Heart => "icons/heart.svg",
|
|
||||||
Self::HeartOff => "icons/heart-off.svg",
|
|
||||||
Self::Inbox => "icons/inbox.svg",
|
Self::Inbox => "icons/inbox.svg",
|
||||||
Self::Info => "icons/info.svg",
|
Self::Info => "icons/info.svg",
|
||||||
Self::LayoutDashboard => "icons/layout-dashboard.svg",
|
|
||||||
Self::Loader => "icons/loader.svg",
|
Self::Loader => "icons/loader.svg",
|
||||||
Self::LoaderCircle => "icons/loader-circle.svg",
|
Self::LoaderCircle => "icons/loader-circle.svg",
|
||||||
Self::Map => "icons/map.svg",
|
Self::MailboxFill => "icons/mailbox-fill.svg",
|
||||||
Self::Maximize => "icons/maximize.svg",
|
|
||||||
Self::Menu => "icons/menu.svg",
|
Self::Menu => "icons/menu.svg",
|
||||||
Self::Minimize => "icons/minimize.svg",
|
|
||||||
Self::Minus => "icons/minus.svg",
|
|
||||||
Self::Moon => "icons/moon.svg",
|
Self::Moon => "icons/moon.svg",
|
||||||
Self::Relays => "icons/relays.svg",
|
|
||||||
Self::Palette => "icons/palette.svg",
|
Self::Palette => "icons/palette.svg",
|
||||||
Self::PanelBottom => "icons/panel-bottom.svg",
|
Self::PanelBottom => "icons/panel-bottom.svg",
|
||||||
Self::PanelBottomOpen => "icons/panel-bottom-open.svg",
|
Self::PanelBottomOpen => "icons/panel-bottom-open.svg",
|
||||||
@@ -163,20 +114,19 @@ impl IconName {
|
|||||||
Self::PanelRightClose => "icons/panel-right-close.svg",
|
Self::PanelRightClose => "icons/panel-right-close.svg",
|
||||||
Self::PanelRightOpen => "icons/panel-right-open.svg",
|
Self::PanelRightOpen => "icons/panel-right-open.svg",
|
||||||
Self::Plus => "icons/plus.svg",
|
Self::Plus => "icons/plus.svg",
|
||||||
|
Self::PlusCircleFill => "icons/plus-circle-fill.svg",
|
||||||
|
Self::Relays => "icons/relays.svg",
|
||||||
|
Self::ResizeCorner => "icons/resize-corner.svg",
|
||||||
Self::Search => "icons/search.svg",
|
Self::Search => "icons/search.svg",
|
||||||
Self::Settings => "icons/settings.svg",
|
Self::Settings => "icons/settings.svg",
|
||||||
Self::Settings2 => "icons/settings-2.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",
|
||||||
Self::SquareTerminal => "icons/square-terminal.svg",
|
|
||||||
Self::Star => "icons/star.svg",
|
|
||||||
Self::StarOff => "icons/star-off.svg",
|
|
||||||
Self::Sun => "icons/sun.svg",
|
Self::Sun => "icons/sun.svg",
|
||||||
Self::ThumbsDown => "icons/thumbs-down.svg",
|
Self::ThumbsDown => "icons/thumbs-down.svg",
|
||||||
Self::ThumbsUp => "icons/thumbs-up.svg",
|
Self::ThumbsUp => "icons/thumbs-up.svg",
|
||||||
Self::TriangleAlert => "icons/triangle-alert.svg",
|
Self::TriangleAlert => "icons/triangle-alert.svg",
|
||||||
Self::Upload => "icons/upload.svg",
|
Self::Upload => "icons/upload.svg",
|
||||||
Self::ResizeCorner => "icons/resize-corner.svg",
|
Self::UsersThreeFill => "icons/users-three-fill.svg",
|
||||||
Self::WindowClose => "icons/window-close.svg",
|
Self::WindowClose => "icons/window-close.svg",
|
||||||
Self::WindowMaximize => "icons/window-maximize.svg",
|
Self::WindowMaximize => "icons/window-maximize.svg",
|
||||||
Self::WindowMinimize => "icons/window-minimize.svg",
|
Self::WindowMinimize => "icons/window-minimize.svg",
|
||||||
@@ -311,8 +261,8 @@ impl RenderOnce for Icon {
|
|||||||
.when_some(self.size, |this, size| match size {
|
.when_some(self.size, |this, size| match size {
|
||||||
Size::Size(px) => this.size(px),
|
Size::Size(px) => this.size(px),
|
||||||
Size::XSmall => this.size_3(),
|
Size::XSmall => this.size_3(),
|
||||||
Size::Small => this.size_3p5(),
|
Size::Small => this.size_4(),
|
||||||
Size::Medium => this.size_4(),
|
Size::Medium => this.size_5(),
|
||||||
Size::Large => this.size_6(),
|
Size::Large => this.size_6(),
|
||||||
})
|
})
|
||||||
.path(self.path)
|
.path(self.path)
|
||||||
@@ -341,8 +291,8 @@ impl Render for Icon {
|
|||||||
.when_some(self.size, |this, size| match size {
|
.when_some(self.size, |this, size| match size {
|
||||||
Size::Size(px) => this.size(px),
|
Size::Size(px) => this.size(px),
|
||||||
Size::XSmall => this.size_3(),
|
Size::XSmall => this.size_3(),
|
||||||
Size::Small => this.size_3p5(),
|
Size::Small => this.size_4(),
|
||||||
Size::Medium => this.size_4(),
|
Size::Medium => this.size_5(),
|
||||||
Size::Large => this.size_6(),
|
Size::Large => this.size_6(),
|
||||||
})
|
})
|
||||||
.path(self.path.clone())
|
.path(self.path.clone())
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ pub struct Indicator {
|
|||||||
impl Indicator {
|
impl Indicator {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
size: Size::Medium,
|
size: Size::Small,
|
||||||
speed: Duration::from_secs_f64(0.8),
|
speed: Duration::from_secs_f64(0.8),
|
||||||
icon: Icon::new(IconName::Loader),
|
icon: Icon::new(IconName::Loader),
|
||||||
color: None,
|
color: None,
|
||||||
|
|||||||
@@ -1568,6 +1568,7 @@ impl Render for TextInput {
|
|||||||
let suffix = self.suffix.as_ref().map(|build| build(window, cx));
|
let suffix = self.suffix.as_ref().map(|build| build(window, cx));
|
||||||
|
|
||||||
div()
|
div()
|
||||||
|
.flex_1()
|
||||||
.flex()
|
.flex()
|
||||||
.id("input")
|
.id("input")
|
||||||
.key_context(CONTEXT)
|
.key_context(CONTEXT)
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
animation::cubic_bezier,
|
animation::cubic_bezier,
|
||||||
button::{Button, 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, Sizable as _, StyledExt,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, anchored, div, point, prelude::FluentBuilder, px, relative, Animation,
|
actions, anchored, div, point, prelude::FluentBuilder, px, Animation, AnimationExt as _,
|
||||||
AnimationExt as _, AnyElement, App, Bounds, ClickEvent, Div, FocusHandle, InteractiveElement,
|
AnyElement, App, Bounds, ClickEvent, Div, FocusHandle, InteractiveElement, IntoElement,
|
||||||
IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point, RenderOnce, SharedString,
|
KeyBinding, MouseButton, ParentElement, Pixels, Point, RenderOnce, SharedString, Styled,
|
||||||
Styled, Window,
|
Window,
|
||||||
};
|
};
|
||||||
use std::{rc::Rc, time::Duration};
|
use std::{rc::Rc, time::Duration};
|
||||||
|
|
||||||
@@ -45,9 +45,9 @@ impl Modal {
|
|||||||
let base = v_flex()
|
let base = v_flex()
|
||||||
.bg(cx.theme().background)
|
.bg(cx.theme().background)
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE))
|
.border_color(cx.theme().base.step(cx, ColorScaleStep::SEVEN))
|
||||||
.rounded_lg()
|
.rounded_xl()
|
||||||
.shadow_xl();
|
.shadow_md();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
base,
|
base,
|
||||||
@@ -190,19 +190,48 @@ impl RenderOnce for Modal {
|
|||||||
.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))
|
||||||
.when_some(self.title, |this, title| {
|
.px_4()
|
||||||
this.child(
|
.pb_4()
|
||||||
|
.child(
|
||||||
div()
|
div()
|
||||||
|
.h_12()
|
||||||
|
.mb_2()
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(cx.theme().base.step(cx, ColorScaleStep::SIX))
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.h_10()
|
.justify_between()
|
||||||
.px_2()
|
.when_some(self.title, |this, title| {
|
||||||
.text_sm()
|
this.child(div().font_semibold().child(title))
|
||||||
.font_semibold()
|
|
||||||
.line_height(relative(1.))
|
|
||||||
.child(title),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
.when(self.closable, |this| {
|
||||||
|
this.child(
|
||||||
|
Button::new(SharedString::from(format!(
|
||||||
|
"modal-close-{layer_ix}"
|
||||||
|
)))
|
||||||
|
.small()
|
||||||
|
.icon(IconName::CloseCircleFill)
|
||||||
|
.custom(
|
||||||
|
ButtonCustomVariant::new(window, cx)
|
||||||
|
.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| {
|
||||||
|
on_close(&ClickEvent::default(), window, cx);
|
||||||
|
window.close_modal(cx);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(self.content)
|
||||||
|
.children(self.footer)
|
||||||
.when(self.keyboard, |this| {
|
.when(self.keyboard, |this| {
|
||||||
this.on_action({
|
this.on_action({
|
||||||
let on_close = self.on_close.clone();
|
let on_close = self.on_close.clone();
|
||||||
@@ -216,27 +245,6 @@ impl RenderOnce for Modal {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.when(self.closable, |this| {
|
|
||||||
this.child(
|
|
||||||
Button::new(SharedString::from(format!(
|
|
||||||
"modal-close-{layer_ix}"
|
|
||||||
)))
|
|
||||||
.absolute()
|
|
||||||
.top_2()
|
|
||||||
.right_2()
|
|
||||||
.small()
|
|
||||||
.ghost()
|
|
||||||
.icon(IconName::Close)
|
|
||||||
.on_click(
|
|
||||||
move |_, window, cx| {
|
|
||||||
on_close(&ClickEvent::default(), window, cx);
|
|
||||||
window.close_modal(cx);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.child(self.content)
|
|
||||||
.children(self.footer)
|
|
||||||
.with_animation(
|
.with_animation(
|
||||||
"slide-down",
|
"slide-down",
|
||||||
Animation::new(Duration::from_secs_f64(0.25))
|
Animation::new(Duration::from_secs_f64(0.25))
|
||||||
|
|||||||
@@ -221,10 +221,9 @@ impl Render for Notification {
|
|||||||
NotificationType::Info => {
|
NotificationType::Info => {
|
||||||
Icon::new(IconName::Info).text_color(blue().step(cx, ColorScaleStep::NINE))
|
Icon::new(IconName::Info).text_color(blue().step(cx, ColorScaleStep::NINE))
|
||||||
}
|
}
|
||||||
NotificationType::Error => {
|
NotificationType::Error => Icon::new(IconName::CloseCircle)
|
||||||
Icon::new(IconName::CircleX).text_color(red().step(cx, ColorScaleStep::NINE))
|
.text_color(red().step(cx, ColorScaleStep::NINE)),
|
||||||
}
|
NotificationType::Success => Icon::new(IconName::CheckCircle)
|
||||||
NotificationType::Success => Icon::new(IconName::CircleCheck)
|
|
||||||
.text_color(green().step(cx, ColorScaleStep::NINE)),
|
.text_color(green().step(cx, ColorScaleStep::NINE)),
|
||||||
NotificationType::Warning => Icon::new(IconName::TriangleAlert)
|
NotificationType::Warning => Icon::new(IconName::TriangleAlert)
|
||||||
.text_color(yellow().step(cx, ColorScaleStep::NINE)),
|
.text_color(yellow().step(cx, ColorScaleStep::NINE)),
|
||||||
|
|||||||
@@ -574,7 +574,7 @@ impl Render for PopupMenu {
|
|||||||
.py_0()
|
.py_0()
|
||||||
.px_2()
|
.px_2()
|
||||||
.rounded_md()
|
.rounded_md()
|
||||||
.text_xs()
|
.text_sm()
|
||||||
.on_mouse_enter(cx.listener(
|
.on_mouse_enter(cx.listener(
|
||||||
move |this, _, _window, cx| {
|
move |this, _, _window, cx| {
|
||||||
this.hovered_menu_ix = Some(ix);
|
this.hovered_menu_ix = Some(ix);
|
||||||
@@ -677,7 +677,7 @@ impl Render for PopupMenu {
|
|||||||
.justify_between()
|
.justify_between()
|
||||||
.child(label.clone())
|
.child(label.clone())
|
||||||
.child(
|
.child(
|
||||||
IconName::ChevronRight,
|
IconName::CaretRight,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -198,9 +198,10 @@ impl<T: Styled> StyleSized<T> for T {
|
|||||||
|
|
||||||
fn input_h(self, size: Size) -> Self {
|
fn input_h(self, size: Size) -> Self {
|
||||||
match size {
|
match size {
|
||||||
Size::Large => self.h_11(),
|
Size::Small => self.h_7(),
|
||||||
Size::Medium => self.h_8(),
|
Size::Medium => self.h_9(),
|
||||||
_ => self.h(px(28.)),
|
Size::Large => self.h_12(),
|
||||||
|
_ => self.h(px(24.)),
|
||||||
}
|
}
|
||||||
.input_text_size(size)
|
.input_text_size(size)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,36 +81,38 @@ impl RenderOnce for Tab {
|
|||||||
let (text_color, bg_color, hover_bg_color) = match (self.selected, self.disabled) {
|
let (text_color, bg_color, hover_bg_color) = match (self.selected, self.disabled) {
|
||||||
(true, false) => (
|
(true, false) => (
|
||||||
cx.theme().base.step(cx, ColorScaleStep::TWELVE),
|
cx.theme().base.step(cx, ColorScaleStep::TWELVE),
|
||||||
cx.theme().base.step(cx, ColorScaleStep::THREE),
|
cx.theme().base.step(cx, ColorScaleStep::FIVE),
|
||||||
cx.theme().base.step(cx, ColorScaleStep::FOUR),
|
cx.theme().base.step(cx, ColorScaleStep::FOUR),
|
||||||
),
|
),
|
||||||
(false, false) => (
|
(false, false) => (
|
||||||
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
|
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
|
||||||
cx.theme().base.step(cx, ColorScaleStep::ONE),
|
cx.theme().transparent,
|
||||||
cx.theme().base.step(cx, ColorScaleStep::FOUR),
|
cx.theme().base.step(cx, ColorScaleStep::FOUR),
|
||||||
),
|
),
|
||||||
// disabled
|
// disabled
|
||||||
(true, true) => (
|
(true, true) => (
|
||||||
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
|
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
|
||||||
cx.theme().base.step(cx, ColorScaleStep::ONE),
|
cx.theme().transparent,
|
||||||
cx.theme().base.step(cx, ColorScaleStep::FOUR),
|
cx.theme().base.step(cx, ColorScaleStep::FOUR),
|
||||||
),
|
),
|
||||||
(false, true) => (
|
(false, true) => (
|
||||||
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
|
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
|
||||||
cx.theme().base.step(cx, ColorScaleStep::ONE),
|
cx.theme().transparent,
|
||||||
cx.theme().base.step(cx, ColorScaleStep::FOUR),
|
cx.theme().base.step(cx, ColorScaleStep::FOUR),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.base
|
self.base
|
||||||
.h(px(30.))
|
.h(px(30.))
|
||||||
|
.px_2()
|
||||||
.relative()
|
.relative()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.flex_shrink_0()
|
.flex_shrink_0()
|
||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.text_sm()
|
.text_xs()
|
||||||
|
.text_ellipsis()
|
||||||
.text_color(text_color)
|
.text_color(text_color)
|
||||||
.bg(bg_color)
|
.bg(bg_color)
|
||||||
.rounded(px(cx.theme().radius))
|
.rounded(px(cx.theme().radius))
|
||||||
@@ -118,16 +120,7 @@ impl RenderOnce for Tab {
|
|||||||
.when_some(self.prefix, |this, prefix| {
|
.when_some(self.prefix, |this, prefix| {
|
||||||
this.child(prefix).text_color(text_color)
|
this.child(prefix).text_color(text_color)
|
||||||
})
|
})
|
||||||
.child(
|
.child(self.label)
|
||||||
div()
|
|
||||||
.px_3()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.gap_1()
|
|
||||||
.text_ellipsis()
|
|
||||||
.text_xs()
|
|
||||||
.child(self.label),
|
|
||||||
)
|
|
||||||
.when_some(self.suffix, |this, suffix| this.child(suffix))
|
.when_some(self.suffix, |this, suffix| this.child(suffix))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ impl Theme {
|
|||||||
Theme {
|
Theme {
|
||||||
base: color_scales.gray,
|
base: color_scales.gray,
|
||||||
accent: color_scales.yellow,
|
accent: color_scales.yellow,
|
||||||
font_size: 16.0,
|
font_size: 15.0,
|
||||||
font_family: if cfg!(target_os = "macos") {
|
font_family: if cfg!(target_os = "macos") {
|
||||||
".SystemUIFont".into()
|
".SystemUIFont".into()
|
||||||
} else if cfg!(target_os = "windows") {
|
} else if cfg!(target_os = "windows") {
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
use crate::{
|
use crate::{h_flex, theme::ActiveTheme, Icon, IconName, InteractiveElementExt as _, Sizable as _};
|
||||||
h_flex,
|
|
||||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
|
||||||
Icon, IconName, InteractiveElementExt as _, Sizable as _,
|
|
||||||
};
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
black, div, prelude::FluentBuilder as _, px, relative, white, AnyElement, App, ClickEvent, Div,
|
black, div, prelude::FluentBuilder as _, px, relative, white, AnyElement, App, ClickEvent, Div,
|
||||||
Element, Hsla, InteractiveElement as _, IntoElement, MouseButton, ParentElement, Pixels,
|
Element, Hsla, InteractiveElement as _, IntoElement, MouseButton, ParentElement, Pixels,
|
||||||
@@ -251,7 +247,7 @@ impl RenderOnce for TitleBar {
|
|||||||
.items_center()
|
.items_center()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.h(HEIGHT)
|
.h(HEIGHT)
|
||||||
.bg(cx.theme().base.step(cx, ColorScaleStep::ONE))
|
.bg(cx.theme().transparent)
|
||||||
.when(window.is_fullscreen(), |this| this.pl(px(12.)))
|
.when(window.is_fullscreen(), |this| this.pl(px(12.)))
|
||||||
.on_double_click(|_, window, _cx| window.zoom_window())
|
.on_double_click(|_, window, _cx| window.zoom_window())
|
||||||
.child(
|
.child(
|
||||||
|
|||||||