feat: Chat Folders (#14)
* add room kinds * add folders * adjust design * update * refactor * cache * update
This commit is contained in:
261
Cargo.lock
generated
261
Cargo.lock
generated
@@ -466,9 +466,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-lc-rs"
|
name = "aws-lc-rs"
|
||||||
version = "1.12.6"
|
version = "1.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dabb68eb3a7aa08b46fddfd59a3d55c978243557a90ab804769f7e20e67d2b01"
|
checksum = "19b756939cb2f8dc900aa6dcd505e6e2428e9cae7ff7b028c49e3946efa70878"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-lc-sys",
|
"aws-lc-sys",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
@@ -476,9 +476,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-lc-sys"
|
name = "aws-lc-sys"
|
||||||
version = "0.27.1"
|
version = "0.28.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77926887776171ced7d662120a75998e444d3750c951abfe07f90da130514b1f"
|
checksum = "b9f7720b74ed28ca77f90769a71fd8c637a0137f6fae4ae947e1050229cff57f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bindgen 0.69.5",
|
"bindgen 0.69.5",
|
||||||
"cc",
|
"cc",
|
||||||
@@ -545,9 +545,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bindgen"
|
name = "bindgen"
|
||||||
version = "0.70.1"
|
version = "0.71.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f"
|
checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
"cexpr",
|
"cexpr",
|
||||||
@@ -558,7 +558,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"regex",
|
"regex",
|
||||||
"rustc-hash 1.1.0",
|
"rustc-hash 2.1.1",
|
||||||
"shlex",
|
"shlex",
|
||||||
"syn 2.0.100",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
@@ -869,9 +869,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.17"
|
version = "1.2.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
|
checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jobserver",
|
"jobserver",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -921,6 +921,15 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cgl"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chacha20"
|
name = "chacha20"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
@@ -1029,9 +1038,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.34"
|
version = "4.5.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e958897981290da2a852763fe9cdb89cd36977a5d729023127095fa94d95e2ff"
|
checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@@ -1039,9 +1048,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.34"
|
version = "4.5.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "83b0f35019843db2160b5bb19ae09b4e6411ac33fc6a712003c33e03090e2489"
|
checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@@ -1149,10 +1158,11 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collections"
|
name = "collections"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f6c81a0595f518bf2f245a199248d50245afacac"
|
source = "git+https://github.com/zed-industries/zed#c1259c136e41b5870d15271190198ff0075ba35c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
|
"workspace-hack",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1330,17 +1340,44 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-text"
|
name = "core-graphics2"
|
||||||
version = "20.1.0"
|
version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5"
|
checksum = "7e4583956b9806b69f73fcb23aee05eb3620efc282972f08f6a6db7504f8334d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation 0.9.4",
|
"bitflags 2.9.0",
|
||||||
"core-graphics 0.23.2",
|
"block",
|
||||||
|
"cfg-if",
|
||||||
|
"core-foundation 0.10.0",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-text"
|
||||||
|
version = "21.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a593227b66cbd4007b2a050dfdd9e1d1318311409c8d600dc82ba1b15ca9c130"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation 0.10.0",
|
||||||
|
"core-graphics 0.24.0",
|
||||||
"foreign-types",
|
"foreign-types",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-video"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d45e71d5be22206bed53c3c3cb99315fc4c3d31b8963808c6bc4538168c4f8ef"
|
||||||
|
dependencies = [
|
||||||
|
"block",
|
||||||
|
"core-foundation 0.10.0",
|
||||||
|
"core-graphics2",
|
||||||
|
"io-surface",
|
||||||
|
"libc",
|
||||||
|
"metal",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core_maths"
|
name = "core_maths"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -1522,11 +1559,12 @@ 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#f6c81a0595f518bf2f245a199248d50245afacac"
|
source = "git+https://github.com/zed-industries/zed#c1259c136e41b5870d15271190198ff0075ba35c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
|
"workspace-hack",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1729,9 +1767,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.10"
|
version = "0.3.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
@@ -1839,9 +1877,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.1.0"
|
version = "1.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
|
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
@@ -1886,12 +1924,12 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "font-kit"
|
name = "font-kit"
|
||||||
version = "0.14.1"
|
version = "0.14.1"
|
||||||
source = "git+https://github.com/zed-industries/font-kit?rev=40391b7#40391b7c0041d8a8572af2afa3de32ae088f0120"
|
source = "git+https://github.com/zed-industries/font-kit?rev=5474cfad4b719a72ec8ed2cb7327b2b01fd10568#5474cfad4b719a72ec8ed2cb7327b2b01fd10568"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"core-foundation 0.9.4",
|
"core-foundation 0.10.0",
|
||||||
"core-graphics 0.23.2",
|
"core-graphics 0.24.0",
|
||||||
"core-text",
|
"core-text",
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
"dwrote",
|
"dwrote",
|
||||||
@@ -1909,9 +1947,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "font-types"
|
name = "font-types"
|
||||||
version = "0.8.3"
|
version = "0.8.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d868ec188a98bb014c606072edd47e52e7ab7297db943b0b28503121e1d037bd"
|
checksum = "1fa6a5e5a77b5f3f7f9e32879f484aa5b3632ddfbe568a16266c904a6f32cdaf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
]
|
]
|
||||||
@@ -2282,13 +2320,13 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui"
|
name = "gpui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f6c81a0595f518bf2f245a199248d50245afacac"
|
source = "git+https://github.com/zed-industries/zed#c1259c136e41b5870d15271190198ff0075ba35c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"as-raw-xcb-connection",
|
"as-raw-xcb-connection",
|
||||||
"ashpd",
|
"ashpd",
|
||||||
"async-task",
|
"async-task",
|
||||||
"bindgen 0.70.1",
|
"bindgen 0.71.1",
|
||||||
"blade-graphics",
|
"blade-graphics",
|
||||||
"blade-macros",
|
"blade-macros",
|
||||||
"blade-util",
|
"blade-util",
|
||||||
@@ -2299,10 +2337,11 @@ dependencies = [
|
|||||||
"cbindgen",
|
"cbindgen",
|
||||||
"cocoa 0.26.0",
|
"cocoa 0.26.0",
|
||||||
"collections",
|
"collections",
|
||||||
"core-foundation 0.9.4",
|
"core-foundation 0.10.0",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"core-graphics 0.23.2",
|
"core-graphics 0.24.0",
|
||||||
"core-text",
|
"core-text",
|
||||||
|
"core-video",
|
||||||
"cosmic-text",
|
"cosmic-text",
|
||||||
"ctor",
|
"ctor",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
@@ -2359,8 +2398,9 @@ dependencies = [
|
|||||||
"wayland-protocols",
|
"wayland-protocols",
|
||||||
"wayland-protocols-plasma",
|
"wayland-protocols-plasma",
|
||||||
"windows",
|
"windows",
|
||||||
"windows-core 0.61.0",
|
"windows-core",
|
||||||
"windows-numerics",
|
"windows-numerics",
|
||||||
|
"workspace-hack",
|
||||||
"x11-clipboard",
|
"x11-clipboard",
|
||||||
"x11rb",
|
"x11rb",
|
||||||
"xim",
|
"xim",
|
||||||
@@ -2370,11 +2410,12 @@ 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#f6c81a0595f518bf2f245a199248d50245afacac"
|
source = "git+https://github.com/zed-industries/zed#c1259c136e41b5870d15271190198ff0075ba35c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
|
"workspace-hack",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2599,7 +2640,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#f6c81a0595f518bf2f245a199248d50245afacac"
|
source = "git+https://github.com/zed-industries/zed#c1259c136e41b5870d15271190198ff0075ba35c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -2610,15 +2651,17 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"url",
|
"url",
|
||||||
|
"workspace-hack",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[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#f6c81a0595f518bf2f245a199248d50245afacac"
|
source = "git+https://github.com/zed-industries/zed#c1259c136e41b5870d15271190198ff0075ba35c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-platform-verifier",
|
"rustls-platform-verifier",
|
||||||
|
"workspace-hack",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2668,9 +2711,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-util"
|
name = "hyper-util"
|
||||||
version = "0.1.10"
|
version = "0.1.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4"
|
checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
@@ -2678,6 +2721,7 @@ dependencies = [
|
|||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
"libc",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -2687,9 +2731,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.62"
|
version = "0.1.63"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127"
|
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android_system_properties",
|
"android_system_properties",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
@@ -2697,7 +2741,7 @@ dependencies = [
|
|||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-core 0.52.0",
|
"windows-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2895,9 +2939,9 @@ checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.8.0"
|
version = "2.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
|
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.15.2",
|
"hashbrown 0.15.2",
|
||||||
@@ -2946,6 +2990,19 @@ dependencies = [
|
|||||||
"rustversion",
|
"rustversion",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "io-surface"
|
||||||
|
version = "0.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8283575d5f0b2e7447ec0840363879d71c0fa325d4c699d5b45208ea4a51f45e"
|
||||||
|
dependencies = [
|
||||||
|
"cgl",
|
||||||
|
"core-foundation 0.10.0",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"leaky-cow",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.11.0"
|
version = "2.11.0"
|
||||||
@@ -3054,10 +3111,11 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobserver"
|
name = "jobserver"
|
||||||
version = "0.1.32"
|
version = "0.1.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
|
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"getrandom 0.3.2",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3118,6 +3176,21 @@ version = "1.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "leak"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bd100e01f1154f2908dfa7d02219aeab25d0b9c7fa955164192e3245255a0c73"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "leaky-cow"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40a8225d44241fd324a8af2806ba635fc7c8a7e9a7de4d5cf3ef54e71f5926fc"
|
||||||
|
dependencies = [
|
||||||
|
"leak",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lebe"
|
name = "lebe"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@@ -3329,15 +3402,17 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "media"
|
name = "media"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f6c81a0595f518bf2f245a199248d50245afacac"
|
source = "git+https://github.com/zed-industries/zed#c1259c136e41b5870d15271190198ff0075ba35c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bindgen 0.70.1",
|
"bindgen 0.71.1",
|
||||||
"core-foundation 0.9.4",
|
"core-foundation 0.10.0",
|
||||||
|
"core-video",
|
||||||
"ctor",
|
"ctor",
|
||||||
"foreign-types",
|
"foreign-types",
|
||||||
"metal",
|
"metal",
|
||||||
"objc",
|
"objc",
|
||||||
|
"workspace-hack",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3366,9 +3441,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "metal"
|
name = "metal"
|
||||||
version = "0.31.0"
|
version = "0.29.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e"
|
checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
"block",
|
"block",
|
||||||
@@ -3403,9 +3478,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.5"
|
version = "0.8.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
|
checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler2",
|
"adler2",
|
||||||
"simd-adler32",
|
"simd-adler32",
|
||||||
@@ -3508,7 +3583,7 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr"
|
name = "nostr"
|
||||||
version = "0.40.0"
|
version = "0.40.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#31180d7dd2aed5afb895945fef9359646f22ff3e"
|
source = "git+https://github.com/rust-nostr/nostr#1347878e90392db54a263d60021fa0410a3b05cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"base64",
|
"base64",
|
||||||
@@ -3533,7 +3608,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-connect"
|
name = "nostr-connect"
|
||||||
version = "0.40.0"
|
version = "0.40.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#31180d7dd2aed5afb895945fef9359646f22ff3e"
|
source = "git+https://github.com/rust-nostr/nostr#1347878e90392db54a263d60021fa0410a3b05cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -3545,7 +3620,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-database"
|
name = "nostr-database"
|
||||||
version = "0.40.0"
|
version = "0.40.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#31180d7dd2aed5afb895945fef9359646f22ff3e"
|
source = "git+https://github.com/rust-nostr/nostr#1347878e90392db54a263d60021fa0410a3b05cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"flatbuffers",
|
"flatbuffers",
|
||||||
"lru",
|
"lru",
|
||||||
@@ -3556,7 +3631,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-lmdb"
|
name = "nostr-lmdb"
|
||||||
version = "0.40.0"
|
version = "0.40.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#31180d7dd2aed5afb895945fef9359646f22ff3e"
|
source = "git+https://github.com/rust-nostr/nostr#1347878e90392db54a263d60021fa0410a3b05cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"heed",
|
"heed",
|
||||||
@@ -3569,7 +3644,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-relay-pool"
|
name = "nostr-relay-pool"
|
||||||
version = "0.40.0"
|
version = "0.40.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#31180d7dd2aed5afb895945fef9359646f22ff3e"
|
source = "git+https://github.com/rust-nostr/nostr#1347878e90392db54a263d60021fa0410a3b05cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"async-wsocket",
|
"async-wsocket",
|
||||||
@@ -3586,7 +3661,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-sdk"
|
name = "nostr-sdk"
|
||||||
version = "0.40.0"
|
version = "0.40.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#31180d7dd2aed5afb895945fef9359646f22ff3e"
|
source = "git+https://github.com/rust-nostr/nostr#1347878e90392db54a263d60021fa0410a3b05cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -3942,9 +4017,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.2"
|
version = "1.21.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2806eaa3524762875e21c3dcd057bc4b7bfa01ce4da8d46be1cd43649e1cc6b"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oneshot"
|
name = "oneshot"
|
||||||
@@ -4333,9 +4408,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
version = "0.2.31"
|
version = "0.2.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb"
|
checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"syn 2.0.100",
|
"syn 2.0.100",
|
||||||
@@ -4412,9 +4487,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.37.3"
|
version = "0.37.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf763ab1c7a3aa408be466efc86efe35ed1bd3dd74173ed39d6b0d0a6f0ba148"
|
checksum = "a4ce8c88de324ff838700f36fb6ab86c96df0e3c4ab6ef3a9b2044465cce1369"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
@@ -4692,9 +4767,10 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "refineable"
|
name = "refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f6c81a0595f518bf2f245a199248d50245afacac"
|
source = "git+https://github.com/zed-industries/zed#c1259c136e41b5870d15271190198ff0075ba35c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_refineable",
|
"derive_refineable",
|
||||||
|
"workspace-hack",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4821,7 +4897,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#f6c81a0595f518bf2f245a199248d50245afacac"
|
source = "git+https://github.com/zed-industries/zed#c1259c136e41b5870d15271190198ff0075ba35c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -4834,6 +4910,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"smol",
|
"smol",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"workspace-hack",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4956,9 +5033,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.0.3"
|
version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96"
|
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
"errno",
|
"errno",
|
||||||
@@ -5233,10 +5310,11 @@ checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "semantic_version"
|
name = "semantic_version"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f6c81a0595f518bf2f245a199248d50245afacac"
|
source = "git+https://github.com/zed-industries/zed#c1259c136e41b5870d15271190198ff0075ba35c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"serde",
|
"serde",
|
||||||
|
"workspace-hack",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5442,9 +5520,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.14.0"
|
version = "1.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
|
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smol"
|
name = "smol"
|
||||||
@@ -5471,9 +5549,9 @@ checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.5.8"
|
version = "0.5.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
|
checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
@@ -5555,11 +5633,12 @@ 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#f6c81a0595f518bf2f245a199248d50245afacac"
|
source = "git+https://github.com/zed-industries/zed#c1259c136e41b5870d15271190198ff0075ba35c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"log",
|
"log",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
"workspace-hack",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5658,9 +5737,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "swash"
|
name = "swash"
|
||||||
version = "0.2.1"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13d5bbc2aa266907ed8ee977c9c9e16363cc2b001266104e13397b57f1d15f71"
|
checksum = "fae9a562c7b46107d9c78cd78b75bbe1e991c16734c0aee8ff0ee711fb8b620a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"skrifa",
|
"skrifa",
|
||||||
"yazi",
|
"yazi",
|
||||||
@@ -5795,7 +5874,7 @@ dependencies = [
|
|||||||
"fastrand 2.3.0",
|
"fastrand 2.3.0",
|
||||||
"getrandom 0.3.2",
|
"getrandom 0.3.2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix 1.0.3",
|
"rustix 1.0.5",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -6431,7 +6510,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#f6c81a0595f518bf2f245a199248d50245afacac"
|
source = "git+https://github.com/zed-industries/zed#c1259c136e41b5870d15271190198ff0075ba35c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-fs",
|
"async-fs",
|
||||||
@@ -6452,6 +6531,7 @@ dependencies = [
|
|||||||
"take-until",
|
"take-until",
|
||||||
"tendril",
|
"tendril",
|
||||||
"unicase",
|
"unicase",
|
||||||
|
"workspace-hack",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6870,7 +6950,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419"
|
checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-collections",
|
"windows-collections",
|
||||||
"windows-core 0.61.0",
|
"windows-core",
|
||||||
"windows-future",
|
"windows-future",
|
||||||
"windows-link",
|
"windows-link",
|
||||||
"windows-numerics",
|
"windows-numerics",
|
||||||
@@ -6882,16 +6962,7 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
|
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-core 0.61.0",
|
"windows-core",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-core"
|
|
||||||
version = "0.52.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
|
||||||
dependencies = [
|
|
||||||
"windows-targets 0.52.6",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6913,7 +6984,7 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32"
|
checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-core 0.61.0",
|
"windows-core",
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -6951,7 +7022,7 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
|
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-core 0.61.0",
|
"windows-core",
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -7338,6 +7409,12 @@ dependencies = [
|
|||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "workspace-hack"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "beffa227304dbaea3ad6a06ac674f9bc83a3dec3b7f63eeb442de37e7cb6bb01"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "write16"
|
name = "write16"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
|||||||
3
assets/icons/bubble-fill.svg
Normal file
3
assets/icons/bubble-fill.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<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="M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10a9.967 9.967 0 0 1-4.098-.876.313.313 0 0 0-.195-.026l-3.471.78a1.75 1.75 0 0 1-2.084-2.12l.809-3.33a.313.313 0 0 0-.028-.204A9.965 9.965 0 0 1 2 12Zm4.5 0a1 1 0 1 0 2 0 1 1 0 0 0-2 0Zm4.5 0a1 1 0 1 0 2 0 1 1 0 0 0-2 0Zm5.5 1a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z" clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 488 B |
3
assets/icons/folder-fill.svg
Normal file
3
assets/icons/folder-fill.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 329 B |
3
assets/icons/folder-open-fill.svg
Normal file
3
assets/icons/folder-open-fill.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path fill="#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>
|
||||||
|
After Width: | Height: | Size: 617 B |
3
assets/icons/folder.svg
Normal file
3
assets/icons/folder.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="M2.75 5.75v11.5a2 2 0 0 0 2 2h14.5a2 2 0 0 0 2-2v-8.5a2 2 0 0 0-2-2h-6.18a2 2 0 0 1-1.664-.89l-.812-1.22a2 2 0 0 0-1.664-.89H4.75a2 2 0 0 0-2 2Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 350 B |
@@ -118,31 +118,34 @@ impl Account {
|
|||||||
let user = profile.public_key;
|
let user = profile.public_key;
|
||||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
|
|
||||||
// Create a contact list filter
|
let metadata = Filter::new()
|
||||||
let contacts = Filter::new().kind(Kind::ContactList).author(user).limit(1);
|
.kinds(vec![
|
||||||
|
Kind::Metadata,
|
||||||
|
Kind::ContactList,
|
||||||
|
Kind::InboxRelays,
|
||||||
|
Kind::MuteList,
|
||||||
|
Kind::SimpleGroups,
|
||||||
|
])
|
||||||
|
.author(user)
|
||||||
|
.limit(10);
|
||||||
|
|
||||||
// Create a user's data filter
|
|
||||||
let data = Filter::new()
|
let data = Filter::new()
|
||||||
.author(user)
|
.author(user)
|
||||||
.since(Timestamp::now())
|
.since(Timestamp::now())
|
||||||
.kinds(vec![
|
.kinds(vec![
|
||||||
Kind::Metadata,
|
Kind::Metadata,
|
||||||
Kind::ContactList,
|
Kind::ContactList,
|
||||||
|
Kind::MuteList,
|
||||||
|
Kind::SimpleGroups,
|
||||||
Kind::InboxRelays,
|
Kind::InboxRelays,
|
||||||
Kind::RelayList,
|
Kind::RelayList,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Create a filter for getting all gift wrapped events send to current user
|
|
||||||
let msg = Filter::new().kind(Kind::GiftWrap).pubkey(user);
|
let msg = Filter::new().kind(Kind::GiftWrap).pubkey(user);
|
||||||
|
|
||||||
// Create a filter to continuously receive new messages.
|
|
||||||
let new_msg = Filter::new().kind(Kind::GiftWrap).pubkey(user).limit(0);
|
let new_msg = Filter::new().kind(Kind::GiftWrap).pubkey(user).limit(0);
|
||||||
|
|
||||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||||
// Only subscribe to the latest contact list
|
client.subscribe(metadata, Some(opts)).await?;
|
||||||
client.subscribe(contacts, Some(opts)).await?;
|
|
||||||
|
|
||||||
// Continuously receive new user's data since now
|
|
||||||
client.subscribe(data, None).await?;
|
client.subscribe(data, None).await?;
|
||||||
|
|
||||||
let sub_id = SubscriptionId::new(ALL_MESSAGES_SUB_ID);
|
let sub_id = SubscriptionId::new(ALL_MESSAGES_SUB_ID);
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ impl ChatSpace {
|
|||||||
);
|
);
|
||||||
|
|
||||||
self.dock.update(cx, |this, cx| {
|
self.dock.update(cx, |this, cx| {
|
||||||
this.set_left_dock(left, Some(px(240.)), true, window, cx);
|
this.set_left_dock(left, Some(px(260.)), true, window, cx);
|
||||||
this.set_center(center, window, cx);
|
this.set_center(center, window, cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use anyhow::{anyhow, Error};
|
||||||
use asset::Assets;
|
use asset::Assets;
|
||||||
use chats::ChatRegistry;
|
use chats::ChatRegistry;
|
||||||
use futures::{select, FutureExt};
|
use futures::{select, FutureExt};
|
||||||
@@ -16,8 +17,8 @@ use gpui::{point, SharedString, TitlebarOptions};
|
|||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use gpui::{WindowBackgroundAppearance, WindowDecorations};
|
use gpui::{WindowBackgroundAppearance, WindowDecorations};
|
||||||
use nostr_sdk::{
|
use nostr_sdk::{
|
||||||
pool::prelude::ReqExitPolicy, Event, Filter, Keys, Kind, PublicKey, RelayMessage,
|
pool::prelude::ReqExitPolicy, Event, EventBuilder, EventId, Filter, JsonUtil, Keys, Kind,
|
||||||
RelayPoolNotification, SubscribeAutoCloseOptions, SubscriptionId,
|
PublicKey, RelayMessage, RelayPoolNotification, SubscribeAutoCloseOptions, SubscriptionId, Tag,
|
||||||
};
|
};
|
||||||
use smol::Timer;
|
use smol::Timer;
|
||||||
use std::{collections::HashSet, mem, sync::Arc, time::Duration};
|
use std::{collections::HashSet, mem, sync::Arc, time::Duration};
|
||||||
@@ -44,7 +45,7 @@ fn main() {
|
|||||||
_ = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
_ = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
||||||
|
|
||||||
let (event_tx, event_rx) = smol::channel::bounded::<Signal>(1024);
|
let (event_tx, event_rx) = smol::channel::bounded::<Signal>(1024);
|
||||||
let (batch_tx, batch_rx) = smol::channel::bounded::<Vec<PublicKey>>(100);
|
let (batch_tx, batch_rx) = smol::channel::bounded::<Vec<PublicKey>>(500);
|
||||||
|
|
||||||
// Initialize nostr client
|
// Initialize nostr client
|
||||||
let client = get_client();
|
let client = get_client();
|
||||||
@@ -68,8 +69,8 @@ fn main() {
|
|||||||
// Handle batch metadata
|
// Handle batch metadata
|
||||||
app.background_executor()
|
app.background_executor()
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
const BATCH_SIZE: usize = 20;
|
const BATCH_SIZE: usize = 500;
|
||||||
const BATCH_TIMEOUT: Duration = Duration::from_millis(500);
|
const BATCH_TIMEOUT: Duration = Duration::from_millis(300);
|
||||||
|
|
||||||
let mut batch: HashSet<PublicKey> = HashSet::new();
|
let mut batch: HashSet<PublicKey> = HashSet::new();
|
||||||
|
|
||||||
@@ -82,7 +83,7 @@ fn main() {
|
|||||||
Ok(keys) => {
|
Ok(keys) => {
|
||||||
batch.extend(keys);
|
batch.extend(keys);
|
||||||
if batch.len() >= BATCH_SIZE {
|
if batch.len() >= BATCH_SIZE {
|
||||||
handle_metadata(mem::take(&mut batch)).await;
|
sync_metadata(mem::take(&mut batch)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => break,
|
Err(_) => break,
|
||||||
@@ -90,7 +91,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
_ = timeout => {
|
_ = timeout => {
|
||||||
if !batch.is_empty() {
|
if !batch.is_empty() {
|
||||||
handle_metadata(mem::take(&mut batch)).await;
|
sync_metadata(mem::take(&mut batch)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,40 +116,60 @@ fn main() {
|
|||||||
} => {
|
} => {
|
||||||
match event.kind {
|
match event.kind {
|
||||||
Kind::GiftWrap => {
|
Kind::GiftWrap => {
|
||||||
if let Ok(gift) = client.unwrap_gift_wrap(&event).await {
|
let event = match get_unwrapped(event.id).await {
|
||||||
// Sign the rumor with the generated keys,
|
Ok(event) => event,
|
||||||
// this event will be used for internal only,
|
Err(_) => match client.unwrap_gift_wrap(&event).await {
|
||||||
// and NEVER send to relays.
|
Ok(unwrap) => {
|
||||||
if let Ok(event) = gift.rumor.sign_with_keys(&rng_keys) {
|
match unwrap.rumor.sign_with_keys(&rng_keys) {
|
||||||
let mut pubkeys = vec![];
|
Ok(ev) => {
|
||||||
pubkeys.extend(event.tags.public_keys());
|
set_unwrapped(event.id, &ev, &rng_keys)
|
||||||
pubkeys.push(event.pubkey);
|
.await
|
||||||
|
.ok();
|
||||||
// Save the event to the database, use for query directly.
|
ev
|
||||||
_ = client.database().save_event(&event).await;
|
}
|
||||||
|
Err(_) => continue,
|
||||||
// Send this event to the GPUI
|
}
|
||||||
if new_id == *subscription_id {
|
|
||||||
_ = event_tx.send(Signal::Event(event)).await;
|
|
||||||
}
|
}
|
||||||
|
Err(_) => continue,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// Send all pubkeys to the batch
|
let mut pubkeys = vec![];
|
||||||
_ = batch_tx.send(pubkeys).await;
|
pubkeys.extend(event.tags.public_keys());
|
||||||
}
|
pubkeys.push(event.pubkey);
|
||||||
|
|
||||||
|
// Send all pubkeys to the batch to sync metadata
|
||||||
|
batch_tx.send(pubkeys).await.ok();
|
||||||
|
|
||||||
|
// Save the event to the database, use for query directly.
|
||||||
|
client.database().save_event(&event).await.ok();
|
||||||
|
|
||||||
|
// Send this event to the GPUI
|
||||||
|
if new_id == *subscription_id {
|
||||||
|
event_tx.send(Signal::Event(event)).await.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Kind::ContactList => {
|
Kind::ContactList => {
|
||||||
let pubkeys =
|
if let Ok(signer) = client.signer().await {
|
||||||
event.tags.public_keys().copied().collect::<HashSet<_>>();
|
if let Ok(public_key) = signer.get_public_key().await {
|
||||||
|
if public_key == event.pubkey {
|
||||||
|
let pubkeys = event
|
||||||
|
.tags
|
||||||
|
.public_keys()
|
||||||
|
.copied()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
handle_metadata(pubkeys).await;
|
batch_tx.send(pubkeys).await.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RelayMessage::EndOfStoredEvents(subscription_id) => {
|
RelayMessage::EndOfStoredEvents(subscription_id) => {
|
||||||
if all_id == *subscription_id {
|
if all_id == *subscription_id {
|
||||||
_ = event_tx.send(Signal::Eose).await;
|
event_tx.send(Signal::Eose).await.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -221,7 +242,7 @@ fn main() {
|
|||||||
cx.update(|window, cx| {
|
cx.update(|window, cx| {
|
||||||
match signal {
|
match signal {
|
||||||
Signal::Eose => {
|
Signal::Eose => {
|
||||||
chats.update(cx, |this, cx| this.load_chat_rooms(window, cx));
|
chats.update(cx, |this, cx| this.load_rooms(window, cx));
|
||||||
}
|
}
|
||||||
Signal::Event(event) => {
|
Signal::Event(event) => {
|
||||||
chats.update(cx, |this, cx| {
|
chats.update(cx, |this, cx| {
|
||||||
@@ -242,17 +263,48 @@ fn main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_metadata(buffer: HashSet<PublicKey>) {
|
async fn set_unwrapped(root: EventId, event: &Event, keys: &Keys) -> Result<(), Error> {
|
||||||
let client = get_client();
|
let client = get_client();
|
||||||
|
let event = EventBuilder::new(Kind::Custom(9001), event.as_json())
|
||||||
|
.tags(vec![Tag::event(root)])
|
||||||
|
.sign(keys)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let opts = SubscribeAutoCloseOptions::default()
|
client.database().save_event(&event).await?;
|
||||||
.exit_policy(ReqExitPolicy::ExitOnEOSE)
|
|
||||||
.idle_timeout(Some(Duration::from_secs(1)));
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_unwrapped(gift_wrap: EventId) -> Result<Event, Error> {
|
||||||
|
let client = get_client();
|
||||||
|
let filter = Filter::new()
|
||||||
|
.kind(Kind::Custom(9001))
|
||||||
|
.event(gift_wrap)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if let Some(event) = client.database().query(filter).await?.first_owned() {
|
||||||
|
let parsed = Event::from_json(event.content)?;
|
||||||
|
Ok(parsed)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Event not found"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sync_metadata(buffer: HashSet<PublicKey>) {
|
||||||
|
let client = get_client();
|
||||||
|
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
|
|
||||||
|
let kinds = vec![
|
||||||
|
Kind::Metadata,
|
||||||
|
Kind::ContactList,
|
||||||
|
Kind::InboxRelays,
|
||||||
|
Kind::UserStatus,
|
||||||
|
];
|
||||||
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.authors(buffer.iter().cloned())
|
.authors(buffer.iter().cloned())
|
||||||
.limit(100)
|
.limit(buffer.len() * kinds.len())
|
||||||
.kinds(vec![Kind::Metadata, Kind::UserStatus]);
|
.kinds(kinds);
|
||||||
|
|
||||||
if let Err(e) = client
|
if let Err(e) = client
|
||||||
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
|
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
|
||||||
|
|||||||
@@ -467,7 +467,7 @@ impl Panel for Chat {
|
|||||||
.child(img(face).size_4())
|
.child(img(face).size_4())
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
.when_some(this.name(), |this, name| this.child(name))
|
.when_some(this.subject(), |this, name| this.child(name))
|
||||||
.into_any()
|
.into_any()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
use chats::{room::Room, ChatRegistry};
|
use chats::{
|
||||||
|
room::{Room, RoomKind},
|
||||||
|
ChatRegistry,
|
||||||
|
};
|
||||||
use common::{profile::NostrProfile, utils::random_name};
|
use common::{profile::NostrProfile, utils::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, Context, Entity, FocusHandle, InteractiveElement, IntoElement, ParentElement,
|
AppContext, ClickEvent, Context, Div, Entity, FocusHandle, InteractiveElement, IntoElement,
|
||||||
Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task, TextAlign,
|
ParentElement, Render, RenderOnce, SharedString, StatefulInteractiveElement, Styled,
|
||||||
Window,
|
Subscription, Task, TextAlign, Window,
|
||||||
};
|
};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use smol::Timer;
|
use smol::Timer;
|
||||||
use std::{collections::HashSet, time::Duration};
|
use std::{collections::HashSet, rc::Rc, time::Duration};
|
||||||
use ui::{
|
use ui::{
|
||||||
button::{Button, ButtonRounded},
|
button::{Button, ButtonRounded},
|
||||||
input::{InputEvent, TextInput},
|
input::{InputEvent, TextInput},
|
||||||
@@ -153,7 +156,7 @@ impl Compose {
|
|||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
// [IMPORTANT]
|
// [IMPORTANT]
|
||||||
// Make sure this event is never send,
|
// Make sure this event is never send,
|
||||||
// this event existed just use for convert to Coop's Chat Room later.
|
// this event existed just use for convert to Coop's Room later.
|
||||||
let event = EventBuilder::private_msg_rumor(*pubkeys.last().unwrap(), "")
|
let event = EventBuilder::private_msg_rumor(*pubkeys.last().unwrap(), "")
|
||||||
.tags(tags)
|
.tags(tags)
|
||||||
.sign(&signer)
|
.sign(&signer)
|
||||||
@@ -165,17 +168,16 @@ impl Compose {
|
|||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
if let Ok(event) = event.await {
|
if let Ok(event) = event.await {
|
||||||
cx.update(|window, cx| {
|
cx.update(|window, cx| {
|
||||||
// Stop loading spinner
|
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.set_submitting(false, cx);
|
this.set_submitting(false, cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
let chats = ChatRegistry::global(cx);
|
let chats = ChatRegistry::global(cx);
|
||||||
let room = Room::new(&event, cx);
|
let room = Room::new(&event, RoomKind::Ongoing);
|
||||||
|
|
||||||
chats.update(cx, |state, cx| {
|
chats.update(cx, |chats, cx| {
|
||||||
match state.push_room(room, cx) {
|
match chats.push(room, cx) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
// TODO: automatically open newly created chat panel
|
// TODO: automatically open newly created chat panel
|
||||||
window.close_modal(cx);
|
window.close_modal(cx);
|
||||||
@@ -500,3 +502,64 @@ impl Render for Compose {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
div()
|
||||||
|
.size_6()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.justify_center()
|
||||||
|
.rounded_full()
|
||||||
|
.bg(cx.theme().accent.step(cx, ColorScaleStep::NINE))
|
||||||
|
.child(
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
323
crates/app/src/views/sidebar/folder.rs
Normal file
323
crates/app/src/views/sidebar/folder.rs
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use gpui::{
|
||||||
|
div, prelude::FluentBuilder, px, App, ClickEvent, Img, InteractiveElement, IntoElement,
|
||||||
|
ParentElement as _, RenderOnce, SharedString, StatefulInteractiveElement, Styled as _, Window,
|
||||||
|
};
|
||||||
|
use ui::{
|
||||||
|
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||||
|
Collapsible, Icon, IconName, StyledExt,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Handler = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>;
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct Parent {
|
||||||
|
icon: Option<Icon>,
|
||||||
|
active_icon: Option<Icon>,
|
||||||
|
label: SharedString,
|
||||||
|
items: Vec<Folder>,
|
||||||
|
collapsed: bool,
|
||||||
|
handler: Handler,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parent {
|
||||||
|
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||||
|
Self {
|
||||||
|
label: label.into(),
|
||||||
|
icon: None,
|
||||||
|
active_icon: None,
|
||||||
|
items: Vec::new(),
|
||||||
|
collapsed: false,
|
||||||
|
handler: Rc::new(|_, _, _| {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
|
||||||
|
self.icon = Some(icon.into());
|
||||||
|
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 {
|
||||||
|
self.collapsed = collapsed;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn child(mut self, child: impl Into<Folder>) -> Self {
|
||||||
|
self.items.push(child.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn children(mut self, children: impl IntoIterator<Item = impl Into<Folder>>) -> Self {
|
||||||
|
self.items = children.into_iter().map(Into::into).collect();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_click(
|
||||||
|
mut self,
|
||||||
|
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||||
|
) -> Self {
|
||||||
|
self.handler = Rc::new(handler);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Collapsible for Parent {
|
||||||
|
fn is_collapsed(&self) -> bool {
|
||||||
|
self.collapsed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collapsed(mut self, collapsed: bool) -> Self {
|
||||||
|
self.collapsed = collapsed;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for Parent {
|
||||||
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
let handler = self.handler.clone();
|
||||||
|
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.id(self.label.clone())
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_2()
|
||||||
|
.px_2()
|
||||||
|
.h_6()
|
||||||
|
.rounded(px(cx.theme().radius))
|
||||||
|
.text_xs()
|
||||||
|
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
||||||
|
.font_semibold()
|
||||||
|
.when_some(self.icon, |this, icon| {
|
||||||
|
this.map(|this| {
|
||||||
|
if self.collapsed {
|
||||||
|
this.child(icon.size_4())
|
||||||
|
} else {
|
||||||
|
this.when_some(self.active_icon, |this, icon| {
|
||||||
|
this.child(icon.size_4())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.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)),
|
||||||
|
)
|
||||||
|
.when(!self.collapsed, |this| {
|
||||||
|
this.child(div().flex().flex_col().gap_1().pl_3().children(self.items))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct Folder {
|
||||||
|
icon: Option<Icon>,
|
||||||
|
active_icon: Option<Icon>,
|
||||||
|
label: SharedString,
|
||||||
|
items: Vec<FolderItem>,
|
||||||
|
collapsed: bool,
|
||||||
|
handler: Handler,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Folder {
|
||||||
|
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||||
|
Self {
|
||||||
|
label: label.into(),
|
||||||
|
icon: None,
|
||||||
|
active_icon: None,
|
||||||
|
items: Vec::new(),
|
||||||
|
collapsed: false,
|
||||||
|
handler: Rc::new(|_, _, _| {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
|
||||||
|
self.icon = Some(icon.into());
|
||||||
|
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 {
|
||||||
|
self.collapsed = collapsed;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn children(mut self, children: impl IntoIterator<Item = impl Into<FolderItem>>) -> Self {
|
||||||
|
self.items = children.into_iter().map(Into::into).collect();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_click(
|
||||||
|
mut self,
|
||||||
|
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||||
|
) -> Self {
|
||||||
|
self.handler = Rc::new(handler);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Collapsible for Folder {
|
||||||
|
fn is_collapsed(&self) -> bool {
|
||||||
|
self.collapsed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collapsed(mut self, collapsed: bool) -> Self {
|
||||||
|
self.collapsed = collapsed;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for Folder {
|
||||||
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
let handler = self.handler.clone();
|
||||||
|
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.id(self.label.clone())
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_2()
|
||||||
|
.px_2()
|
||||||
|
.h_6()
|
||||||
|
.rounded(px(cx.theme().radius))
|
||||||
|
.text_xs()
|
||||||
|
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
||||||
|
.font_semibold()
|
||||||
|
.when_some(self.icon, |this, icon| {
|
||||||
|
this.map(|this| {
|
||||||
|
if self.collapsed {
|
||||||
|
this.child(icon.size_4())
|
||||||
|
} else {
|
||||||
|
this.when_some(self.active_icon, |this, icon| {
|
||||||
|
this.child(icon.size_4())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.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)),
|
||||||
|
)
|
||||||
|
.when(!self.collapsed, |this| {
|
||||||
|
this.child(div().flex().flex_col().gap_1().pl_6().children(self.items))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct FolderItem {
|
||||||
|
ix: usize,
|
||||||
|
img: Option<Img>,
|
||||||
|
label: Option<SharedString>,
|
||||||
|
description: Option<SharedString>,
|
||||||
|
handler: Handler,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FolderItem {
|
||||||
|
pub fn new(ix: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
ix,
|
||||||
|
img: None,
|
||||||
|
label: None,
|
||||||
|
description: None,
|
||||||
|
handler: Rc::new(|_, _, _| {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn label(mut self, label: impl Into<SharedString>) -> Self {
|
||||||
|
self.label = Some(label.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn description(mut self, description: impl Into<SharedString>) -> Self {
|
||||||
|
self.description = Some(description.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn img(mut self, img: Option<Img>) -> Self {
|
||||||
|
self.img = img;
|
||||||
|
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 FolderItem {
|
||||||
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
let handler = self.handler.clone();
|
||||||
|
|
||||||
|
div()
|
||||||
|
.id(self.ix)
|
||||||
|
.h_6()
|
||||||
|
.px_2()
|
||||||
|
.w_full()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.justify_between()
|
||||||
|
.text_xs()
|
||||||
|
.rounded(px(cx.theme().radius))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex_1()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_2()
|
||||||
|
.truncate()
|
||||||
|
.font_medium()
|
||||||
|
.map(|this| {
|
||||||
|
if let Some(img) = self.img {
|
||||||
|
this.child(img.size_4().flex_shrink_0())
|
||||||
|
} else {
|
||||||
|
this.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.justify_center()
|
||||||
|
.items_center()
|
||||||
|
.size_4()
|
||||||
|
.rounded_full()
|
||||||
|
.bg(cx.theme().accent.step(cx, ColorScaleStep::THREE))
|
||||||
|
.child(Icon::new(IconName::GroupFill).size_2().text_color(
|
||||||
|
cx.theme().accent.step(cx, ColorScaleStep::TWELVE),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.when_some(self.label, |this, label| this.child(label)),
|
||||||
|
)
|
||||||
|
.when_some(self.description, |this, description| {
|
||||||
|
this.child(
|
||||||
|
div()
|
||||||
|
.flex_shrink_0()
|
||||||
|
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
||||||
|
.child(description),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.hover(|this| this.bg(cx.theme().base.step(cx, ColorScaleStep::FOUR)))
|
||||||
|
.on_click(move |ev, window, cx| handler(ev, window, cx))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,28 @@
|
|||||||
use chats::{room::Room, ChatRegistry};
|
use chats::{
|
||||||
use compose::Compose;
|
room::{Room, RoomKind},
|
||||||
|
ChatRegistry,
|
||||||
|
};
|
||||||
|
use compose::{Compose, ComposeButton};
|
||||||
|
use folder::{Folder, FolderItem, Parent};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, img, percentage, prelude::FluentBuilder, px, relative, uniform_list, AnyElement, App,
|
div, img, prelude::FluentBuilder, px, AnyElement, App, AppContext, Context, Entity,
|
||||||
AppContext, Context, Div, Empty, Entity, EventEmitter, FocusHandle, Focusable,
|
EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement, Render, SharedString, Styled,
|
||||||
InteractiveElement, IntoElement, ParentElement, Render, SharedString, Stateful,
|
Window,
|
||||||
StatefulInteractiveElement, Styled, Window,
|
|
||||||
};
|
};
|
||||||
use ui::{
|
use ui::{
|
||||||
button::{Button, ButtonRounded, ButtonVariants},
|
button::{Button, ButtonRounded, ButtonVariants},
|
||||||
dock_area::panel::{Panel, PanelEvent},
|
dock_area::panel::{Panel, PanelEvent},
|
||||||
popup_menu::PopupMenu,
|
popup_menu::PopupMenu,
|
||||||
|
scroll::ScrollbarAxis,
|
||||||
skeleton::Skeleton,
|
skeleton::Skeleton,
|
||||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||||
ContextModal, Disableable, Icon, IconName, Sizable, StyledExt,
|
ContextModal, Disableable, IconName, StyledExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::chat_space::{AddPanel, PanelKind};
|
use crate::chat_space::{AddPanel, PanelKind};
|
||||||
|
|
||||||
mod compose;
|
mod compose;
|
||||||
|
mod folder;
|
||||||
|
|
||||||
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)
|
||||||
@@ -26,8 +31,10 @@ pub fn init(window: &mut Window, cx: &mut App) -> Entity<Sidebar> {
|
|||||||
pub struct Sidebar {
|
pub struct Sidebar {
|
||||||
name: SharedString,
|
name: SharedString,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
label: SharedString,
|
ongoing: bool,
|
||||||
is_collapsed: bool,
|
incoming: bool,
|
||||||
|
trusted: bool,
|
||||||
|
unknown: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sidebar {
|
impl Sidebar {
|
||||||
@@ -37,13 +44,14 @@ impl Sidebar {
|
|||||||
|
|
||||||
fn view(_window: &mut Window, cx: &mut Context<Self>) -> Self {
|
fn view(_window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
let focus_handle = cx.focus_handle();
|
let focus_handle = cx.focus_handle();
|
||||||
let label = SharedString::from("Inbox");
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
name: "Sidebar".into(),
|
name: "Chat Sidebar".into(),
|
||||||
is_collapsed: false,
|
ongoing: false,
|
||||||
|
incoming: false,
|
||||||
|
trusted: true,
|
||||||
|
unknown: true,
|
||||||
focus_handle,
|
focus_handle,
|
||||||
label,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,63 +88,37 @@ impl Sidebar {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_room(&self, ix: usize, room: &Entity<Room>, cx: &Context<Self>) -> Stateful<Div> {
|
fn open_room(&self, id: u64, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let room = room.read(cx);
|
window.dispatch_action(
|
||||||
|
Box::new(AddPanel::new(
|
||||||
div()
|
PanelKind::Room(id),
|
||||||
.id(ix)
|
ui::dock_area::dock::DockPlacement::Center,
|
||||||
.px_1()
|
)),
|
||||||
.h_8()
|
cx,
|
||||||
.w_full()
|
);
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_between()
|
|
||||||
.text_xs()
|
|
||||||
.rounded(px(cx.theme().radius))
|
|
||||||
.hover(|this| this.bg(cx.theme().base.step(cx, ColorScaleStep::FOUR)))
|
|
||||||
.child(div().flex_1().truncate().font_medium().map(|this| {
|
|
||||||
if room.is_group() {
|
|
||||||
this.flex()
|
|
||||||
.items_center()
|
|
||||||
.gap_2()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.justify_center()
|
|
||||||
.items_center()
|
|
||||||
.size_6()
|
|
||||||
.rounded_full()
|
|
||||||
.bg(cx.theme().accent.step(cx, ColorScaleStep::THREE))
|
|
||||||
.child(Icon::new(IconName::GroupFill).size_3().text_color(
|
|
||||||
cx.theme().accent.step(cx, ColorScaleStep::TWELVE),
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.when_some(room.name(), |this, name| this.child(name))
|
|
||||||
} else {
|
|
||||||
this.when_some(room.first_member(), |this, member| {
|
|
||||||
this.flex()
|
|
||||||
.items_center()
|
|
||||||
.gap_2()
|
|
||||||
.child(img(member.avatar.clone()).size_6().flex_shrink_0())
|
|
||||||
.child(member.name.clone())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.flex_shrink_0()
|
|
||||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
|
||||||
.child(room.ago()),
|
|
||||||
)
|
|
||||||
.on_click({
|
|
||||||
let id = room.id;
|
|
||||||
|
|
||||||
cx.listener(move |this, _, window, cx| {
|
|
||||||
this.open(id, window, cx);
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ongoing(&mut self, cx: &mut Context<Self>) {
|
||||||
|
self.ongoing = !self.ongoing;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn incoming(&mut self, cx: &mut Context<Self>) {
|
||||||
|
self.incoming = !self.incoming;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trusted(&mut self, cx: &mut Context<Self>) {
|
||||||
|
self.trusted = !self.trusted;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unknown(&mut self, cx: &mut Context<Self>) {
|
||||||
|
self.unknown = !self.unknown;
|
||||||
|
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()
|
||||||
@@ -151,20 +133,49 @@ impl Sidebar {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(&self, id: u64, window: &mut Window, cx: &mut Context<Self>) {
|
fn render_items(rooms: &Vec<&Entity<Room>>, cx: &Context<Self>) -> Vec<FolderItem> {
|
||||||
window.dispatch_action(
|
let mut items = Vec::with_capacity(rooms.len());
|
||||||
Box::new(AddPanel::new(
|
|
||||||
PanelKind::Room(id),
|
for room in rooms {
|
||||||
ui::dock_area::dock::DockPlacement::Center,
|
let room = room.read(cx);
|
||||||
)),
|
let room_id = room.id;
|
||||||
cx,
|
let ago = room.last_seen().ago();
|
||||||
);
|
let Some(member) = room.first_member() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let label = if room.is_group() {
|
||||||
|
room.subject().unwrap_or("Unnamed".into())
|
||||||
|
} else {
|
||||||
|
member.name.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let img = if !room.is_group() {
|
||||||
|
Some(img(member.avatar.clone()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let item = FolderItem::new(room_id as usize)
|
||||||
|
.label(label)
|
||||||
|
.description(ago)
|
||||||
|
.img(img)
|
||||||
|
.on_click({
|
||||||
|
cx.listener(move |this, _, window, cx| {
|
||||||
|
this.open_room(room_id, window, cx);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
items.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
items
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Panel for Sidebar {
|
impl Panel for Sidebar {
|
||||||
fn panel_id(&self) -> SharedString {
|
fn panel_id(&self) -> SharedString {
|
||||||
"Sidebar".into()
|
self.name.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title(&self, _cx: &App) -> AnyElement {
|
fn title(&self, _cx: &App) -> AnyElement {
|
||||||
@@ -190,150 +201,77 @@ 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 entity = cx.entity();
|
let registry = ChatRegistry::global(cx).read(cx);
|
||||||
|
let rooms = registry.rooms(cx);
|
||||||
|
let loading = registry.loading();
|
||||||
|
|
||||||
|
let ongoing = rooms.get(&RoomKind::Ongoing);
|
||||||
|
let trusted = rooms.get(&RoomKind::Trusted);
|
||||||
|
let unknown = rooms.get(&RoomKind::Unknown);
|
||||||
|
|
||||||
div()
|
div()
|
||||||
|
.scrollable(cx.entity_id(), ScrollbarAxis::Vertical)
|
||||||
|
.size_full()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.size_full()
|
.gap_3()
|
||||||
.child(
|
.px_2()
|
||||||
div()
|
.py_3()
|
||||||
.px_2()
|
.child(ComposeButton::new("New Message").on_click(cx.listener(
|
||||||
.py_3()
|
|this, _, window, cx| {
|
||||||
.w_full()
|
this.render_compose(window, cx);
|
||||||
.flex_shrink_0()
|
},
|
||||||
.flex()
|
)))
|
||||||
.flex_col()
|
.map(|this| {
|
||||||
.gap_1()
|
if loading {
|
||||||
|
this.children(self.render_skeleton(6))
|
||||||
|
} else {
|
||||||
|
this.when_some(ongoing, |this, rooms| {
|
||||||
|
this.child(
|
||||||
|
Folder::new("Ongoing")
|
||||||
|
.icon(IconName::FolderFill)
|
||||||
|
.active_icon(IconName::FolderOpenFill)
|
||||||
|
.collapsed(self.ongoing)
|
||||||
|
.on_click(cx.listener(move |this, _, _, cx| {
|
||||||
|
this.ongoing(cx);
|
||||||
|
}))
|
||||||
|
.children(Self::render_items(rooms, cx)),
|
||||||
|
)
|
||||||
|
})
|
||||||
.child(
|
.child(
|
||||||
div()
|
Parent::new("Incoming")
|
||||||
.id("new_message")
|
.icon(IconName::FolderFill)
|
||||||
.flex()
|
.active_icon(IconName::FolderOpenFill)
|
||||||
.items_center()
|
.collapsed(self.incoming)
|
||||||
.gap_2()
|
.on_click(cx.listener(move |this, _, _, cx| {
|
||||||
.px_1()
|
this.incoming(cx);
|
||||||
.h_7()
|
}))
|
||||||
.text_xs()
|
.when_some(trusted, |this, rooms| {
|
||||||
.font_semibold()
|
|
||||||
.rounded(px(cx.theme().radius))
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.size_6()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_center()
|
|
||||||
.rounded_full()
|
|
||||||
.bg(cx.theme().accent.step(cx, ColorScaleStep::NINE))
|
|
||||||
.child(
|
|
||||||
Icon::new(IconName::ComposeFill)
|
|
||||||
.small()
|
|
||||||
.text_color(cx.theme().base.darken(cx)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child("New Message")
|
|
||||||
.hover(|this| this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE)))
|
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
|
||||||
// Open compose modal
|
|
||||||
this.render_compose(window, cx);
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.child(Empty),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.px_2()
|
|
||||||
.w_full()
|
|
||||||
.flex_1()
|
|
||||||
.flex()
|
|
||||||
.flex_col()
|
|
||||||
.gap_1()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.id("inbox_header")
|
|
||||||
.px_1()
|
|
||||||
.h_7()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.flex_shrink_0()
|
|
||||||
.rounded(px(cx.theme().radius))
|
|
||||||
.text_xs()
|
|
||||||
.font_semibold()
|
|
||||||
.child(
|
|
||||||
Icon::new(IconName::ChevronDown)
|
|
||||||
.size_6()
|
|
||||||
.when(self.is_collapsed, |this| {
|
|
||||||
this.rotate(percentage(270. / 360.))
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(self.label.clone())
|
|
||||||
.hover(|this| this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE)))
|
|
||||||
.on_click(cx.listener(move |view, _event, _window, cx| {
|
|
||||||
view.is_collapsed = !view.is_collapsed;
|
|
||||||
cx.notify();
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.when(!self.is_collapsed, |this| {
|
|
||||||
this.flex_1().w_full().map(|this| {
|
|
||||||
let state = ChatRegistry::global(cx);
|
|
||||||
let is_loading = state.read(cx).is_loading();
|
|
||||||
let len = state.read(cx).rooms().len();
|
|
||||||
|
|
||||||
if is_loading {
|
|
||||||
this.children(self.render_skeleton(5))
|
|
||||||
} else if state.read(cx).rooms().is_empty() {
|
|
||||||
this.child(
|
this.child(
|
||||||
div()
|
Folder::new("Trusted")
|
||||||
.px_1()
|
.icon(IconName::FolderFill)
|
||||||
.w_full()
|
.active_icon(IconName::FolderOpenFill)
|
||||||
.h_20()
|
.collapsed(self.trusted)
|
||||||
.flex()
|
.on_click(cx.listener(move |this, _, _, cx| {
|
||||||
.flex_col()
|
this.trusted(cx);
|
||||||
.items_center()
|
}))
|
||||||
.justify_center()
|
.children(Self::render_items(rooms, cx)),
|
||||||
.text_center()
|
|
||||||
.rounded(px(cx.theme().radius))
|
|
||||||
.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.text_xs()
|
|
||||||
.font_semibold()
|
|
||||||
.line_height(relative(1.2))
|
|
||||||
.child("No chats"),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.text_xs()
|
|
||||||
.text_color(
|
|
||||||
cx.theme()
|
|
||||||
.base
|
|
||||||
.step(cx, ColorScaleStep::ELEVEN),
|
|
||||||
)
|
|
||||||
.child("Recent chats will appear here."),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
} else {
|
})
|
||||||
|
.when_some(unknown, |this, rooms| {
|
||||||
this.child(
|
this.child(
|
||||||
uniform_list(
|
Folder::new("Unknown")
|
||||||
entity,
|
.icon(IconName::FolderFill)
|
||||||
"rooms",
|
.active_icon(IconName::FolderOpenFill)
|
||||||
len,
|
.collapsed(self.unknown)
|
||||||
move |this, range, _, cx| {
|
.on_click(cx.listener(move |this, _, _, cx| {
|
||||||
let mut items = vec![];
|
this.unknown(cx);
|
||||||
|
}))
|
||||||
for ix in range {
|
.children(Self::render_items(rooms, cx)),
|
||||||
if let Some(room) = state.read(cx).rooms().get(ix) {
|
|
||||||
items.push(this.render_room(ix, room, cx));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
items
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.size_full(),
|
|
||||||
)
|
)
|
||||||
}
|
}),
|
||||||
})
|
)
|
||||||
}),
|
}
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
use std::cmp::Reverse;
|
use std::{cmp::Reverse, collections::HashMap};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use common::{last_seen::LastSeen, utils::room_hash};
|
use common::{last_seen::LastSeen, utils::room_hash};
|
||||||
use global::get_client;
|
use global::get_client;
|
||||||
use gpui::{App, AppContext, Context, Entity, Global, Task, Window};
|
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task, Window};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
use room::RoomKind;
|
||||||
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
use crate::room::Room;
|
use crate::room::Room;
|
||||||
|
|
||||||
@@ -13,7 +15,7 @@ pub mod message;
|
|||||||
pub mod room;
|
pub mod room;
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
ChatRegistry::set_global(cx.new(|_| ChatRegistry::new()), cx);
|
ChatRegistry::set_global(cx.new(ChatRegistry::new), cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GlobalChatRegistry(Entity<ChatRegistry>);
|
struct GlobalChatRegistry(Entity<ChatRegistry>);
|
||||||
@@ -22,7 +24,9 @@ impl Global for GlobalChatRegistry {}
|
|||||||
|
|
||||||
pub struct ChatRegistry {
|
pub struct ChatRegistry {
|
||||||
rooms: Vec<Entity<Room>>,
|
rooms: Vec<Entity<Room>>,
|
||||||
is_loading: bool,
|
loading: bool,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
subscriptions: SmallVec<[Subscription; 1]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChatRegistry {
|
impl ChatRegistry {
|
||||||
@@ -34,21 +38,40 @@ impl ChatRegistry {
|
|||||||
cx.set_global(GlobalChatRegistry(state));
|
cx.set_global(GlobalChatRegistry(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new() -> Self {
|
fn new(cx: &mut Context<Self>) -> Self {
|
||||||
|
let mut subscriptions = smallvec![];
|
||||||
|
|
||||||
|
subscriptions.push(cx.observe_new::<Room>(|this, _, cx| {
|
||||||
|
let load_metadata = this.load_metadata(cx);
|
||||||
|
|
||||||
|
cx.spawn(async move |this, cx| {
|
||||||
|
if let Ok(profiles) = load_metadata.await {
|
||||||
|
cx.update(|cx| {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.update_members(profiles, cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
rooms: vec![],
|
rooms: vec![],
|
||||||
is_loading: true,
|
loading: true,
|
||||||
|
subscriptions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn current_rooms_ids(&self, cx: &mut Context<Self>) -> Vec<u64> {
|
pub fn load_rooms(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.rooms.iter().map(|room| room.read(cx).id).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_chat_rooms(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
let client = get_client();
|
let client = get_client();
|
||||||
|
let room_ids = self.room_ids(cx);
|
||||||
|
|
||||||
let task: Task<Result<Vec<Event>, Error>> = cx.background_spawn(async move {
|
type LoadResult = Result<Vec<(Event, usize, bool)>, Error>;
|
||||||
|
|
||||||
|
let task: Task<LoadResult> = cx.background_spawn(async move {
|
||||||
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?;
|
||||||
|
|
||||||
@@ -64,11 +87,31 @@ impl ChatRegistry {
|
|||||||
let recv_events = client.database().query(recv).await?;
|
let recv_events = client.database().query(recv).await?;
|
||||||
let events = send_events.merge(recv_events);
|
let events = send_events.merge(recv_events);
|
||||||
|
|
||||||
let result: Vec<Event> = events
|
let mut room_map: HashMap<u64, (Event, usize, bool)> = HashMap::new();
|
||||||
|
|
||||||
|
for event in events
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|ev| ev.tags.public_keys().peekable().peek().is_some())
|
.filter(|ev| ev.tags.public_keys().peekable().peek().is_some())
|
||||||
.unique_by(room_hash)
|
{
|
||||||
.sorted_by_key(|ev| Reverse(ev.created_at))
|
let hash = room_hash(&event);
|
||||||
|
|
||||||
|
if !room_ids.iter().any(|id| id == &hash) {
|
||||||
|
let filter = Filter::new().kind(Kind::ContactList).pubkey(event.pubkey);
|
||||||
|
let is_trust = client.database().count(filter).await? >= 1;
|
||||||
|
|
||||||
|
room_map
|
||||||
|
.entry(hash)
|
||||||
|
.and_modify(|(_, count, trusted)| {
|
||||||
|
*count += 1;
|
||||||
|
*trusted = is_trust;
|
||||||
|
})
|
||||||
|
.or_insert((event, 1, is_trust));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: Vec<(Event, usize, bool)> = room_map
|
||||||
|
.into_values()
|
||||||
|
.sorted_by_key(|(ev, _, _)| Reverse(ev.created_at))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
@@ -76,30 +119,27 @@ impl ChatRegistry {
|
|||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
if let Ok(events) = task.await {
|
if let Ok(events) = task.await {
|
||||||
|
let rooms: Vec<Entity<Room>> = events
|
||||||
|
.into_iter()
|
||||||
|
.map(|(event, count, trusted)| {
|
||||||
|
let kind = if count > 2 {
|
||||||
|
// If frequency count is greater than 2, mark this room as ongoing
|
||||||
|
RoomKind::Ongoing
|
||||||
|
} else if trusted {
|
||||||
|
RoomKind::Trusted
|
||||||
|
} else {
|
||||||
|
RoomKind::Unknown
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.new(|_| Room::new(&event, kind)).unwrap()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
cx.update(|_, cx| {
|
cx.update(|_, cx| {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
if !events.is_empty() {
|
this.rooms.extend(rooms);
|
||||||
let current_ids = this.current_rooms_ids(cx);
|
this.rooms.sort_by_key(|r| Reverse(r.read(cx).last_seen()));
|
||||||
let items: Vec<Entity<Room>> = events
|
this.loading = false;
|
||||||
.into_iter()
|
|
||||||
.filter_map(|ev| {
|
|
||||||
let new = room_hash(&ev);
|
|
||||||
// Filter all seen rooms
|
|
||||||
if !current_ids.iter().any(|this| this == &new) {
|
|
||||||
Some(Room::new(&ev, cx))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
this.is_loading = false;
|
|
||||||
this.rooms.extend(items);
|
|
||||||
this.rooms
|
|
||||||
.sort_by_key(|room| Reverse(room.read(cx).last_seen()));
|
|
||||||
} else {
|
|
||||||
this.is_loading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
@@ -111,14 +151,40 @@ impl ChatRegistry {
|
|||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rooms(&self) -> &[Entity<Room>] {
|
/// Get the IDs of all rooms.
|
||||||
&self.rooms
|
pub fn room_ids(&self, cx: &mut Context<Self>) -> Vec<u64> {
|
||||||
|
self.rooms.iter().map(|room| room.read(cx).id).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_loading(&self) -> bool {
|
/// Get all rooms.
|
||||||
self.is_loading
|
pub fn rooms(&self, cx: &App) -> HashMap<RoomKind, Vec<&Entity<Room>>> {
|
||||||
|
let mut groups = HashMap::new();
|
||||||
|
groups.insert(RoomKind::Ongoing, Vec::new());
|
||||||
|
groups.insert(RoomKind::Trusted, Vec::new());
|
||||||
|
groups.insert(RoomKind::Unknown, Vec::new());
|
||||||
|
|
||||||
|
for room in self.rooms.iter() {
|
||||||
|
let kind = room.read(cx).kind();
|
||||||
|
groups.entry(kind).or_insert_with(Vec::new).push(room);
|
||||||
|
}
|
||||||
|
|
||||||
|
groups
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get rooms by their kind.
|
||||||
|
pub fn rooms_by_kind(&self, kind: RoomKind, cx: &App) -> Vec<&Entity<Room>> {
|
||||||
|
self.rooms
|
||||||
|
.iter()
|
||||||
|
.filter(|room| room.read(cx).kind() == kind)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the loading status of the rooms.
|
||||||
|
pub fn loading(&self) -> bool {
|
||||||
|
self.loading
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a room by its ID.
|
||||||
pub fn get(&self, id: &u64, cx: &App) -> Option<Entity<Room>> {
|
pub fn get(&self, id: &u64, cx: &App) -> Option<Entity<Room>> {
|
||||||
self.rooms
|
self.rooms
|
||||||
.iter()
|
.iter()
|
||||||
@@ -126,11 +192,10 @@ impl ChatRegistry {
|
|||||||
.cloned()
|
.cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_room(
|
/// Push a room to the list.
|
||||||
&mut self,
|
pub fn push(&mut self, room: Room, cx: &mut Context<Self>) -> Result<(), anyhow::Error> {
|
||||||
room: Entity<Room>,
|
let room = cx.new(|_| room);
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Result<(), anyhow::Error> {
|
|
||||||
if !self
|
if !self
|
||||||
.rooms
|
.rooms
|
||||||
.iter()
|
.iter()
|
||||||
@@ -145,6 +210,7 @@ impl ChatRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Push a message to a room.
|
||||||
pub fn push_message(&mut self, event: Event, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn push_message(&mut self, event: Event, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let id = room_hash(&event);
|
let id = room_hash(&event);
|
||||||
|
|
||||||
@@ -158,7 +224,7 @@ impl ChatRegistry {
|
|||||||
self.rooms
|
self.rooms
|
||||||
.sort_by_key(|room| Reverse(room.read(cx).last_seen()));
|
.sort_by_key(|room| Reverse(room.read(cx).last_seen()));
|
||||||
} else {
|
} else {
|
||||||
let new_room = Room::new(&event, cx);
|
let new_room = cx.new(|_| Room::new(&event, RoomKind::default()));
|
||||||
|
|
||||||
// Push the new room to the front of the list
|
// Push the new room to the front of the list
|
||||||
self.rooms.insert(0, new_room);
|
self.rooms.insert(0, new_room);
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ use common::{
|
|||||||
utils::{compare, room_hash},
|
utils::{compare, room_hash},
|
||||||
};
|
};
|
||||||
use global::get_client;
|
use global::get_client;
|
||||||
use gpui::{App, AppContext, Context, Entity, EventEmitter, SharedString, Task, Window};
|
use gpui::{App, AppContext, Context, EventEmitter, SharedString, Task, Window};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::message::{Message, RoomMessage};
|
use crate::message::{Message, RoomMessage};
|
||||||
|
|
||||||
@@ -19,13 +19,25 @@ pub struct IncomingEvent {
|
|||||||
pub event: RoomMessage,
|
pub event: RoomMessage,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Hash, Debug, PartialEq, Eq, Default)]
|
||||||
|
pub enum RoomKind {
|
||||||
|
Ongoing,
|
||||||
|
Trusted,
|
||||||
|
#[default]
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Room {
|
pub struct Room {
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
pub last_seen: LastSeen,
|
pub last_seen: LastSeen,
|
||||||
/// Subject of the room
|
/// Subject of the room
|
||||||
pub name: Option<SharedString>,
|
pub subject: Option<SharedString>,
|
||||||
/// All members of the room
|
/// All members of the room
|
||||||
pub members: Arc<SmallVec<[NostrProfile; 2]>>,
|
pub members: Arc<SmallVec<[NostrProfile; 2]>>,
|
||||||
|
/// Kind
|
||||||
|
pub kind: RoomKind,
|
||||||
|
/// All public keys of the room members
|
||||||
|
pubkeys: Vec<PublicKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<IncomingEvent> for Room {}
|
impl EventEmitter<IncomingEvent> for Room {}
|
||||||
@@ -37,66 +49,34 @@ impl PartialEq for Room {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Room {
|
impl Room {
|
||||||
pub fn new(event: &Event, cx: &mut App) -> Entity<Self> {
|
/// Create a new room from an Nostr Event
|
||||||
|
pub fn new(event: &Event, kind: RoomKind) -> Self {
|
||||||
let id = room_hash(event);
|
let id = room_hash(event);
|
||||||
let last_seen = LastSeen(event.created_at);
|
let last_seen = LastSeen(event.created_at);
|
||||||
|
|
||||||
// Get the subject from the event's tags
|
// Get the subject from the event's tags
|
||||||
let name = if let Some(tag) = event.tags.find(TagKind::Subject) {
|
let subject = if let Some(tag) = event.tags.find(TagKind::Subject) {
|
||||||
tag.content().map(|s| s.to_owned().into())
|
tag.content().map(|s| s.to_owned().into())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a task for loading metadata
|
// Get all public keys from the event's tags
|
||||||
let load_metadata = Self::load_metadata(event, cx);
|
let mut pubkeys = vec![];
|
||||||
|
pubkeys.extend(event.tags.public_keys().collect::<HashSet<_>>());
|
||||||
|
pubkeys.push(event.pubkey);
|
||||||
|
|
||||||
// Create a new GPUI's Entity
|
Self {
|
||||||
cx.new(|cx| {
|
id,
|
||||||
let this = Self {
|
last_seen,
|
||||||
id,
|
subject,
|
||||||
last_seen,
|
kind,
|
||||||
name,
|
members: Arc::new(SmallVec::with_capacity(pubkeys.len())),
|
||||||
members: Arc::new(smallvec![]),
|
pubkeys,
|
||||||
};
|
}
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
|
||||||
if let Ok(profiles) = load_metadata.await {
|
|
||||||
cx.update(|cx| {
|
|
||||||
this.update(cx, |this: &mut Room, cx| {
|
|
||||||
// Update the room's name if it's not already set
|
|
||||||
if this.name.is_none() {
|
|
||||||
let mut name = profiles
|
|
||||||
.iter()
|
|
||||||
.take(2)
|
|
||||||
.map(|profile| profile.name.to_string())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", ");
|
|
||||||
|
|
||||||
if profiles.len() > 2 {
|
|
||||||
name = format!("{}, +{}", name, profiles.len() - 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.name = Some(name.into())
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut new_members = SmallVec::new();
|
|
||||||
new_members.extend(profiles);
|
|
||||||
this.members = Arc::new(new_members);
|
|
||||||
|
|
||||||
cx.notify();
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
this
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get room's id
|
||||||
pub fn id(&self) -> u64 {
|
pub fn id(&self) -> u64 {
|
||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
@@ -116,12 +96,17 @@ impl Room {
|
|||||||
|
|
||||||
/// Collect room's member's public keys
|
/// Collect room's member's public keys
|
||||||
pub fn public_keys(&self) -> Vec<PublicKey> {
|
pub fn public_keys(&self) -> Vec<PublicKey> {
|
||||||
self.members.iter().map(|m| m.public_key).collect()
|
self.pubkeys.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get room's display name
|
/// Get room's display name
|
||||||
pub fn name(&self) -> Option<SharedString> {
|
pub fn subject(&self) -> Option<SharedString> {
|
||||||
self.name.clone()
|
self.subject.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get room's kind
|
||||||
|
pub fn kind(&self) -> RoomKind {
|
||||||
|
self.kind
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine if room is a group
|
/// Determine if room is a group
|
||||||
@@ -145,6 +130,31 @@ impl Room {
|
|||||||
self.last_seen.ago()
|
self.last_seen.ago()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_members(&mut self, profiles: Vec<NostrProfile>, cx: &mut Context<Self>) {
|
||||||
|
// Update the room's name if it's not already set
|
||||||
|
if self.subject.is_none() {
|
||||||
|
// Merge all members into a single name
|
||||||
|
let mut name = profiles
|
||||||
|
.iter()
|
||||||
|
.take(2)
|
||||||
|
.map(|profile| profile.name.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
// Create a specific name for group
|
||||||
|
if profiles.len() > 2 {
|
||||||
|
name = format!("{}, +{}", name, profiles.len() - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.subject = Some(name.into());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update the room's members
|
||||||
|
self.members = Arc::new(profiles.into());
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
/// Verify messaging_relays for all room's members
|
/// Verify messaging_relays for all room's members
|
||||||
pub fn messaging_relays(&self, cx: &App) -> Task<Result<Vec<(PublicKey, bool)>, Error>> {
|
pub fn messaging_relays(&self, cx: &App) -> Task<Result<Vec<(PublicKey, bool)>, Error>> {
|
||||||
let client = get_client();
|
let client = get_client();
|
||||||
@@ -208,6 +218,38 @@ impl Room {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Load metadata for all members
|
||||||
|
pub fn load_metadata(&self, cx: &mut Context<Self>) -> Task<Result<Vec<NostrProfile>, Error>> {
|
||||||
|
let client = get_client();
|
||||||
|
let pubkeys = self.public_keys();
|
||||||
|
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
let signer = client.signer().await?;
|
||||||
|
let signer_pubkey = signer.get_public_key().await?;
|
||||||
|
let mut profiles = Vec::with_capacity(pubkeys.len());
|
||||||
|
|
||||||
|
for public_key in pubkeys.into_iter() {
|
||||||
|
let metadata = client
|
||||||
|
.database()
|
||||||
|
.metadata(public_key)
|
||||||
|
.await?
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// Convert metadata to profile
|
||||||
|
let profile = NostrProfile::new(public_key, metadata);
|
||||||
|
|
||||||
|
if public_key == signer_pubkey {
|
||||||
|
// Room's owner always push to the end of the vector
|
||||||
|
profiles.push(profile);
|
||||||
|
} else {
|
||||||
|
profiles.insert(0, profile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(profiles)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Load room messages
|
/// Load room messages
|
||||||
pub fn load_messages(&self, cx: &App) -> Task<Result<Vec<RoomMessage>, Error>> {
|
pub fn load_messages(&self, cx: &App) -> Task<Result<Vec<RoomMessage>, Error>> {
|
||||||
let client = get_client();
|
let client = get_client();
|
||||||
@@ -350,40 +392,4 @@ impl Room {
|
|||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load metadata for all members
|
|
||||||
fn load_metadata(event: &Event, cx: &App) -> Task<Result<Vec<NostrProfile>, Error>> {
|
|
||||||
let client = get_client();
|
|
||||||
let mut pubkeys = vec![];
|
|
||||||
|
|
||||||
// Get all pubkeys from event's tags
|
|
||||||
pubkeys.extend(event.tags.public_keys().collect::<HashSet<_>>());
|
|
||||||
pubkeys.push(event.pubkey);
|
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
|
||||||
let signer = client.signer().await?;
|
|
||||||
let signer_pubkey = signer.get_public_key().await?;
|
|
||||||
let mut profiles = Vec::with_capacity(pubkeys.len());
|
|
||||||
|
|
||||||
for public_key in pubkeys.into_iter() {
|
|
||||||
let metadata = client
|
|
||||||
.database()
|
|
||||||
.metadata(public_key)
|
|
||||||
.await?
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
// Convert metadata to profile
|
|
||||||
let profile = NostrProfile::new(public_key, metadata);
|
|
||||||
|
|
||||||
if public_key == signer_pubkey {
|
|
||||||
// Room's owner always push to the end of the vector
|
|
||||||
profiles.push(profile);
|
|
||||||
} else {
|
|
||||||
profiles.insert(0, profile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(profiles)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ pub const APP_ID: &str = "su.reya.coop";
|
|||||||
pub const BOOTSTRAP_RELAYS: [&str; 5] = [
|
pub const BOOTSTRAP_RELAYS: [&str; 5] = [
|
||||||
"wss://relay.damus.io",
|
"wss://relay.damus.io",
|
||||||
"wss://relay.primal.net",
|
"wss://relay.primal.net",
|
||||||
"wss://purplepag.es",
|
|
||||||
"wss://user.kindpag.es",
|
"wss://user.kindpag.es",
|
||||||
"wss://relaydiscovery.com",
|
"wss://relaydiscovery.com",
|
||||||
|
"wss://purplepag.es",
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Subscriptions
|
/// Subscriptions
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ pub enum IconName {
|
|||||||
Bell,
|
Bell,
|
||||||
BookOpen,
|
BookOpen,
|
||||||
Bot,
|
Bot,
|
||||||
|
BubbleFill,
|
||||||
Calendar,
|
Calendar,
|
||||||
ChartPie,
|
ChartPie,
|
||||||
Check,
|
Check,
|
||||||
@@ -42,6 +43,9 @@ pub enum IconName {
|
|||||||
Eye,
|
Eye,
|
||||||
EyeOff,
|
EyeOff,
|
||||||
Frame,
|
Frame,
|
||||||
|
Folder,
|
||||||
|
FolderFill,
|
||||||
|
FolderOpenFill,
|
||||||
GalleryVerticalEnd,
|
GalleryVerticalEnd,
|
||||||
GitHub,
|
GitHub,
|
||||||
Globe,
|
Globe,
|
||||||
@@ -104,6 +108,7 @@ impl IconName {
|
|||||||
Self::Bell => "icons/bell.svg",
|
Self::Bell => "icons/bell.svg",
|
||||||
Self::BookOpen => "icons/book-open.svg",
|
Self::BookOpen => "icons/book-open.svg",
|
||||||
Self::Bot => "icons/bot.svg",
|
Self::Bot => "icons/bot.svg",
|
||||||
|
Self::BubbleFill => "icons/bubble-fill.svg",
|
||||||
Self::Calendar => "icons/calendar.svg",
|
Self::Calendar => "icons/calendar.svg",
|
||||||
Self::ChartPie => "icons/chart-pie.svg",
|
Self::ChartPie => "icons/chart-pie.svg",
|
||||||
Self::Check => "icons/check.svg",
|
Self::Check => "icons/check.svg",
|
||||||
@@ -126,6 +131,9 @@ impl IconName {
|
|||||||
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::Frame => "icons/frame.svg",
|
||||||
|
Self::Folder => "icons/folder.svg",
|
||||||
|
Self::FolderFill => "icons/folder-fill.svg",
|
||||||
|
Self::FolderOpenFill => "icons/folder-open-fill.svg",
|
||||||
Self::GalleryVerticalEnd => "icons/gallery-vertical-end.svg",
|
Self::GalleryVerticalEnd => "icons/gallery-vertical-end.svg",
|
||||||
Self::GitHub => "icons/github.svg",
|
Self::GitHub => "icons/github.svg",
|
||||||
Self::Globe => "icons/globe.svg",
|
Self::Globe => "icons/globe.svg",
|
||||||
|
|||||||
@@ -209,8 +209,8 @@ where
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.into_any_element();
|
.into_any_element();
|
||||||
let element_id = element.request_layout(window, cx);
|
|
||||||
|
|
||||||
|
let element_id = element.request_layout(window, cx);
|
||||||
let layout_id = window.request_layout(style, vec![element_id], cx);
|
let layout_id = window.request_layout(style, vec![element_id], cx);
|
||||||
|
|
||||||
(layout_id, element)
|
(layout_id, element)
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ pub(crate) const WIDTH: Pixels = px(12.);
|
|||||||
const MIN_THUMB_SIZE: f32 = 80.;
|
const MIN_THUMB_SIZE: f32 = 80.;
|
||||||
const THUMB_RADIUS: Pixels = Pixels(4.0);
|
const THUMB_RADIUS: Pixels = Pixels(4.0);
|
||||||
const THUMB_INSET: Pixels = Pixels(3.);
|
const THUMB_INSET: Pixels = Pixels(3.);
|
||||||
const FADE_OUT_DURATION: f32 = 3.0;
|
const FADE_OUT_DURATION: f32 = 2.0;
|
||||||
const FADE_OUT_DELAY: f32 = 2.0;
|
const FADE_OUT_DELAY: f32 = 1.2;
|
||||||
|
|
||||||
pub trait ScrollHandleOffsetable {
|
pub trait ScrollHandleOffsetable {
|
||||||
fn offset(&self) -> Point<Pixels>;
|
fn offset(&self) -> Point<Pixels>;
|
||||||
|
|||||||
Reference in New Issue
Block a user