From 3ac17ddd9f8539233baaea79d3c2e6e51efb9e8e Mon Sep 17 00:00:00 2001 From: Bnyro Date: Wed, 21 Jun 2023 16:36:24 +0200 Subject: [PATCH] Support for editing comments --- src/components/comment_row.rs | 38 +++++++++++++++---- src/components/community_page.rs | 8 ++-- src/components/post_page.rs | 63 +++++++++++++++++++++++++------- src/components/post_row.rs | 8 ++-- src/dialogs/editor.rs | 31 +++++++++------- 5 files changed, 106 insertions(+), 42 deletions(-) diff --git a/src/components/comment_row.rs b/src/components/comment_row.rs index 0571d3d..4cc6ead 100644 --- a/src/components/comment_row.rs +++ b/src/components/comment_row.rs @@ -4,16 +4,18 @@ use gtk::prelude::*; use relm4_components::web_image::WebImage; use crate::api; +use crate::dialogs::editor::EditorData; use crate::util::get_web_image_url; use crate::util::markdown_to_pango_markup; use crate::settings; +use super::post_page::PostInput; use super::voting_row::VotingRowModel; use super::voting_row::VotingStats; #[derive(Debug)] pub struct CommentRow { - comment: CommentView, + pub comment: CommentView, avatar: Controller, voting_row: Controller } @@ -22,16 +24,17 @@ pub struct CommentRow { pub enum CommentRowMsg { OpenPerson, DeleteComment, + OpenEditCommentDialog } #[relm4::factory(pub)] impl FactoryComponent for CommentRow { type Init = CommentView; type Input = CommentRowMsg; - type Output = crate::AppMsg; + type Output = PostInput; type CommandOutput = (); type Widgets = PostViewWidgets; - type ParentInput = crate::AppMsg; + type ParentInput = PostInput; type ParentWidget = gtk::Box; view! { @@ -64,9 +67,11 @@ impl FactoryComponent for CommentRow { }, gtk::Label { + #[watch] set_markup: &markdown_to_pango_markup(self.comment.comment.content.clone()), set_halign: gtk::Align::Start, set_use_markup: true, + set_selectable: true, }, gtk::Box { @@ -74,6 +79,16 @@ impl FactoryComponent for CommentRow { #[local_ref] voting_row -> gtk::Box {}, + if self.comment.creator.id.0 == settings::get_current_account().id { + gtk::Button { + set_icon_name: "document-edit", + connect_clicked => CommentRowMsg::OpenEditCommentDialog, + set_margin_start: 5, + } + } else { + gtk::Box {} + }, + if self.comment.creator.id.0 == settings::get_current_account().id { gtk::Button { set_icon_name: "edit-delete", @@ -94,8 +109,8 @@ impl FactoryComponent for CommentRow { } fn init_model(value: Self::Init, _index: &DynamicIndex, _sender: FactorySender) -> Self { - let avatar = WebImage::builder().launch(get_web_image_url(value.community.clone().icon)).detach(); - let voting_row = VotingRowModel::builder().launch(VotingStats::from_comment(value.clone().counts, value.my_vote)).detach(); + let avatar = WebImage::builder().launch(get_web_image_url(value.creator.avatar.clone())).detach(); + let voting_row = VotingRowModel::builder().launch(VotingStats::from_comment(value.counts.clone(), value.my_vote)).detach(); Self { comment: value, avatar, voting_row } } @@ -116,15 +131,24 @@ impl FactoryComponent for CommentRow { fn update(&mut self, message: Self::Input, sender: FactorySender) { match message { CommentRowMsg::OpenPerson => { - sender.output(crate::AppMsg::OpenPerson(self.comment.creator.name.clone())) + sender.output(PostInput::PassAppMessage(crate::AppMsg::OpenPerson(self.comment.creator.name.clone()))); } CommentRowMsg::DeleteComment => { let comment_id = self.comment.comment.id; std::thread::spawn(move || { let _ = api::comment::delete_comment(comment_id); - let _ = sender.output(crate::AppMsg::StartFetchPosts(None)); + let _ = sender.output(PostInput::PassAppMessage(crate::AppMsg::StartFetchPosts(None))); }); } + CommentRowMsg::OpenEditCommentDialog => { + let data = EditorData { + name: String::from(""), + body: self.comment.comment.content.clone(), + url: None, + id: Some(self.comment.comment.id.0), + }; + sender.output(PostInput::OpenEditCommentDialog(data)); + } } } } \ No newline at end of file diff --git a/src/components/community_page.rs b/src/components/community_page.rs index 660699a..a995b5e 100644 --- a/src/components/community_page.rs +++ b/src/components/community_page.rs @@ -1,4 +1,4 @@ -use crate::{util::markdown_to_pango_markup, dialogs::editor::{EditorDialog, DialogMsg, EditorOutput, DialogType, EditorData}}; +use crate::{util::markdown_to_pango_markup, dialogs::editor::{EditorDialog, DialogMsg, EditorOutput, EditorType, EditorData}}; use lemmy_api_common::{lemmy_db_views::structs::PostView, lemmy_db_views_actor::structs::CommunityView, lemmy_db_schema::SubscribedType}; use relm4::{prelude::*, factory::FactoryVecDeque, MessageBroker}; use gtk::prelude::*; @@ -58,7 +58,7 @@ impl SimpleComponent for CommunityPage { }, gtk::Label { #[watch] - set_markup: &markdown_to_pango_markup(model.info.clone().community.description.unwrap_or("".to_string())), + set_markup: &markdown_to_pango_markup(model.info.community.description.clone().unwrap_or("".to_string())), set_use_markup: true, }, gtk::Box { @@ -124,9 +124,9 @@ impl SimpleComponent for CommunityPage { let dialog = EditorDialog::builder() .transient_for(root) - .launch_with_broker(DialogType::Post, &COMMUNITY_PAGE_BROKER) + .launch_with_broker(EditorType::Post, &COMMUNITY_PAGE_BROKER) .forward(sender.input_sender(), |msg| match msg { - EditorOutput::CreateRequest(post) => CommunityInput::CreatePostRequest(post), + EditorOutput::CreateRequest(post, _) => CommunityInput::CreatePostRequest(post), _ => CommunityInput::None }); diff --git a/src/components/post_page.rs b/src/components/post_page.rs index e55ed47..211b16f 100644 --- a/src/components/post_page.rs +++ b/src/components/post_page.rs @@ -3,7 +3,7 @@ use relm4::{prelude::*, factory::FactoryVecDeque, MessageBroker}; use gtk::prelude::*; use relm4_components::web_image::WebImage; -use crate::{api, util::{get_web_image_msg, get_web_image_url, markdown_to_pango_markup}, dialogs::editor::{EditorDialog, EditorOutput, DialogMsg, DialogType, EditorData}, settings}; +use crate::{api, util::{get_web_image_msg, get_web_image_url, markdown_to_pango_markup}, dialogs::editor::{EditorDialog, EditorOutput, DialogMsg, EditorType, EditorData}, settings}; use super::{comment_row::CommentRow, voting_row::{VotingRowModel, VotingStats, VotingRowInput}}; @@ -31,9 +31,13 @@ pub enum PostInput { CreateCommentRequest(EditorData), EditPostRequest(EditorData), CreatedComment(CommentView), - EditPost, + OpenEditPostDialog, + OpenEditCommentDialog(EditorData), DeletePost, - DoneEditPost(PostView) + DoneEditPost(PostView), + PassAppMessage(crate::AppMsg), + EditCommentRequest(EditorData), + UpdateComment(CommentView), } #[relm4::component(pub)] @@ -125,8 +129,8 @@ impl SimpleComponent for PostPage { if model.info.post_view.creator.id.0 == settings::get_current_account().id { gtk::Button { set_icon_name: "document-edit", - connect_clicked => PostInput::EditPost, - set_margin_start: 10, + connect_clicked => PostInput::OpenEditPostDialog, + set_margin_start: 5, } } else { gtk::Box {} @@ -136,7 +140,7 @@ impl SimpleComponent for PostPage { gtk::Button { set_icon_name: "edit-delete", connect_clicked => PostInput::DeletePost, - set_margin_start: 10, + set_margin_start: 5, } } else { gtk::Box {} @@ -180,15 +184,18 @@ impl SimpleComponent for PostPage { sender: relm4::ComponentSender, ) -> relm4::ComponentParts { let image = WebImage::builder().launch("".to_string()).detach(); - let comments = FactoryVecDeque::new(gtk::Box::default(), sender.output_sender()); + let comments = FactoryVecDeque::new(gtk::Box::default(), sender.input_sender()); let creator_avatar = WebImage::builder().launch("".to_string()).detach(); let community_avatar = WebImage::builder().launch("".to_string()).detach(); let dialog = EditorDialog::builder() .transient_for(root) - .launch_with_broker(DialogType::Comment, &POST_PAGE_BROKER) + .launch_with_broker(EditorType::Comment, &POST_PAGE_BROKER) .forward(sender.input_sender(), |msg| match msg { - EditorOutput::CreateRequest(comment) => PostInput::CreateCommentRequest(comment), - EditorOutput::EditRequest(post) => PostInput::EditPostRequest(post) + EditorOutput::CreateRequest(comment, _) => PostInput::CreateCommentRequest(comment), + EditorOutput::EditRequest(post, type_) => match type_ { + EditorType::Post => PostInput::EditPostRequest(post), + EditorType::Comment => PostInput::EditCommentRequest(post) + } }); let voting_row = VotingRowModel::builder().launch(VotingStats::default()).detach(); let model = PostPage { info: init, image, comments, creator_avatar, community_avatar, create_comment_dialog: dialog, voting_row }; @@ -249,7 +256,8 @@ impl SimpleComponent for PostPage { gtk::show_uri(None::<&relm4::gtk::Window>, &link, 0); } PostInput::OpenCreateCommentDialog => { - POST_PAGE_BROKER.send(DialogMsg::Show) + POST_PAGE_BROKER.send(DialogMsg::UpdateType(EditorType::Comment, true)); + POST_PAGE_BROKER.send(DialogMsg::Show); } PostInput::CreatedComment(comment) => { self.comments.guard().push_front(comment); @@ -271,7 +279,7 @@ impl SimpleComponent for PostPage { let _ = sender.output(crate::AppMsg::StartFetchPosts(None)); }); } - PostInput::EditPost => { + PostInput::OpenEditPostDialog => { let url = match self.info.post_view.post.url.clone() { Some(url) => url.to_string(), None => String::from("") @@ -280,9 +288,10 @@ impl SimpleComponent for PostPage { name: self.info.post_view.post.name.clone(), body: self.info.post_view.post.body.clone().unwrap_or(String::from("")), url: reqwest::Url::parse(&url).ok(), + id: None, }; POST_PAGE_BROKER.send(DialogMsg::UpdateData(data)); - POST_PAGE_BROKER.send(DialogMsg::UpdateType(DialogType::Post, false)); + POST_PAGE_BROKER.send(DialogMsg::UpdateType(EditorType::Post, false)); POST_PAGE_BROKER.send(DialogMsg::Show) } PostInput::EditPostRequest(post) => { @@ -298,6 +307,34 @@ impl SimpleComponent for PostPage { PostInput::DoneEditPost(post) => { self.info.post_view = post; } + PostInput::OpenEditCommentDialog(data) => { + POST_PAGE_BROKER.send(DialogMsg::UpdateData(data)); + POST_PAGE_BROKER.send(DialogMsg::UpdateType(EditorType::Comment, false)); + POST_PAGE_BROKER.send(DialogMsg::Show); + } + PostInput::EditCommentRequest(data) => { + std::thread::spawn(move || { + let message = match api::comment::edit_comment(data.body, data.id.unwrap()) { + Ok(comment) => Some(PostInput::UpdateComment(comment.comment_view)), + Err(err) => { println!("{}", err.to_string()); None } + }; + if let Some(message) = message { sender.input(message) }; + }); + } + PostInput::UpdateComment(comment) => { + let mut index = 0; + let id = comment.comment.id; + loop { + if self.comments.guard().get(index).unwrap().comment.comment.id == id { + self.comments.guard().get_mut(index).unwrap().comment = comment; + break; + } + index += 1; + } + } + PostInput::PassAppMessage(message) => { + let _ = sender.output(message); + } } } } diff --git a/src/components/post_row.rs b/src/components/post_row.rs index 4941c0f..299e1c8 100644 --- a/src/components/post_row.rs +++ b/src/components/post_row.rs @@ -104,7 +104,7 @@ impl FactoryComponent for PostRow { }, gtk::Label { set_halign: gtk::Align::Start, - set_text: &format!("{} comments", self.post.clone().counts.comments), + set_text: &format!("{} comments", self.post.counts.comments.clone()), }, if self.post.creator.id.0 == settings::get_current_account().id { gtk::Button { @@ -126,9 +126,9 @@ impl FactoryComponent for PostRow { fn forward_to_parent(output: Self::Output) -> Option { Some(output) } fn init_model(value: Self::Init, _index: &DynamicIndex, _sender: FactorySender) -> Self { - let author_image= WebImage::builder().launch(get_web_image_url(value.creator.clone().avatar)).detach(); - let community_image= WebImage::builder().launch(get_web_image_url(value.creator.clone().avatar)).detach(); - let voting_row = VotingRowModel::builder().launch(VotingStats::from_post(value.clone().counts, value.my_vote)).detach(); + let author_image= WebImage::builder().launch(get_web_image_url(value.creator.avatar.clone())).detach(); + let community_image= WebImage::builder().launch(get_web_image_url(value.community.icon.clone())).detach(); + let voting_row = VotingRowModel::builder().launch(VotingStats::from_post(value.counts.clone(), value.my_vote)).detach(); Self { post: value, author_image, community_image, voting_row } } diff --git a/src/dialogs/editor.rs b/src/dialogs/editor.rs index e3cab61..f6e249a 100644 --- a/src/dialogs/editor.rs +++ b/src/dialogs/editor.rs @@ -6,19 +6,22 @@ pub struct EditorData { pub name: String, pub body: String, pub url: Option, + pub id: Option } pub struct EditorDialog { - type_: DialogType, + type_: EditorType, is_new: bool, visible: bool, name_buffer: gtk::EntryBuffer, url_buffer: gtk::EntryBuffer, body_buffer: gtk::TextBuffer, + // Optional field to temporarily store the post or comment id + id: Option } #[derive(Debug, Clone, Copy)] -pub enum DialogType { +pub enum EditorType { Post, Comment } @@ -27,20 +30,20 @@ pub enum DialogType { pub enum DialogMsg { Show, Hide, - UpdateType(DialogType, bool), + UpdateType(EditorType, bool), UpdateData(EditorData), Okay } #[derive(Debug)] pub enum EditorOutput { - CreateRequest(EditorData), - EditRequest(EditorData) + CreateRequest(EditorData, EditorType), + EditRequest(EditorData, EditorType) } #[relm4::component(pub)] impl SimpleComponent for EditorDialog { - type Init = DialogType; + type Init = EditorType; type Input = DialogMsg; type Output = EditorOutput; @@ -58,13 +61,13 @@ impl SimpleComponent for EditorDialog { set_margin_all: 20, match model.type_ { - DialogType::Post => { + EditorType::Post => { gtk::Box { set_orientation: gtk::Orientation::Vertical, gtk::Label { set_halign: gtk::Align::Center, set_valign: gtk::Align::Center, - set_label: "Create post", + set_label: "Post content", add_css_class: "font-bold" }, gtk::Entry { @@ -81,12 +84,12 @@ impl SimpleComponent for EditorDialog { }, } } - DialogType::Comment => { + EditorType::Comment => { gtk::Box { gtk::Label { set_halign: gtk::Align::Center, set_valign: gtk::Align::Center, - set_label: "Create comment", + set_label: "Comment content", add_css_class: "font-bold" }, } @@ -133,7 +136,7 @@ impl SimpleComponent for EditorDialog { let name_buffer = gtk::EntryBuffer::builder().build(); let url_buffer = gtk::EntryBuffer::builder().build(); let body_buffer = gtk::TextBuffer::builder().build(); - let model = EditorDialog { type_: init, visible: false, is_new: true, name_buffer, url_buffer, body_buffer }; + let model = EditorDialog { type_: init, visible: false, is_new: true, name_buffer, url_buffer, body_buffer, id: None }; let widgets = view_output!(); ComponentParts { model, widgets } } @@ -153,10 +156,10 @@ impl SimpleComponent for EditorDialog { let (start, end) = &self.body_buffer.bounds(); let body = self.body_buffer.text(start, end, true).to_string(); let url = reqwest::Url::parse(&url).ok(); - let post = EditorData { name, body, url }; + let post = EditorData { name, body, url, id: self.id }; let message = match self.is_new { - true => EditorOutput::CreateRequest(post), - false => EditorOutput::EditRequest(post) + true => EditorOutput::CreateRequest(post, self.type_), + false => EditorOutput::EditRequest(post, self.type_) }; let _ = sender.output(message); self.visible = false;