From 4e24061817c46fb1d78eb8faadcef1355d7a32e1 Mon Sep 17 00:00:00 2001
From: reya <123083837+reyamir@users.noreply.github.com>
Date: Mon, 12 May 2025 20:46:01 +0700
Subject: [PATCH] feat: Redesign New Chat (#31)
* make subject is optional
* redesign
* search
* fix
* adjust
---
Cargo.lock | 76 ++---
Cargo.toml | 2 +-
assets/icons/plus-fill.svg | 3 +
crates/account/src/lib.rs | 9 +
crates/chats/Cargo.toml | 2 +
crates/chats/src/lib.rs | 98 ++++--
crates/chats/src/room.rs | 43 +--
crates/common/Cargo.toml | 3 +-
crates/common/src/debounced_delay.rs | 55 +++
crates/common/src/lib.rs | 16 +-
crates/coop/Cargo.toml | 2 +-
crates/coop/src/chatspace.rs | 19 +-
crates/coop/src/views/chat.rs | 35 +-
crates/coop/src/views/compose.rs | 17 +-
crates/coop/src/views/mod.rs | 1 -
crates/coop/src/views/profile.rs | 2 +-
crates/coop/src/views/sidebar/button.rs | 58 ----
crates/coop/src/views/sidebar/folder.rs | 2 +-
crates/coop/src/views/sidebar/mod.rs | 436 ++++++++++++++++++------
crates/coop/src/views/subject.rs | 5 -
crates/theme/src/colors.rs | 2 +
crates/theme/src/lib.rs | 6 +-
crates/theme/src/scale.rs | 2 +
crates/ui/src/button.rs | 12 +-
crates/ui/src/emoji_picker.rs | 15 +-
crates/ui/src/icon.rs | 2 +
crates/ui/src/input/input.rs | 6 +-
crates/ui/src/list/list.rs | 4 +-
crates/ui/src/list/list_item.rs | 4 +-
crates/ui/src/popup_menu.rs | 2 +-
crates/ui/src/skeleton.rs | 2 +-
crates/ui/src/tooltip.rs | 6 +-
32 files changed, 580 insertions(+), 367 deletions(-)
create mode 100644 assets/icons/plus-fill.svg
create mode 100644 crates/common/src/debounced_delay.rs
delete mode 100644 crates/coop/src/views/sidebar/button.rs
diff --git a/Cargo.lock b/Cargo.lock
index 1befafb..b55daa9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -966,6 +966,7 @@ dependencies = [
"anyhow",
"chrono",
"common",
+ "fuzzy-matcher",
"global",
"gpui",
"itertools 0.13.0",
@@ -1136,7 +1137,7 @@ dependencies = [
[[package]]
name = "collections"
version = "0.1.0"
-source = "git+https://github.com/zed-industries/zed#ab3e5cdc6cf416181fe009d13476ed5e779f7c24"
+source = "git+https://github.com/zed-industries/zed#d6c7cdd60ff3cbede868b7c73d92ce808f7b9ee3"
dependencies = [
"indexmap",
"rustc-hash 2.1.1",
@@ -1171,13 +1172,14 @@ version = "0.1.5"
dependencies = [
"anyhow",
"chrono",
+ "futures",
"global",
"gpui",
"itertools 0.13.0",
"nostr-sdk",
"qrcode-generator",
- "random_name_generator",
"smallvec",
+ "smol",
]
[[package]]
@@ -1527,7 +1529,7 @@ dependencies = [
[[package]]
name = "derive_refineable"
version = "0.1.0"
-source = "git+https://github.com/zed-industries/zed#ab3e5cdc6cf416181fe009d13476ed5e779f7c24"
+source = "git+https://github.com/zed-industries/zed#d6c7cdd60ff3cbede868b7c73d92ce808f7b9ee3"
dependencies = [
"proc-macro2",
"quote",
@@ -2169,6 +2171,15 @@ dependencies = [
"slab",
]
+[[package]]
+name = "fuzzy-matcher"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
+dependencies = [
+ "thread_local",
+]
+
[[package]]
name = "generic-array"
version = "0.14.7"
@@ -2318,7 +2329,7 @@ dependencies = [
[[package]]
name = "gpui"
version = "0.1.0"
-source = "git+https://github.com/zed-industries/zed#ab3e5cdc6cf416181fe009d13476ed5e779f7c24"
+source = "git+https://github.com/zed-industries/zed#d6c7cdd60ff3cbede868b7c73d92ce808f7b9ee3"
dependencies = [
"anyhow",
"as-raw-xcb-connection",
@@ -2410,7 +2421,7 @@ dependencies = [
[[package]]
name = "gpui_macros"
version = "0.1.0"
-source = "git+https://github.com/zed-industries/zed#ab3e5cdc6cf416181fe009d13476ed5e779f7c24"
+source = "git+https://github.com/zed-industries/zed#d6c7cdd60ff3cbede868b7c73d92ce808f7b9ee3"
dependencies = [
"proc-macro2",
"quote",
@@ -2634,7 +2645,7 @@ dependencies = [
[[package]]
name = "http_client"
version = "0.1.0"
-source = "git+https://github.com/zed-industries/zed#ab3e5cdc6cf416181fe009d13476ed5e779f7c24"
+source = "git+https://github.com/zed-industries/zed#d6c7cdd60ff3cbede868b7c73d92ce808f7b9ee3"
dependencies = [
"anyhow",
"bytes",
@@ -2651,7 +2662,7 @@ dependencies = [
[[package]]
name = "http_client_tls"
version = "0.1.0"
-source = "git+https://github.com/zed-industries/zed#ab3e5cdc6cf416181fe009d13476ed5e779f7c24"
+source = "git+https://github.com/zed-industries/zed#d6c7cdd60ff3cbede868b7c73d92ce808f7b9ee3"
dependencies = [
"rustls",
"rustls-platform-verifier",
@@ -3109,12 +3120,6 @@ dependencies = [
"libc",
]
-[[package]]
-name = "joinery"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5"
-
[[package]]
name = "jpeg-decoder"
version = "0.3.1"
@@ -3392,7 +3397,7 @@ dependencies = [
[[package]]
name = "media"
version = "0.1.0"
-source = "git+https://github.com/zed-industries/zed#ab3e5cdc6cf416181fe009d13476ed5e779f7c24"
+source = "git+https://github.com/zed-industries/zed#d6c7cdd60ff3cbede868b7c73d92ce808f7b9ee3"
dependencies = [
"anyhow",
"bindgen 0.71.1",
@@ -4699,23 +4704,6 @@ dependencies = [
"getrandom 0.3.2",
]
-[[package]]
-name = "random_name_generator"
-version = "0.3.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83f35cf4ff1039c849a4d890c6aa4332df47f9def1e9398ef1e5959bc7f89992"
-dependencies = [
- "anyhow",
- "bitflags 2.9.0",
- "clap",
- "lazy_static",
- "log",
- "rand 0.8.5",
- "regex",
- "rust-embed",
- "titlecase",
-]
-
[[package]]
name = "rangemap"
version = "1.5.1"
@@ -4843,7 +4831,7 @@ dependencies = [
[[package]]
name = "refineable"
version = "0.1.0"
-source = "git+https://github.com/zed-industries/zed#ab3e5cdc6cf416181fe009d13476ed5e779f7c24"
+source = "git+https://github.com/zed-industries/zed#d6c7cdd60ff3cbede868b7c73d92ce808f7b9ee3"
dependencies = [
"derive_refineable",
"workspace-hack",
@@ -4982,7 +4970,7 @@ dependencies = [
[[package]]
name = "reqwest_client"
version = "0.1.0"
-source = "git+https://github.com/zed-industries/zed#ab3e5cdc6cf416181fe009d13476ed5e779f7c24"
+source = "git+https://github.com/zed-industries/zed#d6c7cdd60ff3cbede868b7c73d92ce808f7b9ee3"
dependencies = [
"anyhow",
"bytes",
@@ -5168,11 +5156,12 @@ dependencies = [
[[package]]
name = "rustls-pki-types"
-version = "1.11.0"
+version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c"
+checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
dependencies = [
"web-time",
+ "zeroize",
]
[[package]]
@@ -5452,7 +5441,7 @@ checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749"
[[package]]
name = "semantic_version"
version = "0.1.0"
-source = "git+https://github.com/zed-industries/zed#ab3e5cdc6cf416181fe009d13476ed5e779f7c24"
+source = "git+https://github.com/zed-industries/zed#d6c7cdd60ff3cbede868b7c73d92ce808f7b9ee3"
dependencies = [
"anyhow",
"serde",
@@ -5775,7 +5764,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "sum_tree"
version = "0.1.0"
-source = "git+https://github.com/zed-industries/zed#ab3e5cdc6cf416181fe009d13476ed5e779f7c24"
+source = "git+https://github.com/zed-industries/zed#d6c7cdd60ff3cbede868b7c73d92ce808f7b9ee3"
dependencies = [
"arrayvec",
"log",
@@ -6196,17 +6185,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
-[[package]]
-name = "titlecase"
-version = "2.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38397a8cdb017cfeb48bf6c154d6de975ac69ffeed35980fde199d2ee0842042"
-dependencies = [
- "joinery",
- "lazy_static",
- "regex",
-]
-
[[package]]
name = "tokio"
version = "1.45.0"
@@ -6695,7 +6673,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "util"
version = "0.1.0"
-source = "git+https://github.com/zed-industries/zed#ab3e5cdc6cf416181fe009d13476ed5e779f7c24"
+source = "git+https://github.com/zed-industries/zed#d6c7cdd60ff3cbede868b7c73d92ce808f7b9ee3"
dependencies = [
"anyhow",
"async-fs",
diff --git a/Cargo.toml b/Cargo.toml
index d0678d7..5feb624 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,12 +24,12 @@ nostr-keyring = { git = "https://github.com/rust-nostr/nostr" }
# Others
emojis = "0.6.4"
smol = "2"
+futures = "0.3"
oneshot = "0.1.10"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
dirs = "5.0"
itertools = "0.13.0"
-futures = "0.3.30"
chrono = "0.4.38"
tracing = "0.1.40"
anyhow = "1.0.44"
diff --git a/assets/icons/plus-fill.svg b/assets/icons/plus-fill.svg
new file mode 100644
index 0000000..1ba3086
--- /dev/null
+++ b/assets/icons/plus-fill.svg
@@ -0,0 +1,3 @@
+
diff --git a/crates/account/src/lib.rs b/crates/account/src/lib.rs
index 700015a..f62ec37 100644
--- a/crates/account/src/lib.rs
+++ b/crates/account/src/lib.rs
@@ -27,6 +27,10 @@ impl Account {
cx.global::().0.clone()
}
+ pub fn get_global(cx: &App) -> &Self {
+ cx.global::().0.read(cx)
+ }
+
pub fn set_global(account: Entity, cx: &mut App) {
cx.set_global(GlobalAccount(account));
}
@@ -162,6 +166,11 @@ impl Account {
.detach();
}
+ /// Get the reference to profile.
+ pub fn profile_ref(&self) -> Option<&Profile> {
+ self.profile.as_ref()
+ }
+
/// Sets the profile for the account.
pub fn profile(&mut self, profile: Profile, cx: &mut Context) {
self.profile = Some(profile);
diff --git a/crates/chats/Cargo.toml b/crates/chats/Cargo.toml
index 990edec..a60a71a 100644
--- a/crates/chats/Cargo.toml
+++ b/crates/chats/Cargo.toml
@@ -20,3 +20,5 @@ smallvec.workspace = true
smol.workspace = true
oneshot.workspace = true
log.workspace = true
+
+fuzzy-matcher = "0.3.7"
diff --git a/crates/chats/src/lib.rs b/crates/chats/src/lib.rs
index b0fea62..18474c7 100644
--- a/crates/chats/src/lib.rs
+++ b/crates/chats/src/lib.rs
@@ -1,7 +1,11 @@
-use std::{cmp::Reverse, collections::HashMap};
+use std::{
+ cmp::Reverse,
+ collections::{BTreeMap, BTreeSet, HashMap},
+};
use anyhow::Error;
use common::room_hash;
+use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
use global::get_client;
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task, Window};
use itertools::Itertools;
@@ -33,14 +37,11 @@ impl Global for GlobalChatRegistry {}
/// - Handling messages and room creation
pub struct ChatRegistry {
/// Collection of all chat rooms
- rooms: Vec>,
-
+ rooms: BTreeSet>,
/// Map of user public keys to their profile metadata
- profiles: Entity>>,
-
+ profiles: Entity>>,
/// Indicates if rooms are currently being loaded
- loading: bool,
-
+ pub loading: bool,
/// Subscriptions for observing changes
#[allow(dead_code)]
subscriptions: SmallVec<[Subscription; 1]>,
@@ -52,6 +53,11 @@ impl ChatRegistry {
cx.global::().0.clone()
}
+ /// Retrieve the ChatRegistry instance
+ pub fn get_global(cx: &App) -> &Self {
+ cx.global::().0.read(cx)
+ }
+
/// Set the global ChatRegistry instance
pub fn set_global(state: Entity, cx: &mut App) {
cx.set_global(GlobalChatRegistry(state));
@@ -59,7 +65,7 @@ impl ChatRegistry {
/// Create a new ChatRegistry instance
fn new(cx: &mut Context) -> Self {
- let profiles = cx.new(|_| HashMap::new());
+ let profiles = cx.new(|_| BTreeMap::new());
let mut subscriptions = smallvec![];
// Observe new Room creations to collect profile metadata
@@ -82,18 +88,13 @@ impl ChatRegistry {
}));
Self {
- rooms: vec![],
+ rooms: BTreeSet::new(),
loading: true,
profiles,
subscriptions,
}
}
- /// Get the global loading status
- pub fn loading(&self) -> bool {
- self.loading
- }
-
/// Get a room by its ID.
pub fn room(&self, id: &u64, cx: &App) -> Option> {
self.rooms
@@ -103,33 +104,43 @@ impl ChatRegistry {
}
/// Get all rooms grouped by their kind.
- pub fn rooms(&self, cx: &App) -> HashMap>> {
- let mut groups = HashMap::new();
+ pub fn rooms(&self, cx: &App) -> BTreeMap>> {
+ let mut groups = BTreeMap::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
+ .entry(kind)
+ .or_insert_with(Vec::new)
+ .push(room.to_owned());
}
groups
}
- /// Get rooms by their kind.
- pub fn rooms_by_kind(&self, kind: RoomKind, cx: &App) -> Vec<&Entity> {
- self.rooms
- .iter()
- .filter(|room| room.read(cx).kind == kind)
- .collect()
- }
-
/// Get the IDs of all rooms.
pub fn room_ids(&self, cx: &mut Context) -> Vec {
self.rooms.iter().map(|room| room.read(cx).id).collect()
}
+ /// Search rooms by their name.
+ pub fn search(&self, query: &str, cx: &App) -> Vec> {
+ let matcher = SkimMatcherV2::default();
+
+ self.rooms
+ .iter()
+ .filter(|room| {
+ matcher
+ .fuzzy_match(room.read(cx).display_name(cx).as_ref(), query)
+ .is_some()
+ })
+ .cloned()
+ .collect()
+ }
+
/// Load all rooms from the lmdb.
///
/// This method:
@@ -215,7 +226,6 @@ impl ChatRegistry {
.collect();
this.rooms.extend(rooms);
- this.rooms.sort_by_key(|r| Reverse(r.read(cx).created_at));
this.loading = false;
cx.notify();
@@ -259,15 +269,20 @@ impl ChatRegistry {
Profile::new(*public_key, metadata)
}
- /// Add a new room to the registry
+ /// Parse a Nostr event into a Room and push it to the registry
///
- /// Returns an error if the room already exists
- pub fn push(&mut self, event: &Event, window: &mut Window, cx: &mut Context) -> u64 {
+ /// Returns the ID of the new room
+ pub fn push_event(
+ &mut self,
+ event: &Event,
+ window: &mut Window,
+ cx: &mut Context,
+ ) -> u64 {
let room = Room::new(event).kind(RoomKind::Ongoing);
let id = room.id;
- if !self.rooms.iter().any(|r| r.read(cx) == &room) {
- self.rooms.insert(0, cx.new(|_| room));
+ if !self.rooms.iter().any(|this| this.read(cx) == &room) {
+ self.rooms.insert(cx.new(|_| room));
cx.notify();
} else {
window.push_notification("Room already exists", cx);
@@ -276,6 +291,20 @@ impl ChatRegistry {
id
}
+ /// Parse a nostr event into Room and push to the registry
+ ///
+ /// Returns the ID of the new room
+ pub fn push_room(&mut self, room: Entity, cx: &mut Context) -> u64 {
+ let id = room.read(cx).id;
+
+ if !self.rooms.iter().any(|this| this.read(cx) == room.read(cx)) {
+ self.rooms.insert(room);
+ cx.notify();
+ }
+
+ id
+ }
+
/// Push a new message to a room
///
/// If the room doesn't exist, it will be created.
@@ -291,14 +320,9 @@ impl ChatRegistry {
this.emit_message(event, window, cx);
});
});
-
- cx.defer_in(window, |this, _, cx| {
- this.rooms
- .sort_by_key(|room| Reverse(room.read(cx).created_at));
- });
} else {
// Push the new room to the front of the list
- self.rooms.insert(0, cx.new(|_| Room::new(&event)));
+ self.rooms.insert(cx.new(|_| Room::new(&event)));
cx.notify();
}
}
diff --git a/crates/chats/src/room.rs b/crates/chats/src/room.rs
index 1ba41de..f379c26 100644
--- a/crates/chats/src/room.rs
+++ b/crates/chats/src/room.rs
@@ -1,4 +1,4 @@
-use std::sync::Arc;
+use std::{cmp::Ordering, sync::Arc};
use account::Account;
use anyhow::Error;
@@ -27,7 +27,7 @@ pub enum SendStatus {
Failed(Error),
}
-#[derive(Clone, Copy, Hash, Debug, PartialEq, Eq, Default)]
+#[derive(Clone, Copy, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum RoomKind {
Ongoing,
Trusted,
@@ -35,6 +35,7 @@ pub enum RoomKind {
Unknown,
}
+#[derive(Debug)]
pub struct Room {
pub id: u64,
pub created_at: Timestamp,
@@ -48,7 +49,19 @@ pub struct Room {
pub kind: RoomKind,
}
-impl EventEmitter for Room {}
+impl Ord for Room {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.created_at.cmp(&other.created_at)
+ }
+}
+
+impl PartialOrd for Room {
+ fn partial_cmp(&self, other: &Self) -> Option {
+ Some(self.cmp(other))
+ }
+}
+
+impl Eq for Room {}
impl PartialEq for Room {
fn eq(&self, other: &Self) -> bool {
@@ -56,6 +69,8 @@ impl PartialEq for Room {
}
}
+impl EventEmitter for Room {}
+
impl Room {
/// Creates a new Room instance from a Nostr event
///
@@ -186,28 +201,6 @@ impl Room {
}
}
- /// Gets all avatars for members in the room
- ///
- /// # Arguments
- ///
- /// * `cx` - The App context
- ///
- /// # Returns
- ///
- /// A vector of SharedString containing all members' avatars
- pub fn avatars(&self, cx: &App) -> Vec {
- let profiles: Vec = self
- .members
- .iter()
- .map(|pubkey| ChatRegistry::global(cx).read(cx).profile(pubkey, cx))
- .collect();
-
- profiles
- .iter()
- .map(|member| member.shared_avatar())
- .collect()
- }
-
/// Gets a formatted string of member names
///
/// # Arguments
diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml
index 2b3d50a..87387e1 100644
--- a/crates/common/Cargo.toml
+++ b/crates/common/Cargo.toml
@@ -13,6 +13,7 @@ anyhow.workspace = true
itertools.workspace = true
chrono.workspace = true
smallvec.workspace = true
+smol.workspace = true
+futures.workspace = true
-random_name_generator = "0.3.6"
qrcode-generator = "5.0.0"
diff --git a/crates/common/src/debounced_delay.rs b/crates/common/src/debounced_delay.rs
new file mode 100644
index 0000000..03281d1
--- /dev/null
+++ b/crates/common/src/debounced_delay.rs
@@ -0,0 +1,55 @@
+use futures::{channel::oneshot, FutureExt};
+use gpui::{Context, Task};
+use std::{marker::PhantomData, time::Duration};
+
+pub struct DebouncedDelay {
+ task: Option>,
+ cancel_channel: Option>,
+ _phantom_data: PhantomData,
+}
+
+impl Default for DebouncedDelay {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl DebouncedDelay {
+ pub fn new() -> Self {
+ Self {
+ task: None,
+ cancel_channel: None,
+ _phantom_data: PhantomData,
+ }
+ }
+
+ pub fn fire_new(&mut self, delay: Duration, cx: &mut Context, func: F)
+ where
+ F: 'static + Send + FnOnce(&mut E, &mut Context) -> Task<()>,
+ {
+ if let Some(channel) = self.cancel_channel.take() {
+ _ = channel.send(());
+ }
+
+ let (sender, mut receiver) = oneshot::channel::<()>();
+ self.cancel_channel = Some(sender);
+
+ let previous_task = self.task.take();
+ self.task = Some(cx.spawn(async move |entity, cx| {
+ let mut timer = cx.background_executor().timer(delay).fuse();
+
+ if let Some(previous_task) = previous_task {
+ previous_task.await;
+ }
+
+ futures::select_biased! {
+ _ = receiver => return,
+ _ = timer => {}
+ }
+
+ if let Ok(task) = entity.update(cx, |project, cx| (func)(project, cx)) {
+ task.await;
+ }
+ }));
+ }
+}
diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs
index 24d2623..14f5358 100644
--- a/crates/common/src/lib.rs
+++ b/crates/common/src/lib.rs
@@ -4,14 +4,13 @@ use std::{
sync::Arc,
};
-use anyhow::Context;
use global::constants::NIP96_SERVER;
use gpui::Image;
use itertools::Itertools;
use nostr_sdk::prelude::*;
use qrcode_generator::QrCodeEcc;
-use rnglib::{Language, RNG};
+pub mod debounced_delay;
pub mod profile;
pub async fn nip96_upload(client: &Client, file: Vec) -> anyhow::Result {
@@ -43,19 +42,6 @@ pub fn room_hash(event: &Event) -> u64 {
hasher.finish()
}
-pub fn device_pubkey(event: &Event) -> Result {
- let n_tag = event.tags.find(TagKind::custom("n")).context("Invalid")?;
- let hex = n_tag.content().context("Invalid")?;
- let pubkey = PublicKey::parse(hex)?;
-
- Ok(pubkey)
-}
-
-pub fn random_name(length: usize) -> String {
- let rng = RNG::from(&Language::Roman);
- rng.generate_names(length, true).join("-").to_lowercase()
-}
-
pub fn create_qr(data: &str) -> Result, anyhow::Error> {
let qr = qrcode_generator::to_png_to_vec_from_str(data, QrCodeEcc::Medium, 256)?;
let img = Arc::new(Image {
diff --git a/crates/coop/Cargo.toml b/crates/coop/Cargo.toml
index e41e68a..add2abe 100644
--- a/crates/coop/Cargo.toml
+++ b/crates/coop/Cargo.toml
@@ -32,9 +32,9 @@ rust-embed.workspace = true
log.workspace = true
smallvec.workspace = true
smol.workspace = true
+futures.workspace = true
oneshot.workspace = true
webbrowser = "1.0.4"
rustls = "0.23.23"
-futures = "0.3"
tracing-subscriber = { version = "0.3.18", features = ["fmt"] }
diff --git a/crates/coop/src/chatspace.rs b/crates/coop/src/chatspace.rs
index c87564a..4f15790 100644
--- a/crates/coop/src/chatspace.rs
+++ b/crates/coop/src/chatspace.rs
@@ -20,12 +20,10 @@ use ui::{
use crate::{
lru_cache::cache_provider,
- views::{
- chat, compose, login, new_account, onboarding, profile, relays, search, sidebar, welcome,
- },
+ views::{chat, compose, login, new_account, onboarding, profile, relays, sidebar, welcome},
};
-const CACHE_SIZE: usize = 200;
+const IMAGE_CACHE_SIZE: usize = 200;
const MODAL_WIDTH: f32 = 420.;
const SIDEBAR_WIDTH: f32 = 280.;
@@ -53,7 +51,6 @@ pub enum PanelKind {
pub enum ModalKind {
Profile,
Compose,
- Search,
Relay,
Onboarding,
SetupRelay,
@@ -242,16 +239,6 @@ impl ChatSpace {
.child(compose.clone())
})
}
- ModalKind::Search => {
- let search = search::init(window, cx);
-
- window.open_modal(cx, move |modal, _, _| {
- modal
- .closable(false)
- .width(px(MODAL_WIDTH))
- .child(search.clone())
- })
- }
ModalKind::Relay => {
let relays = relays::init(window, cx);
@@ -299,7 +286,7 @@ impl Render for ChatSpace {
.relative()
.size_full()
.child(
- image_cache(cache_provider("image-cache", CACHE_SIZE))
+ image_cache(cache_provider("image-cache", IMAGE_CACHE_SIZE))
.size_full()
.child(
div()
diff --git a/crates/coop/src/views/chat.rs b/crates/coop/src/views/chat.rs
index 9f7a9cf..5903551 100644
--- a/crates/coop/src/views/chat.rs
+++ b/crates/coop/src/views/chat.rs
@@ -148,22 +148,19 @@ impl Chat {
cx.spawn_in(window, async move |this, cx| {
if let Ok(result) = task.await {
- cx.update(|_, cx| {
- this.update(cx, |this, cx| {
- result.into_iter().for_each(|item| {
- if !item.1 {
- let profile = this
- .room
- .read_with(cx, |this, _| this.profile_by_pubkey(&item.0, cx));
+ this.update(cx, |this, cx| {
+ result.into_iter().for_each(|item| {
+ if !item.1 {
+ let profile = this
+ .room
+ .read_with(cx, |this, _| this.profile_by_pubkey(&item.0, cx));
- this.push_system_message(
- format!("{} {}", profile.shared_name(), ALERT),
- cx,
- );
- }
- });
- })
- .ok();
+ this.push_system_message(
+ format!("{} {}", profile.shared_name(), ALERT),
+ cx,
+ );
+ }
+ });
})
.ok();
}
@@ -235,8 +232,8 @@ impl Chat {
// Update input state
self.input.update(cx, |this, cx| {
- this.set_loading(true, window, cx);
- this.set_disabled(true, window, cx);
+ this.set_loading(true, cx);
+ this.set_disabled(true, cx);
});
let room = self.room.read(cx);
@@ -261,8 +258,8 @@ impl Chat {
cx.update(|window, cx| {
this.update(cx, |this, cx| {
this.input.update(cx, |this, cx| {
- this.set_loading(false, window, cx);
- this.set_disabled(false, window, cx);
+ this.set_loading(false, cx);
+ this.set_disabled(false, cx);
this.set_text("", window, cx);
});
received = true;
diff --git a/crates/coop/src/views/compose.rs b/crates/coop/src/views/compose.rs
index f10651e..9e3e4f2 100644
--- a/crates/coop/src/views/compose.rs
+++ b/crates/coop/src/views/compose.rs
@@ -5,7 +5,7 @@ use std::{
use anyhow::Error;
use chats::ChatRegistry;
-use common::{profile::SharedProfile, random_name};
+use common::profile::SharedProfile;
use global::get_client;
use gpui::{
div, img, impl_internal_actions, prelude::FluentBuilder, px, red, relative, uniform_list, App,
@@ -56,14 +56,10 @@ impl Compose {
let error_message = cx.new(|_| None);
let title_input = cx.new(|cx| {
- let name = random_name(2);
- let mut input = TextInput::new(window, cx)
+ TextInput::new(window, cx)
.appearance(false)
- .text_size(Size::Small);
-
- input.set_placeholder("Family... . (Optional)");
- input.set_text(name, window, cx);
- input
+ .placeholder("Family... . (Optional)")
+ .text_size(Size::Small)
});
let user_input = cx.new(|cx| {
@@ -151,6 +147,7 @@ impl Compose {
let event: Task> = cx.background_spawn(async move {
let client = get_client();
let signer = client.signer().await?;
+
// [IMPORTANT]
// Make sure this event is never send,
// this event existed just use for convert to Coop's Room later.
@@ -166,7 +163,7 @@ impl Compose {
Ok(event) => {
cx.update(|window, cx| {
ChatRegistry::global(cx).update(cx, |chats, cx| {
- let id = chats.push(&event, window, cx);
+ let id = chats.push_event(&event, window, cx);
window.close_modal(cx);
window.dispatch_action(
Box::new(AddPanel::new(PanelKind::Room(id), DockPlacement::Center)),
@@ -351,7 +348,7 @@ impl Render for Compose {
.flex()
.items_center()
.gap_1()
- .child(div().pb_0p5().text_sm().font_semibold().child("Title:"))
+ .child(div().pb_0p5().text_sm().font_semibold().child("Subject:"))
.child(self.title_input.clone()),
),
)
diff --git a/crates/coop/src/views/mod.rs b/crates/coop/src/views/mod.rs
index 88fd8a7..b5914f4 100644
--- a/crates/coop/src/views/mod.rs
+++ b/crates/coop/src/views/mod.rs
@@ -5,7 +5,6 @@ pub mod new_account;
pub mod onboarding;
pub mod profile;
pub mod relays;
-pub mod search;
pub mod sidebar;
pub mod subject;
pub mod welcome;
diff --git a/crates/coop/src/views/profile.rs b/crates/coop/src/views/profile.rs
index 9211939..e6f25d1 100644
--- a/crates/coop/src/views/profile.rs
+++ b/crates/coop/src/views/profile.rs
@@ -314,7 +314,7 @@ impl Render for Profile {
.child(self.bio_input.clone()),
)
.child(
- div().p_3().child(
+ div().py_3().child(
Button::new("submit")
.label("Update")
.primary()
diff --git a/crates/coop/src/views/sidebar/button.rs b/crates/coop/src/views/sidebar/button.rs
deleted file mode 100644
index cb32847..0000000
--- a/crates/coop/src/views/sidebar/button.rs
+++ /dev/null
@@ -1,58 +0,0 @@
-use std::rc::Rc;
-
-use gpui::{
- div, prelude::FluentBuilder, App, ClickEvent, Div, InteractiveElement, IntoElement,
- ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window,
-};
-use theme::ActiveTheme;
-use ui::Icon;
-
-type Handler = Rc;
-
-#[derive(IntoElement)]
-pub struct SidebarButton {
- base: Div,
- label: SharedString,
- icon: Option,
- handler: Handler,
-}
-
-impl SidebarButton {
- pub fn new(label: impl Into) -> Self {
- Self {
- base: div().flex().items_center().gap_3().px_3().h_8(),
- label: label.into(),
- icon: None,
- handler: Rc::new(|_, _, _| {}),
- }
- }
-
- pub fn icon(mut self, icon: impl Into) -> Self {
- self.icon = Some(icon.into());
- self
- }
-
- pub fn on_click(
- mut self,
- handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
- ) -> Self {
- self.handler = Rc::new(handler);
- self
- }
-}
-
-impl RenderOnce for SidebarButton {
- fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
- let handler = self.handler.clone();
-
- self.base
- .id(self.label.clone())
- .rounded(cx.theme().radius)
- .when_some(self.icon, |this, icon| {
- this.child(div().text_color(cx.theme().text_muted).child(icon))
- })
- .child(self.label.clone())
- .hover(|this| this.bg(cx.theme().elevated_surface_background))
- .on_click(move |ev, window, cx| handler(ev, window, cx))
- }
-}
diff --git a/crates/coop/src/views/sidebar/folder.rs b/crates/coop/src/views/sidebar/folder.rs
index eb02718..53ac45c 100644
--- a/crates/coop/src/views/sidebar/folder.rs
+++ b/crates/coop/src/views/sidebar/folder.rs
@@ -299,7 +299,7 @@ impl RenderOnce for FolderItem {
.font_medium()
.map(|this| {
if let Some(img) = self.img {
- this.child(img.size_5().flex_shrink_0())
+ this.child(img.size_6().flex_shrink_0())
} else {
this.child(
div()
diff --git a/crates/coop/src/views/sidebar/mod.rs b/crates/coop/src/views/sidebar/mod.rs
index 94c26dc..14420e6 100644
--- a/crates/coop/src/views/sidebar/mod.rs
+++ b/crates/coop/src/views/sidebar/mod.rs
@@ -1,27 +1,35 @@
-use std::{cmp::Reverse, collections::HashSet};
+use std::{
+ cmp::Reverse,
+ collections::{BTreeSet, HashSet},
+ time::Duration,
+};
use account::Account;
-use button::SidebarButton;
+use async_utility::task::spawn;
use chats::{
room::{Room, RoomKind},
ChatRegistry,
};
-use common::profile::SharedProfile;
+
+use common::{debounced_delay::DebouncedDelay, profile::SharedProfile};
use folder::{Folder, FolderItem, Parent};
-use global::get_client;
+use global::{constants::SEARCH_RELAYS, get_client};
use gpui::{
- actions, div, img, prelude::FluentBuilder, AnyElement, App, AppContext, Context, Entity,
- EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render,
- ScrollHandle, SharedString, StatefulInteractiveElement, Styled, Task, Window,
+ div, img, prelude::FluentBuilder, AnyElement, App, AppContext, Context, Entity, EventEmitter,
+ FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render, ScrollHandle,
+ SharedString, StatefulInteractiveElement, Styled, Subscription, Task, Window,
};
use itertools::Itertools;
+use nostr_sdk::prelude::*;
+use smallvec::{smallvec, SmallVec};
use theme::ActiveTheme;
use ui::{
- button::{Button, ButtonCustomVariant, ButtonVariants},
+ button::{Button, ButtonCustomVariant, ButtonRounded, ButtonVariants},
dock_area::{
dock::DockPlacement,
panel::{Panel, PanelEvent},
},
+ input::{InputEvent, TextInput},
popup_menu::{PopupMenu, PopupMenuExt},
skeleton::Skeleton,
IconName, Sizable, StyledExt,
@@ -29,10 +37,10 @@ use ui::{
use crate::chatspace::{AddPanel, ModalKind, PanelKind, ToggleModal};
-mod button;
mod folder;
-actions!(profile, [Logout]);
+const FIND_DELAY: u64 = 600;
+const FIND_LIMIT: usize = 10;
pub fn init(window: &mut Window, cx: &mut App) -> Entity {
Sidebar::new(window, cx)
@@ -52,11 +60,21 @@ pub enum SubItem {
pub struct Sidebar {
name: SharedString,
+ // Search
+ find_input: Entity,
+ find_debouncer: DebouncedDelay,
+ finding: bool,
+ local_result: Entity