(Down-)voting posts and comments
This commit is contained in:
parent
99e122ed9a
commit
1e984b0765
|
@ -6,10 +6,14 @@ use relm4_components::web_image::WebImage;
|
||||||
use crate::util::get_web_image_url;
|
use crate::util::get_web_image_url;
|
||||||
use crate::util::markdown_to_pango_markup;
|
use crate::util::markdown_to_pango_markup;
|
||||||
|
|
||||||
|
use super::voting_row::VotingRowModel;
|
||||||
|
use super::voting_row::VotingStats;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CommentRow {
|
pub struct CommentRow {
|
||||||
comment: CommentView,
|
comment: CommentView,
|
||||||
avatar: Controller<WebImage>
|
avatar: Controller<WebImage>,
|
||||||
|
voting_row: Controller<VotingRowModel>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -62,10 +66,8 @@ impl FactoryComponent for CommentRow {
|
||||||
set_use_markup: true,
|
set_use_markup: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
gtk::Label {
|
#[local_ref]
|
||||||
set_label: &format!("{} score", self.comment.counts.score),
|
voting_row -> gtk::Box {},
|
||||||
set_halign: gtk::Align::Start,
|
|
||||||
},
|
|
||||||
|
|
||||||
gtk::Separator {}
|
gtk::Separator {}
|
||||||
}
|
}
|
||||||
|
@ -77,8 +79,9 @@ impl FactoryComponent for CommentRow {
|
||||||
|
|
||||||
fn init_model(value: Self::Init, _index: &DynamicIndex, _sender: FactorySender<Self>) -> Self {
|
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 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(
|
fn init_widgets(
|
||||||
|
@ -89,6 +92,7 @@ impl FactoryComponent for CommentRow {
|
||||||
sender: FactorySender<Self>,
|
sender: FactorySender<Self>,
|
||||||
) -> Self::Widgets {
|
) -> Self::Widgets {
|
||||||
let community_image = self.avatar.widget();
|
let community_image = self.avatar.widget();
|
||||||
|
let voting_row = self.voting_row.widget();
|
||||||
let widgets = view_output!();
|
let widgets = view_output!();
|
||||||
widgets
|
widgets
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,7 +167,7 @@ impl SimpleComponent for CommunityPage {
|
||||||
Ok(post) => Some(CommunityInput::CreatedPost(post.post_view)),
|
Ok(post) => Some(CommunityInput::CreatedPost(post.post_view)),
|
||||||
Err(err) => { println!("{}", err.to_string()); None }
|
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 => {
|
CommunityInput::ToggleSubscription => {
|
||||||
|
|
|
@ -4,3 +4,4 @@ pub mod profile_page;
|
||||||
pub mod community_page;
|
pub mod community_page;
|
||||||
pub mod post_page;
|
pub mod post_page;
|
||||||
pub mod comment_row;
|
pub mod comment_row;
|
||||||
|
pub mod voting_row;
|
||||||
|
|
|
@ -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 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 {
|
pub struct PostPage {
|
||||||
info: GetPostResponse,
|
info: GetPostResponse,
|
||||||
|
@ -14,7 +14,8 @@ pub struct PostPage {
|
||||||
community_avatar: Controller<WebImage>,
|
community_avatar: Controller<WebImage>,
|
||||||
comments: FactoryVecDeque<CommentRow>,
|
comments: FactoryVecDeque<CommentRow>,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
create_comment_dialog: Controller<CreatePostDialog>
|
create_comment_dialog: Controller<CreatePostDialog>,
|
||||||
|
voting_row: Controller<VotingRowModel>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -122,9 +123,13 @@ impl SimpleComponent for PostPage {
|
||||||
set_margin_bottom: 10,
|
set_margin_bottom: 10,
|
||||||
set_halign: gtk::Align::Center,
|
set_halign: gtk::Align::Center,
|
||||||
|
|
||||||
|
#[local_ref]
|
||||||
|
voting_row -> gtk::Box {
|
||||||
|
set_margin_end: 10,
|
||||||
|
},
|
||||||
gtk::Label {
|
gtk::Label {
|
||||||
#[watch]
|
#[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 {
|
gtk::Button {
|
||||||
set_label: "Comment",
|
set_label: "Comment",
|
||||||
|
@ -158,12 +163,14 @@ impl SimpleComponent for PostPage {
|
||||||
.forward(sender.input_sender(), |msg| match msg {
|
.forward(sender.input_sender(), |msg| match msg {
|
||||||
CreatePostDialogOutput::CreateRequest(_name, body) => PostInput::CreateCommentRequest(body)
|
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 image = model.image.widget();
|
||||||
let comments = model.comments.widget();
|
let comments = model.comments.widget();
|
||||||
let creator_avatar = model.creator_avatar.widget();
|
let creator_avatar = model.creator_avatar.widget();
|
||||||
let community_avatar = model.community_avatar.widget();
|
let community_avatar = model.community_avatar.widget();
|
||||||
|
let voting_row = model.voting_row.widget();
|
||||||
let widgets = view_output!();
|
let widgets = view_output!();
|
||||||
|
|
||||||
ComponentParts { model, widgets }
|
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.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.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();
|
self.comments.guard().clear();
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
|
|
|
@ -5,11 +5,14 @@ use relm4_components::web_image::WebImage;
|
||||||
|
|
||||||
use crate::util::get_web_image_url;
|
use crate::util::get_web_image_url;
|
||||||
|
|
||||||
|
use super::voting_row::{VotingRowModel, VotingStats};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PostRow {
|
pub struct PostRow {
|
||||||
post: PostView,
|
post: PostView,
|
||||||
author_image: Controller<WebImage>,
|
author_image: Controller<WebImage>,
|
||||||
community_image: Controller<WebImage>,
|
community_image: Controller<WebImage>,
|
||||||
|
voting_row: Controller<VotingRowModel>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -91,9 +94,16 @@ impl FactoryComponent for PostRow {
|
||||||
add_css_class: "font-bold",
|
add_css_class: "font-bold",
|
||||||
},
|
},
|
||||||
|
|
||||||
gtk::Label {
|
gtk::Box {
|
||||||
set_halign: gtk::Align::Start,
|
set_orientation: gtk::Orientation::Horizontal,
|
||||||
set_text: &format!("{} score, {} comments", self.post.counts.score, self.post.clone().counts.comments),
|
#[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 {
|
gtk::Separator {
|
||||||
|
@ -107,8 +117,9 @@ impl FactoryComponent for PostRow {
|
||||||
fn init_model(value: Self::Init, _index: &DynamicIndex, _sender: FactorySender<Self>) -> Self {
|
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 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 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(
|
fn init_widgets(
|
||||||
|
@ -120,6 +131,7 @@ impl FactoryComponent for PostRow {
|
||||||
) -> Self::Widgets {
|
) -> Self::Widgets {
|
||||||
let author_image = self.author_image.widget();
|
let author_image = self.author_image.widget();
|
||||||
let community_image = self.community_image.widget();
|
let community_image = self.community_image.widget();
|
||||||
|
let voting_row = self.voting_row.widget();
|
||||||
let widgets = view_output!();
|
let widgets = view_output!();
|
||||||
widgets
|
widgets
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue