Support for editing comments

This commit is contained in:
Bnyro 2023-06-21 16:36:24 +02:00
parent b56776b671
commit 3ac17ddd9f
5 changed files with 106 additions and 42 deletions

View File

@ -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<WebImage>,
voting_row: Controller<VotingRowModel>
}
@ -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>) -> 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<Self>) {
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));
}
}
}
}

View File

@ -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
});

View File

@ -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<Self>,
) -> relm4::ComponentParts<Self> {
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);
}
}
}
}

View File

@ -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<Self::ParentInput> { Some(output) }
fn init_model(value: Self::Init, _index: &DynamicIndex, _sender: FactorySender<Self>) -> 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 }
}

View File

@ -6,19 +6,22 @@ pub struct EditorData {
pub name: String,
pub body: String,
pub url: Option<reqwest::Url>,
pub id: Option<i32>
}
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<i32>
}
#[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;