(Down-)voting posts and comments

This commit is contained in:
Bnyro 2023-06-20 17:13:14 +02:00
parent 99e122ed9a
commit 1e984b0765
6 changed files with 182 additions and 15 deletions

View File

@ -6,10 +6,14 @@ use relm4_components::web_image::WebImage;
use crate::util::get_web_image_url;
use crate::util::markdown_to_pango_markup;
use super::voting_row::VotingRowModel;
use super::voting_row::VotingStats;
#[derive(Debug)]
pub struct CommentRow {
comment: CommentView,
avatar: Controller<WebImage>
avatar: Controller<WebImage>,
voting_row: Controller<VotingRowModel>
}
#[derive(Debug)]
@ -62,10 +66,8 @@ impl FactoryComponent for CommentRow {
set_use_markup: true,
},
gtk::Label {
set_label: &format!("{} score", self.comment.counts.score),
set_halign: gtk::Align::Start,
},
#[local_ref]
voting_row -> gtk::Box {},
gtk::Separator {}
}
@ -77,8 +79,9 @@ 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();
Self { comment: value, avatar }
Self { comment: value, avatar, voting_row }
}
fn init_widgets(
@ -89,6 +92,7 @@ impl FactoryComponent for CommentRow {
sender: FactorySender<Self>,
) -> Self::Widgets {
let community_image = self.avatar.widget();
let voting_row = self.voting_row.widget();
let widgets = view_output!();
widgets
}

View File

@ -167,7 +167,7 @@ impl SimpleComponent for CommunityPage {
Ok(post) => Some(CommunityInput::CreatedPost(post.post_view)),
Err(err) => { println!("{}", err.to_string()); None }
};
if message.is_some() { sender.input(message.unwrap()) };
if let Some(message) = message { sender.input(message) };
});
}
CommunityInput::ToggleSubscription => {

View File

@ -4,3 +4,4 @@ pub mod profile_page;
pub mod community_page;
pub mod post_page;
pub mod comment_row;
pub mod voting_row;

View File

@ -5,7 +5,7 @@ use relm4_components::web_image::WebImage;
use crate::{api, util::{get_web_image_msg, get_web_image_url, markdown_to_pango_markup}, dialogs::create_post::{CreatePostDialog, CreatePostDialogOutput, DialogMsg, CREATE_COMMENT_DIALOG_BROKER, DialogType}};
use super::comment_row::CommentRow;
use super::{comment_row::CommentRow, voting_row::{VotingRowModel, VotingStats, VotingRowInput}};
pub struct PostPage {
info: GetPostResponse,
@ -14,7 +14,8 @@ pub struct PostPage {
community_avatar: Controller<WebImage>,
comments: FactoryVecDeque<CommentRow>,
#[allow(dead_code)]
create_comment_dialog: Controller<CreatePostDialog>
create_comment_dialog: Controller<CreatePostDialog>,
voting_row: Controller<VotingRowModel>
}
#[derive(Debug)]
@ -122,9 +123,13 @@ impl SimpleComponent for PostPage {
set_margin_bottom: 10,
set_halign: gtk::Align::Center,
#[local_ref]
voting_row -> gtk::Box {
set_margin_end: 10,
},
gtk::Label {
#[watch]
set_text: &format!("{} comments, {} score", model.info.post_view.counts.comments, model.info.post_view.counts.score),
set_text: &format!("{} comments", model.info.post_view.counts.comments),
},
gtk::Button {
set_label: "Comment",
@ -158,12 +163,14 @@ impl SimpleComponent for PostPage {
.forward(sender.input_sender(), |msg| match msg {
CreatePostDialogOutput::CreateRequest(_name, body) => PostInput::CreateCommentRequest(body)
});
let model = PostPage { info: init, image, comments, creator_avatar, community_avatar, create_comment_dialog: dialog, };
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 };
let image = model.image.widget();
let comments = model.comments.widget();
let creator_avatar = model.creator_avatar.widget();
let community_avatar = model.community_avatar.widget();
let voting_row = model.voting_row.widget();
let widgets = view_output!();
ComponentParts { model, widgets }
@ -178,6 +185,7 @@ impl SimpleComponent for PostPage {
self.community_avatar.emit(get_web_image_msg(post.community_view.community.icon));
self.creator_avatar.emit(get_web_image_msg(post.post_view.creator.avatar));
self.voting_row.emit(VotingRowInput::UpdateStats(VotingStats::from_post(post.post_view.counts.clone(), post.post_view.my_vote)));
self.comments.guard().clear();
std::thread::spawn(move || {

View File

@ -5,11 +5,14 @@ use relm4_components::web_image::WebImage;
use crate::util::get_web_image_url;
use super::voting_row::{VotingRowModel, VotingStats};
#[derive(Debug)]
pub struct PostRow {
post: PostView,
author_image: Controller<WebImage>,
community_image: Controller<WebImage>,
voting_row: Controller<VotingRowModel>
}
#[derive(Debug)]
@ -91,9 +94,16 @@ impl FactoryComponent for PostRow {
add_css_class: "font-bold",
},
gtk::Label {
set_halign: gtk::Align::Start,
set_text: &format!("{} score, {} comments", self.post.counts.score, self.post.clone().counts.comments),
gtk::Box {
set_orientation: gtk::Orientation::Horizontal,
#[local_ref]
voting_row -> gtk::Box {
set_margin_end: 10,
},
gtk::Label {
set_halign: gtk::Align::Start,
set_text: &format!("{} comments", self.post.clone().counts.comments),
},
},
gtk::Separator {
@ -107,8 +117,9 @@ impl FactoryComponent for PostRow {
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();
Self { post: value, author_image, community_image }
Self { post: value, author_image, community_image, voting_row }
}
fn init_widgets(
@ -120,6 +131,7 @@ impl FactoryComponent for PostRow {
) -> Self::Widgets {
let author_image = self.author_image.widget();
let community_image = self.community_image.widget();
let voting_row = self.voting_row.widget();
let widgets = view_output!();
widgets
}

View File

@ -0,0 +1,142 @@
use lemmy_api_common::{lemmy_db_schema::{aggregates::structs::{PostAggregates, CommentAggregates}, newtypes::{PostId, CommentId}}};
use relm4::{SimpleComponent, ComponentParts, gtk};
use gtk::prelude::*;
use crate::api;
#[derive(Default, Debug, Clone)]
pub struct VotingStats {
#[allow(dead_code)]
upvotes: i64,
#[allow(dead_code)]
downvotes: i64,
score: i64,
own_vote: Option<i16>,
#[allow(dead_code)]
id: i32,
post_id: Option<i32>,
comment_id: Option<i32>
}
impl VotingStats {
pub fn from_post(counts: PostAggregates, my_vote: Option<i16>) -> Self {
Self {
upvotes: counts.upvotes,
downvotes: counts.downvotes,
own_vote: my_vote,
post_id: Some(counts.post_id.0),
id: counts.id,
score: counts.score,
comment_id: None,
}
}
pub fn from_comment(counts: CommentAggregates, my_vote: Option<i16>) -> Self {
Self {
upvotes: counts.upvotes,
downvotes: counts.downvotes,
own_vote: my_vote,
post_id: None,
id: counts.id,
score: counts.score,
comment_id: Some(counts.comment_id.0),
}
}
}
#[derive(Debug)]
pub struct VotingRowModel {
stats: VotingStats
}
#[derive(Debug)]
pub enum VotingRowInput {
UpdateStats(VotingStats),
Vote(i16),
}
#[derive(Debug)]
pub enum VotingRowOutput {
}
#[relm4::component(pub)]
impl SimpleComponent for VotingRowModel {
type Input = VotingRowInput;
type Output = VotingRowOutput;
type Init = VotingStats;
view! {
gtk::Box {
set_orientation: gtk::Orientation::Horizontal,
if model.stats.own_vote == Some(1) {
gtk::Button {
set_icon_name: "go-up",
connect_clicked => VotingRowInput::Vote(0),
add_css_class: "suggested-action",
}
} else {
gtk::Button {
set_icon_name: "go-up",
connect_clicked => VotingRowInput::Vote(1)
}
},
gtk::Label {
#[watch]
set_label: &format!("{}", model.stats.score),
set_margin_start: 10,
set_margin_end: 10,
},
if model.stats.own_vote == Some(-1) {
gtk::Button {
set_icon_name: "go-down",
connect_clicked => VotingRowInput::Vote(0),
add_css_class: "suggested-action",
}
} else {
gtk::Button {
set_icon_name: "go-down",
connect_clicked => VotingRowInput::Vote(-1)
}
},
}
}
fn init(
init: Self::Init,
root: &Self::Root,
_sender: relm4::ComponentSender<Self>,
) -> relm4::ComponentParts<Self> {
let model = VotingRowModel { stats: init };
let widgets = view_output!();
ComponentParts { widgets, model }
}
fn update(&mut self, message: Self::Input, sender: relm4::ComponentSender<Self>) {
match message {
VotingRowInput::Vote(score) => {
let stats = self.stats.clone();
std::thread::spawn(move || {
let info = if stats.post_id.is_some() {
let response = api::post::like_post(PostId { 0: stats.post_id.unwrap() }, score);
match response {
Ok(post) => Some(VotingStats::from_post(post.post_view.counts, post.post_view.my_vote)),
Err(err) => { println!("{}", err.to_string()); None }
}
} else {
let response = api::comment::like_comment(CommentId { 0: stats.comment_id.unwrap() }, score);
match response {
Ok(comment) => Some(VotingStats::from_comment(comment.comment_view.counts, comment.comment_view.my_vote)),
Err(err) => { println!("{}", err.to_string()); None }
}
};
if let Some(info) = info { sender.input(VotingRowInput::UpdateStats(info)) };
});
}
VotingRowInput::UpdateStats(stats) => {
self.stats = stats;
}
}
}
}