feat: add support for subject of conversation
This commit is contained in:
@@ -22,8 +22,6 @@ use crate::views::{onboarding, sidebar};
|
||||
const MODAL_WIDTH: f32 = 420.;
|
||||
const SIDEBAR_WIDTH: f32 = 280.;
|
||||
|
||||
impl_internal_actions!(dock, [AddPanel, ToggleModal]);
|
||||
|
||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<ChatSpace> {
|
||||
ChatSpace::new(window, cx)
|
||||
}
|
||||
@@ -58,6 +56,8 @@ pub struct ToggleModal {
|
||||
pub modal: ModalKind,
|
||||
}
|
||||
|
||||
impl_internal_actions!(dock, [AddPanel, ToggleModal]);
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
||||
pub struct AddPanel {
|
||||
panel: PanelKind,
|
||||
@@ -263,7 +263,7 @@ impl ChatSpace {
|
||||
};
|
||||
}
|
||||
|
||||
fn set_center_panel<P: PanelView>(panel: P, window: &mut Window, cx: &mut App) {
|
||||
pub(crate) fn set_center_panel<P: PanelView>(panel: P, window: &mut Window, cx: &mut App) {
|
||||
if let Some(Some(root)) = window.root::<Root>() {
|
||||
if let Ok(chatspace) = root.read(cx).view().clone().downcast::<ChatSpace>() {
|
||||
let panel = Arc::new(panel);
|
||||
|
||||
@@ -298,11 +298,11 @@ fn main() {
|
||||
|
||||
// Spawn a task to handle events from nostr channel
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
let chats = cx.update(|_, cx| ChatRegistry::global(cx)).unwrap();
|
||||
let auto_updater = cx.update(|_, cx| AutoUpdater::global(cx)).unwrap();
|
||||
|
||||
while let Ok(signal) = event_rx.recv().await {
|
||||
cx.update(|window, cx| {
|
||||
let chats = ChatRegistry::global(cx);
|
||||
let auto_updater = AutoUpdater::global(cx);
|
||||
|
||||
match signal {
|
||||
Signal::Event(event) => {
|
||||
chats.update(cx, |this, cx| {
|
||||
|
||||
@@ -4,12 +4,14 @@ use chats::{message::RoomMessage, room::Room, ChatRegistry};
|
||||
use common::{nip96_upload, profile::SharedProfile};
|
||||
use global::{constants::IMAGE_SERVICE, get_client};
|
||||
use gpui::{
|
||||
div, img, list, prelude::FluentBuilder, px, relative, svg, white, AnyElement, App, AppContext,
|
||||
Context, Element, Entity, EventEmitter, Flatten, FocusHandle, Focusable, InteractiveElement,
|
||||
IntoElement, ListAlignment, ListState, ObjectFit, ParentElement, PathPromptOptions, Render,
|
||||
SharedString, StatefulInteractiveElement, Styled, StyledImage, Subscription, Window,
|
||||
div, img, impl_internal_actions, list, prelude::FluentBuilder, px, relative, svg, white,
|
||||
AnyElement, App, AppContext, Context, Element, Empty, Entity, EventEmitter, Flatten,
|
||||
FocusHandle, Focusable, InteractiveElement, IntoElement, ListAlignment, ListState, ObjectFit,
|
||||
ParentElement, PathPromptOptions, Render, SharedString, StatefulInteractiveElement, Styled,
|
||||
StyledImage, Subscription, Window,
|
||||
};
|
||||
use nostr_sdk::prelude::*;
|
||||
use serde::Deserialize;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use smol::fs;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
@@ -24,7 +26,15 @@ use ui::{
|
||||
v_flex, ContextModal, Disableable, Icon, IconName, Size, StyledExt,
|
||||
};
|
||||
|
||||
use crate::views::subject;
|
||||
|
||||
const ALERT: &str = "has not set up Messaging (DM) Relays, so they will NOT receive your messages.";
|
||||
const DESC: &str = "This conversation is private. Only members can see each other's messages.";
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
||||
pub struct ChangeSubject(pub String);
|
||||
|
||||
impl_internal_actions!(chat, [ChangeSubject]);
|
||||
|
||||
pub fn init(id: &u64, window: &mut Window, cx: &mut App) -> Result<Arc<Entity<Chat>>, Error> {
|
||||
if let Some(room) = ChatRegistry::global(cx).read(cx).room(id, cx) {
|
||||
@@ -99,7 +109,7 @@ impl Chat {
|
||||
this.update(cx, |this, cx| {
|
||||
this.render_message(ix, window, cx).into_any_element()
|
||||
})
|
||||
.unwrap()
|
||||
.unwrap_or(Empty.into_any())
|
||||
}
|
||||
});
|
||||
|
||||
@@ -216,7 +226,7 @@ impl Chat {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable input when sending message
|
||||
// temporarily disable message input
|
||||
self.input.update(cx, |this, cx| {
|
||||
this.set_loading(true, window, cx);
|
||||
this.set_disabled(true, window, cx);
|
||||
@@ -226,27 +236,38 @@ impl Chat {
|
||||
let task = room.send_message(content, cx);
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
if let Ok(msgs) = task.await {
|
||||
cx.update(|window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
// Reset message input
|
||||
cx.update_entity(&this.input, |this, cx| {
|
||||
this.set_loading(false, window, cx);
|
||||
this.set_disabled(false, window, cx);
|
||||
this.set_text("", window, cx);
|
||||
cx.notify();
|
||||
});
|
||||
match task.await {
|
||||
Ok(reports) => {
|
||||
cx.update(|window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
// Reset message input
|
||||
this.input.update(cx, |this, cx| {
|
||||
this.set_loading(false, window, cx);
|
||||
this.set_disabled(false, window, cx);
|
||||
this.set_text("", window, cx);
|
||||
cx.notify();
|
||||
});
|
||||
})
|
||||
.ok();
|
||||
|
||||
for item in reports.into_iter() {
|
||||
window.push_notification(
|
||||
Notification::error(item).title("Message Failed to Send"),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
|
||||
for item in msgs.into_iter() {
|
||||
}
|
||||
Err(e) => {
|
||||
cx.update(|window, cx| {
|
||||
window.push_notification(
|
||||
Notification::error(item).title("Message Failed to Send"),
|
||||
Notification::error(e.to_string()).title("Message Failed to Send"),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
@@ -265,12 +286,15 @@ impl Chat {
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
match Flatten::flatten(paths.await.map_err(|e| e.into())) {
|
||||
Ok(Some(mut paths)) => {
|
||||
let path = paths.pop().unwrap();
|
||||
let Some(path) = paths.pop() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Ok(file_data) = fs::read(path).await {
|
||||
let client = get_client();
|
||||
let (tx, rx) = oneshot::channel::<Url>();
|
||||
|
||||
// spawn task via async_utility
|
||||
spawn(async move {
|
||||
if let Ok(url) = nip96_upload(client, file_data).await {
|
||||
_ = tx.send(url);
|
||||
@@ -280,7 +304,6 @@ impl Chat {
|
||||
if let Ok(url) = rx.await {
|
||||
cx.update(|_, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
// Stop loading spinner
|
||||
this.set_loading(false, cx);
|
||||
|
||||
this.attaches.update(cx, |this, cx| {
|
||||
@@ -299,13 +322,13 @@ impl Chat {
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
// Stop loading spinner
|
||||
if let Some(view) = this.upgrade() {
|
||||
cx.update_entity(&view, |this, cx| {
|
||||
cx.update(|_, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.set_loading(false, cx);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
.ok();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
@@ -316,9 +339,10 @@ impl Chat {
|
||||
fn remove_media(&mut self, url: &Url, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.attaches.update(cx, |model, cx| {
|
||||
if let Some(urls) = model.as_mut() {
|
||||
let ix = urls.iter().position(|x| x == url).unwrap();
|
||||
urls.remove(ix);
|
||||
cx.notify();
|
||||
if let Some(ix) = urls.iter().position(|x| x == url) {
|
||||
urls.remove(ix);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -334,10 +358,10 @@ impl Chat {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
const ROOM_DESCRIPTION: &str =
|
||||
"This conversation is private. Only members of this chat can see each other's messages.";
|
||||
let Some(message) = self.messages.read(cx).get(ix) else {
|
||||
return div().into_element();
|
||||
};
|
||||
|
||||
let message = self.messages.read(cx).get(ix).unwrap();
|
||||
let text_data = &mut self.text_data;
|
||||
|
||||
div()
|
||||
@@ -427,7 +451,7 @@ impl Chat {
|
||||
.size_10()
|
||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::THREE)),
|
||||
)
|
||||
.child(ROOM_DESCRIPTION),
|
||||
.child(DESC),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -472,8 +496,28 @@ impl Panel for Chat {
|
||||
menu.track_focus(&self.focus_handle)
|
||||
}
|
||||
|
||||
fn toolbar_buttons(&self, _window: &Window, _cx: &App) -> Vec<Button> {
|
||||
vec![]
|
||||
fn toolbar_buttons(&self, _window: &Window, cx: &App) -> Vec<Button> {
|
||||
let id = self.room.read(cx).id;
|
||||
let subject = self
|
||||
.room
|
||||
.read(cx)
|
||||
.subject
|
||||
.as_ref()
|
||||
.map(|subject| subject.to_string());
|
||||
|
||||
let button = Button::new("subject")
|
||||
.icon(IconName::EditFill)
|
||||
.tooltip("Change Subject")
|
||||
.on_click(move |_, window, cx| {
|
||||
let subject = subject::init(id, subject.clone(), window, cx);
|
||||
|
||||
window.open_modal(cx, move |this, _window, _cx| {
|
||||
this.title("Change the subject of the conversation")
|
||||
.child(subject.clone())
|
||||
});
|
||||
});
|
||||
|
||||
vec![button]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,4 +7,5 @@ pub mod onboarding;
|
||||
pub mod profile;
|
||||
pub mod relays;
|
||||
pub mod sidebar;
|
||||
pub mod subject;
|
||||
pub mod welcome;
|
||||
|
||||
@@ -283,8 +283,7 @@ impl Render for Relays {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.track_focus(&self.focus_handle)
|
||||
.w_full()
|
||||
.h_full()
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.justify_between()
|
||||
|
||||
111
crates/coop/src/views/subject.rs
Normal file
111
crates/coop/src/views/subject.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use chats::ChatRegistry;
|
||||
use gpui::{
|
||||
div, App, AppContext, Context, Entity, FocusHandle, InteractiveElement, IntoElement,
|
||||
ParentElement, Render, Styled, Window,
|
||||
};
|
||||
use ui::{
|
||||
button::{Button, ButtonVariants},
|
||||
input::TextInput,
|
||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||
ContextModal, Size,
|
||||
};
|
||||
|
||||
pub fn init(
|
||||
id: u64,
|
||||
subject: Option<String>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<Subject> {
|
||||
Subject::new(id, subject, window, cx)
|
||||
}
|
||||
|
||||
pub struct Subject {
|
||||
id: u64,
|
||||
input: Entity<TextInput>,
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl Subject {
|
||||
pub fn new(
|
||||
id: u64,
|
||||
subject: Option<String>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<Self> {
|
||||
let input = cx.new(|cx| {
|
||||
let mut this = TextInput::new(window, cx).text_size(Size::Small);
|
||||
if let Some(text) = subject.clone() {
|
||||
this.set_text(text, window, cx);
|
||||
} else {
|
||||
this.set_placeholder("prepare for holidays...");
|
||||
}
|
||||
this
|
||||
});
|
||||
|
||||
cx.new(|cx| Self {
|
||||
id,
|
||||
input,
|
||||
focus_handle: cx.focus_handle(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let registry = ChatRegistry::global(cx).read(cx);
|
||||
let subject = self.input.read(cx).text();
|
||||
|
||||
if subject.is_empty() {
|
||||
window.push_notification("Subject cannot be empty", cx);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(room) = registry.room(&self.id, cx) {
|
||||
room.update(cx, |this, cx| {
|
||||
this.subject = Some(subject);
|
||||
cx.notify();
|
||||
});
|
||||
window.close_modal(cx);
|
||||
} else {
|
||||
window.push_notification("Room not found", cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Subject {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
const HELP_TEXT: &str = "Subject will be updated when you send a message.";
|
||||
|
||||
div()
|
||||
.track_focus(&self.focus_handle)
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
||||
.child("Subject:"),
|
||||
)
|
||||
.child(self.input.clone())
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.italic()
|
||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::NINE))
|
||||
.child(HELP_TEXT),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Button::new("submit")
|
||||
.label("Change")
|
||||
.primary()
|
||||
.w_full()
|
||||
.on_click(cx.listener(|this, _, window, cx| this.update(window, cx))),
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user