Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(chat): Impl editing last message key shortcut #1950

Merged
merged 3 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions kit/src/layout/chatbar/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use crate::{
};

use common::{icons, language::get_local_text, warp_runner::thumbnail_to_base64};

pub type To = &'static str;

pub enum SuggestionType {
Expand Down Expand Up @@ -77,6 +76,7 @@ pub struct Props<'a> {
suggestions: &'a SuggestionType,
oncursor_update: Option<EventHandler<'a, (String, i64)>>,
on_suggestion_click: Option<EventHandler<'a, (String, String, i64)>>,
onup_down_arrow: Option<EventHandler<'a, Code>>,
}

#[derive(Props)]
Expand Down Expand Up @@ -175,6 +175,12 @@ pub fn Chatbar<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
let selected_suggestion: &UseRef<Option<usize>> = use_ref(cx, || None);
let arrow_selected = use_ref(cx, || false);
let is_suggestion_modal_closed: &UseRef<bool> = use_ref(cx, || false);
let has_value = cx
.props
.value
.as_ref()
.map(|s| !s.is_empty())
.unwrap_or_default();
let eval = use_eval(cx);

cx.render(rsx!(
Expand Down Expand Up @@ -237,7 +243,7 @@ pub fn Chatbar<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
*cursor_position.write_silent() = Some(p)
},
is_disabled: cx.props.is_disabled,
prevent_up_down_arrows: !cx.props.suggestions.is_empty(),
prevent_up_down_arrows: !cx.props.suggestions.is_empty() || !has_value,
onup_down_arrow:
move |code| {
let amount = match cx.props.suggestions {
Expand All @@ -247,6 +253,11 @@ pub fn Chatbar<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
};
if amount == 0 {
*selected_suggestion.write_silent() = None;
if let SuggestionType::None = cx.props.suggestions {
if let Some(e) = &cx.props.onup_down_arrow {
e.call(code);
}
}
return;
}
let current = &mut *selected_suggestion.write();
Expand Down
35 changes: 28 additions & 7 deletions ui/src/layouts/chats/data/chat_data/active_chat/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use chrono::{DateTime, Utc};
use common::warp_runner::ui_adapter;
use std::collections::{HashMap, HashSet, VecDeque};
use uuid::Uuid;
use warp::crypto::DID;

use crate::layouts::chats::data::DEFAULT_MESSAGES_TO_TAKE;

Expand All @@ -15,13 +16,19 @@ pub struct Messages {
pub loaded: HashSet<Uuid>,
// used for displayed_messages
pub times: HashMap<Uuid, DateTime<Utc>>,
pub last_user_msg: Option<Uuid>,
}

impl Messages {
pub fn new(mut m: VecDeque<ui_adapter::Message>) -> Self {
pub fn new(own: DID, mut m: VecDeque<ui_adapter::Message>) -> Self {
let mut message_times = HashMap::new();
let mut messages = VecDeque::new();
let displayed = VecDeque::new();
let last_user_msg = m
.iter()
.rev()
.find(|msg| msg.inner.sender().eq(&own))
.map(|msg| msg.inner.id());
for msg in m.drain(..) {
message_times.insert(msg.inner.id(), msg.inner.date());
messages.push_back(msg);
Expand All @@ -32,6 +39,7 @@ impl Messages {
displayed,
loaded: HashSet::new(),
times: message_times,
last_user_msg,
}
}

Expand All @@ -46,7 +54,7 @@ impl Messages {
self.displayed.clear();
}

pub fn insert_messages(&mut self, m: Vec<ui_adapter::Message>) {
pub fn insert_messages(&mut self, own: DID, m: Vec<ui_adapter::Message>) {
if m.is_empty() {
return;
}
Expand All @@ -69,19 +77,19 @@ impl Messages {

if self.all.is_empty() {
log::trace!("appending messages");
return self.append_messages(m);
return self.append_messages(own, m);
}

// latest last
if m.last().unwrap().inner.date() <= self.all.front().unwrap().inner.date() {
log::trace!("appending messages");
return self.prepend_messages(m);
return self.prepend_messages(own, m);
}

// earliest first
if m.first().unwrap().inner.date() >= self.all.back().unwrap().inner.date() {
log::trace!("appending messages");
return self.append_messages(m);
return self.append_messages(own, m);
}

log::warn!("insert_messages: invalid insert");
Expand Down Expand Up @@ -189,7 +197,12 @@ impl Messages {
}

impl Messages {
fn append_messages(&mut self, mut m: Vec<ui_adapter::Message>) {
fn append_messages(&mut self, own: DID, mut m: Vec<ui_adapter::Message>) {
self.last_user_msg = m
.iter()
.rev()
.find(|msg| msg.inner.sender().eq(&own))
.map(|msg| msg.inner.id());
m.retain(|x| !self.times.contains_key(&x.inner.id()));
for msg in m.iter() {
self.times.insert(msg.inner.id(), msg.inner.date());
Expand All @@ -205,7 +218,15 @@ impl Messages {
}
}

fn prepend_messages(&mut self, mut m: Vec<ui_adapter::Message>) {
fn prepend_messages(&mut self, own: DID, mut m: Vec<ui_adapter::Message>) {
// Calculate last user message
if self.last_user_msg.is_none() {
self.last_user_msg = m
.iter()
.rev()
.find(|msg| msg.inner.sender().eq(&own))
.map(|msg| msg.inner.id());
}
m.retain(|x| !self.times.contains_key(&x.inner.id()));
for msg in m.iter() {
self.times.insert(msg.inner.id(), msg.inner.date());
Expand Down
2 changes: 1 addition & 1 deletion ui/src/layouts/chats/data/chat_data/active_chat/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl ActiveChat {
) -> Self {
Self {
metadata: Metadata::new(s, chat),
messages: Messages::new(messages),
messages: Messages::new(s.did_key(), messages),
is_initialized: false,
key: Uuid::new_v4(),
}
Expand Down
13 changes: 11 additions & 2 deletions ui/src/layouts/chats/data/chat_data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ pub struct MessagesToSend {
pub messages_to_send: Vec<(Option<String>, Vec<FileLocation>)>,
}

#[derive(Clone, Default)]
pub struct MessagesToEdit {
pub edit: Option<Uuid>,
}

impl PartialEq for ChatData {
fn eq(&self, _other: &Self) -> bool {
false
Expand Down Expand Up @@ -162,7 +167,9 @@ impl ChatData {
return;
}

self.active_chat.messages.insert_messages(messages);
self.active_chat
.messages
.insert_messages(self.active_chat.my_id().did_key(), messages);
}

pub fn is_loaded(&self, conv_id: Uuid) -> bool {
Expand Down Expand Up @@ -191,7 +198,9 @@ impl ChatData {
}

if should_append_msg {
self.active_chat.messages.insert_messages(vec![msg]);
self.active_chat
.messages
.insert_messages(self.active_chat.my_id().did_key(), vec![msg]);
true
} else {
if let Some(behavior) = behavior {
Expand Down
3 changes: 2 additions & 1 deletion ui/src/layouts/chats/presentation/chat/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use kit::{
use crate::{
components::media::calling::CallControl,
layouts::chats::{
data::{self, ChatData, MessagesToSend, ScrollBtn},
data::{self, ChatData, MessagesToEdit, MessagesToSend, ScrollBtn},
presentation::{
chat::{edit_group::EditGroup, group_settings::GroupSettings, group_users::GroupUsers},
chatbar::get_chatbar,
Expand All @@ -40,6 +40,7 @@ pub fn Compose(cx: Scope) -> Element {
use_shared_state_provider(cx, ChatData::default);
use_shared_state_provider(cx, ScrollBtn::new);
use_shared_state_provider(cx, MessagesToSend::default);
use_shared_state_provider(cx, MessagesToEdit::default);
let state = use_shared_state::<State>(cx)?;
let chat_data = use_shared_state::<ChatData>(cx)?;

Expand Down
13 changes: 12 additions & 1 deletion ui/src/layouts/chats/presentation/chatbar/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ use crate::{
components::{files::attachments::Attachments, shortcuts},
layouts::{
chats::{
data::{ChatData, ChatProps, MessagesToSend, MsgChInput, ScrollBtn, TypingIndicator},
data::{
ChatData, ChatProps, MessagesToEdit, MessagesToSend, MsgChInput, ScrollBtn,
TypingIndicator,
},
scripts::SHOW_CONTEXT,
},
storage::send_files_layout::{modal::SendFilesLayoutModal, SendFilesStartLocation},
Expand All @@ -62,6 +65,7 @@ pub fn get_chatbar<'a>(cx: &'a Scoped<'a, ChatProps>) -> Element<'a> {
let chat_data = use_shared_state::<ChatData>(cx)?;
let scroll_btn = use_shared_state::<ScrollBtn>(cx)?;
let to_send = use_shared_state::<MessagesToSend>(cx)?;
let edit_msg = use_shared_state::<MessagesToEdit>(cx)?;
state.write_silent().scope_ids.chatbar = Some(cx.scope_id().0);

let active_chat_id = chat_data.read().active_chat.id();
Expand Down Expand Up @@ -437,6 +441,13 @@ pub fn get_chatbar<'a>(cx: &'a Scoped<'a, ChatProps>) -> Element<'a> {
suggestions.set(SuggestionType::None);
}
},
onup_down_arrow: move |code|{
if code == Code::ArrowUp && edit_msg.read().edit.is_none() {
if let Some(msg) = chat_data.read().active_chat.messages.last_user_msg {
edit_msg.write().edit = Some(msg);
}
}
},
controls: cx.render(
rsx!(
Button {
Expand Down
25 changes: 13 additions & 12 deletions ui/src/layouts/chats/presentation/messages/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ use crate::{
components::emoji_group::EmojiGroup,
layouts::{
chats::{
data::{self, ChatData, MessagesToSend, ScrollBtn},
data::{self, ChatData, MessagesToEdit, MessagesToSend, ScrollBtn},
scripts,
},
storage::files_layout::file_preview::open_file_preview_modal,
Expand Down Expand Up @@ -343,7 +343,7 @@ struct MessagesProps<'a> {
}
fn wrap_messages_in_context_menu<'a>(cx: Scope<'a, MessagesProps<'a>>) -> Element<'a> {
let state = use_shared_state::<State>(cx)?;
let edit_msg: &UseState<Option<Uuid>> = use_state(cx, || None);
let edit_msg = use_shared_state::<MessagesToEdit>(cx)?;
// see comment in ContextMenu about this variable.
let reacting_to: &UseState<Option<Uuid>> = use_state(cx, || None);

Expand All @@ -362,7 +362,7 @@ fn wrap_messages_in_context_menu<'a>(cx: Scope<'a, MessagesProps<'a>>) -> Elemen

// WARNING: these keys are required to prevent a bug with the context menu, which manifests when deleting messages.
let is_editing = edit_msg
.get()
.read().edit
.map(|id| !cx.props.is_remote && (id == message.inner.id()))
.unwrap_or(false);
let message_key = format!("{}-{:?}", &message.key, is_editing);
Expand All @@ -376,7 +376,7 @@ fn wrap_messages_in_context_menu<'a>(cx: Scope<'a, MessagesProps<'a>>) -> Elemen
message: grouped_message,
is_remote: cx.props.is_remote,
message_key: message_key,
edit_msg: edit_msg.clone(),
edit_msg: edit_msg,
pending: cx.props.pending
});
}
Expand All @@ -390,7 +390,7 @@ fn wrap_messages_in_context_menu<'a>(cx: Scope<'a, MessagesProps<'a>>) -> Elemen
message: grouped_message,
is_remote: cx.props.is_remote,
message_key: message_key,
edit_msg: edit_msg.clone(),
edit_msg: edit_msg,
pending: cx.props.pending
})),
items: cx.render(rsx!(
Expand Down Expand Up @@ -482,9 +482,9 @@ fn wrap_messages_in_context_menu<'a>(cx: Scope<'a, MessagesProps<'a>>) -> Elemen
aria_label: "messages-edit".into(),
text: get_local_text("messages.edit"),
should_render: !cx.props.is_remote
&& edit_msg.get().map(|id| id != msg_uuid).unwrap_or(true),
&& edit_msg.read().edit.map(|id| id != msg_uuid).unwrap_or(true),
onpress: move |_| {
edit_msg.set(Some(msg_uuid));
edit_msg.write().edit = Some(msg_uuid);
state.write().ui.ignore_focus = true;
}
},
Expand All @@ -493,9 +493,9 @@ fn wrap_messages_in_context_menu<'a>(cx: Scope<'a, MessagesProps<'a>>) -> Elemen
aria_label: "messages-cancel-edit".into(),
text: get_local_text("messages.cancel-edit"),
should_render: !cx.props.is_remote
&& edit_msg.get().map(|id| id == msg_uuid).unwrap_or(false),
&& edit_msg.read().edit.map(|id| id == msg_uuid).unwrap_or(false),
onpress: move |_| {
edit_msg.set(None);
edit_msg.write().edit = None;
state.write().ui.ignore_focus = false;
}
},
Expand All @@ -522,7 +522,7 @@ struct MessageProps<'a> {
message: &'a data::MessageGroupMsg,
is_remote: bool,
message_key: String,
edit_msg: UseState<Option<Uuid>>,
edit_msg: &'a UseSharedState<MessagesToEdit>,
pending: bool,
}
fn render_message<'a>(cx: Scope<'a, MessageProps<'a>>) -> Element<'a> {
Expand All @@ -548,7 +548,8 @@ fn render_message<'a>(cx: Scope<'a, MessageProps<'a>>) -> Element<'a> {
} = cx.props;
let message = &grouped_message.message;
let is_editing = edit_msg
.current()
.read()
.edit
.map(|id| !cx.props.is_remote && (id == message.inner.id()))
.unwrap_or(false);

Expand Down Expand Up @@ -693,7 +694,7 @@ fn render_message<'a>(cx: Scope<'a, MessageProps<'a>>) -> Element<'a> {
}
},
on_edit: move |update: String| {
edit_msg.set(None);
edit_msg.write().edit = None;
state.write().ui.ignore_focus = false;
let msg = update.split('\n').map(|x| x.to_string()).collect::<Vec<String>>();
if message.inner.lines() == msg || !msg.iter().any(|x| !x.trim().is_empty()) {
Expand Down
Loading