feat: add action to compose modal
This commit is contained in:
@@ -156,6 +156,17 @@ impl ChatRegistry {
|
||||
.map(|model| model.downgrade())
|
||||
}
|
||||
|
||||
pub fn new_room(&mut self, room: Room, cx: &mut AppContext) {
|
||||
let room = cx.new_model(|_| room);
|
||||
|
||||
self.inbox.update(cx, |this, cx| {
|
||||
if !this.rooms.iter().any(|r| r.read(cx) == room.read(cx)) {
|
||||
this.rooms.insert(0, room);
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn receive(&mut self, event: Event, cx: &mut AppContext) {
|
||||
let mut pubkeys: Vec<_> = event.tags.public_keys().copied().collect();
|
||||
pubkeys.push(event.pubkey);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use crate::{
|
||||
constants::IMAGE_SERVICE,
|
||||
utils::{room_hash, shorted_public_key},
|
||||
utils::{compare, random_name, room_hash, shorted_public_key},
|
||||
};
|
||||
use gpui::SharedString;
|
||||
use nostr_sdk::prelude::*;
|
||||
use rnglib::{Language, RNG};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Member {
|
||||
@@ -67,6 +66,18 @@ pub struct Room {
|
||||
pub new_messages: Vec<Event>, // Hold all new messages
|
||||
}
|
||||
|
||||
impl PartialEq for Room {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
let mut pubkeys: Vec<PublicKey> = self.members.iter().map(|m| m.public_key()).collect();
|
||||
pubkeys.push(self.owner.public_key());
|
||||
|
||||
let mut pubkeys2: Vec<PublicKey> = other.members.iter().map(|m| m.public_key()).collect();
|
||||
pubkeys2.push(other.owner.public_key());
|
||||
|
||||
compare(&pubkeys, &pubkeys2)
|
||||
}
|
||||
}
|
||||
|
||||
impl Room {
|
||||
pub fn new(
|
||||
id: u64,
|
||||
@@ -76,6 +87,11 @@ impl Room {
|
||||
last_seen: Timestamp,
|
||||
) -> Self {
|
||||
let is_group = members.len() > 1;
|
||||
let title = if title.is_none() {
|
||||
Some(random_name(2).into())
|
||||
} else {
|
||||
title
|
||||
};
|
||||
|
||||
Self {
|
||||
id,
|
||||
@@ -103,10 +119,7 @@ impl Room {
|
||||
let title = if let Some(tag) = event.tags.find(TagKind::Title) {
|
||||
tag.content().map(|s| s.to_owned().into())
|
||||
} else {
|
||||
let rng = RNG::from(&Language::Roman);
|
||||
let name = rng.generate_names(2, true).join("-").to_lowercase();
|
||||
|
||||
Some(name.into())
|
||||
None
|
||||
};
|
||||
|
||||
Self::new(id, owner, members, title, last_seen)
|
||||
@@ -136,11 +149,23 @@ impl Room {
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
self.members
|
||||
.iter()
|
||||
.map(|profile| profile.name())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
if self.members.len() <= 2 {
|
||||
self.members
|
||||
.iter()
|
||||
.map(|profile| profile.name())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
} else {
|
||||
let name = self
|
||||
.members
|
||||
.iter()
|
||||
.take(2)
|
||||
.map(|profile| profile.name())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
|
||||
format!("{}, +{}", name, self.members.len() - 2)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_all_keys(&self) -> Vec<PublicKey> {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::{constants::NIP96_SERVER, get_client};
|
||||
use chrono::{Datelike, Local, TimeZone};
|
||||
use nostr_sdk::prelude::*;
|
||||
use rnglib::{Language, RNG};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
hash::{DefaultHasher, Hash, Hasher},
|
||||
@@ -26,6 +27,11 @@ pub fn room_hash(tags: &Tags) -> u64 {
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
pub fn random_name(length: usize) -> String {
|
||||
let rng = RNG::from(&Language::Roman);
|
||||
rng.generate_names(length, true).join("-").to_lowercase()
|
||||
}
|
||||
|
||||
pub fn compare<T>(a: &[T], b: &[T]) -> bool
|
||||
where
|
||||
T: Eq + Hash,
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
use crate::{get_client, states::chat::room::Member};
|
||||
use crate::{
|
||||
get_client,
|
||||
states::{
|
||||
app::AppRegistry,
|
||||
chat::room::{Member, Room},
|
||||
},
|
||||
utils::{random_name, room_hash},
|
||||
};
|
||||
use gpui::{
|
||||
div, img, impl_internal_actions, px, uniform_list, Context, FocusHandle, InteractiveElement,
|
||||
IntoElement, Model, ParentElement, Render, StatefulInteractiveElement, Styled, View,
|
||||
ViewContext, VisualContext, WindowContext,
|
||||
IntoElement, Model, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled,
|
||||
View, ViewContext, VisualContext, WindowContext,
|
||||
};
|
||||
use nostr_sdk::prelude::*;
|
||||
use serde::Deserialize;
|
||||
@@ -44,17 +51,19 @@ impl Compose {
|
||||
});
|
||||
|
||||
let title_input = cx.new_view(|cx| {
|
||||
TextInput::new(cx)
|
||||
.appearance(false)
|
||||
.text_size(Size::XSmall)
|
||||
.placeholder("Family...")
|
||||
let name = random_name(2);
|
||||
let mut input = TextInput::new(cx).appearance(false).text_size(Size::XSmall);
|
||||
|
||||
input.set_placeholder("Family... . (Optional)");
|
||||
input.set_text(name, cx);
|
||||
input
|
||||
});
|
||||
|
||||
let message_input = cx.new_view(|cx| {
|
||||
TextInput::new(cx)
|
||||
.appearance(false)
|
||||
.text_size(Size::XSmall)
|
||||
.placeholder("Hello...")
|
||||
.placeholder("Hello... (Optional)")
|
||||
});
|
||||
|
||||
cx.subscribe(&user_input, move |this, _, input_event, cx| {
|
||||
@@ -110,8 +119,51 @@ impl Compose {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected<'a>(&self, cx: &'a WindowContext) -> Vec<&'a PublicKey> {
|
||||
self.selected.read(cx).iter().collect()
|
||||
pub fn room(&self, cx: &WindowContext) -> Option<Room> {
|
||||
let weak_user = cx.global::<AppRegistry>().current_user();
|
||||
|
||||
if let Some(user) = weak_user.upgrade() {
|
||||
let public_key = user.read(cx).unwrap();
|
||||
|
||||
// Convert selected pubkeys into nostr tags
|
||||
let tags: Vec<Tag> = self
|
||||
.selected
|
||||
.read(cx)
|
||||
.iter()
|
||||
.map(|pk| Tag::public_key(*pk))
|
||||
.collect();
|
||||
let tags = Tags::new(tags);
|
||||
|
||||
// Convert selected pubkeys into members
|
||||
let members: Vec<Member> = self
|
||||
.selected
|
||||
.read(cx)
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|pk| Member::new(pk, Metadata::new()))
|
||||
.collect();
|
||||
|
||||
// Get room's id
|
||||
let id = room_hash(&tags);
|
||||
|
||||
// Get room's owner (current user)
|
||||
let owner = Member::new(public_key, Metadata::new());
|
||||
|
||||
// Get room's title
|
||||
let title = self.title_input.read(cx).text().to_string().into();
|
||||
|
||||
Some(Room::new(id, owner, members, Some(title), Timestamp::now()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn label(&self, cx: &WindowContext) -> SharedString {
|
||||
if self.selected.read(cx).len() > 1 {
|
||||
"Create Group DM".into()
|
||||
} else {
|
||||
"Create DM".into()
|
||||
}
|
||||
}
|
||||
|
||||
fn add(&mut self, cx: &mut ViewContext<Self>) {
|
||||
|
||||
@@ -22,7 +22,7 @@ pub struct Inbox {
|
||||
impl Inbox {
|
||||
pub fn new(_cx: &mut ViewContext<'_, Self>) -> Self {
|
||||
Self {
|
||||
label: "Direct Messages".into(),
|
||||
label: "Inbox".into(),
|
||||
is_collapsed: false,
|
||||
}
|
||||
}
|
||||
@@ -138,11 +138,6 @@ impl Render for Inbox {
|
||||
.rounded(px(cx.theme().radius))
|
||||
.text_xs()
|
||||
.font_semibold()
|
||||
.hover(|this| this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE)))
|
||||
.on_click(cx.listener(move |view, _event, cx| {
|
||||
view.is_collapsed = !view.is_collapsed;
|
||||
cx.notify();
|
||||
}))
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDown)
|
||||
.size_6()
|
||||
@@ -150,7 +145,12 @@ impl Render for Inbox {
|
||||
this.rotate(percentage(270. / 360.))
|
||||
}),
|
||||
)
|
||||
.child(self.label.clone()),
|
||||
.child(self.label.clone())
|
||||
.hover(|this| this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE)))
|
||||
.on_click(cx.listener(move |view, _event, cx| {
|
||||
view.is_collapsed = !view.is_collapsed;
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.when(!self.is_collapsed, |this| this.child(self.render_item(cx)))
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::views::sidebar::inbox::Inbox;
|
||||
use crate::{states::chat::ChatRegistry, views::sidebar::inbox::Inbox};
|
||||
use compose::Compose;
|
||||
use gpui::{
|
||||
div, px, AnyElement, AppContext, Entity, EntityId, EventEmitter, FocusHandle, FocusableView,
|
||||
IntoElement, ParentElement, Render, SharedString, Styled, View, ViewContext, VisualContext,
|
||||
WindowContext,
|
||||
div, px, AnyElement, AppContext, BorrowAppContext, Entity, EntityId, EventEmitter, FocusHandle,
|
||||
FocusableView, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled, View, ViewContext, VisualContext, WindowContext,
|
||||
};
|
||||
use ui::{
|
||||
button::{Button, ButtonRounded, ButtonVariants},
|
||||
@@ -53,12 +53,7 @@ impl Sidebar {
|
||||
let compose = cx.new_view(Compose::new);
|
||||
|
||||
cx.open_modal(move |modal, cx| {
|
||||
let selected = compose.model.read(cx).selected(cx);
|
||||
let label = if selected.len() > 1 {
|
||||
"Create Group DM"
|
||||
} else {
|
||||
"Create DM"
|
||||
};
|
||||
let label = compose.read(cx).label(cx);
|
||||
|
||||
modal
|
||||
.title("Direct Messages")
|
||||
@@ -75,7 +70,16 @@ impl Sidebar {
|
||||
.primary()
|
||||
.bold()
|
||||
.rounded(ButtonRounded::Large)
|
||||
.w_full(),
|
||||
.w_full()
|
||||
.on_click(cx.listener_for(&compose, |this, _, cx| {
|
||||
if let Some(room) = this.room(cx) {
|
||||
cx.update_global::<ChatRegistry, _>(|this, cx| {
|
||||
this.new_room(room, cx);
|
||||
});
|
||||
|
||||
cx.close_modal();
|
||||
}
|
||||
})),
|
||||
),
|
||||
)
|
||||
})
|
||||
@@ -127,13 +131,33 @@ impl Render for Sidebar {
|
||||
.py_3()
|
||||
.gap_3()
|
||||
.child(
|
||||
v_flex().px_2().gap_0p5().child(
|
||||
Button::new("compose")
|
||||
.small()
|
||||
.ghost()
|
||||
.not_centered()
|
||||
.icon(Icon::new(IconName::ComposeFill))
|
||||
.label("Compose")
|
||||
v_flex().px_2().gap_1().child(
|
||||
div()
|
||||
.id("new")
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.px_1()
|
||||
.h_7()
|
||||
.text_xs()
|
||||
.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, _, cx| this.show_compose(cx))),
|
||||
),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user