Initial commit
This commit is contained in:
commit
6b8eae051e
|
@ -0,0 +1,2 @@
|
|||
[build]
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
|
@ -0,0 +1 @@
|
|||
/target
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "lemmy-gtk"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
relm4 = "0.6.0"
|
||||
relm4-components = { version = "0.6.0", features = ["web"] }
|
||||
reqwest = { version = "0.11.16", features = ["json", "blocking"] }
|
||||
serde = { version = "1.0.160", features = ["derive"] }
|
||||
serde_json = "1.0.96"
|
||||
lemmy_api_common = { git = "https://github.com/LemmyNet/lemmy.git", tag = "0.17.2" }
|
|
@ -0,0 +1,20 @@
|
|||
use lemmy_api_common::{community::{ListCommunities, ListCommunitiesResponse}, lemmy_db_schema::{SortType, SearchType}, lemmy_db_views_actor::structs::CommunityView};
|
||||
|
||||
use crate::components::CLIENT;
|
||||
|
||||
use super::search;
|
||||
|
||||
pub fn fetch_communities(page: i64, query: Option<String>) -> std::result::Result<Vec<CommunityView>, reqwest::Error> {
|
||||
if query.is_none() || query.clone().unwrap().trim().is_empty() {
|
||||
let params = ListCommunities {
|
||||
sort: Some(SortType::TopMonth),
|
||||
page: Some(page),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let url = format!("{}/community/list", super::get_api_url());
|
||||
Ok(CLIENT.get(&url).query(¶ms).send()?.json::<ListCommunitiesResponse>()?.communities)
|
||||
} else {
|
||||
Ok(search::fetch_search(page, query.unwrap(), Some(SearchType::Communities))?.communities)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
use lemmy_api_common::community::{GetCommunity, GetCommunityResponse};
|
||||
|
||||
use crate::components::CLIENT;
|
||||
|
||||
pub fn get_community(name: String) -> std::result::Result<GetCommunityResponse, reqwest::Error> {
|
||||
let params = GetCommunity {
|
||||
name: Some(name),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let url = format!("{}/community", super::get_api_url());
|
||||
CLIENT.get(&url).query(¶ms).send()?.json()
|
||||
}
|
||||
|
||||
pub fn default_community() -> GetCommunityResponse {
|
||||
serde_json::from_str(include_str!("../examples/community.json")).unwrap()
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
use crate::settings::get_prefs;
|
||||
|
||||
pub mod communities;
|
||||
pub mod community;
|
||||
pub mod post;
|
||||
pub mod posts;
|
||||
pub mod search;
|
||||
pub mod user;
|
||||
|
||||
static API_VERSION: &str = "v3";
|
||||
|
||||
pub fn get_api_url() -> String {
|
||||
format!("{}/api/{}", get_prefs().instance_url, API_VERSION).to_string()
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
use lemmy_api_common::{post::{GetPost, GetPostResponse}, lemmy_db_schema::{newtypes::PostId, CommentSortType, ListingType}, comment::{GetComments, GetCommentsResponse}, lemmy_db_views::structs::CommentView};
|
||||
|
||||
use crate::components::CLIENT;
|
||||
|
||||
pub fn get_post(id: PostId) -> std::result::Result<GetPostResponse, reqwest::Error> {
|
||||
let params = GetPost {
|
||||
id: Some(id),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let url = format!("{}/post", super::get_api_url());
|
||||
CLIENT.get(&url).query(¶ms).send()?.json()
|
||||
}
|
||||
|
||||
pub fn get_comments(post_id: PostId) -> std::result::Result<Vec<CommentView>, reqwest::Error> {
|
||||
let params = GetComments {
|
||||
post_id: Some(post_id),
|
||||
sort: Some(CommentSortType::Hot),
|
||||
type_: Some(ListingType::All),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let url = format!("{}/comment/list", super::get_api_url());
|
||||
let mut comments = CLIENT.get(&url).query(¶ms).send()?.json::<GetCommentsResponse>()?.comments;
|
||||
|
||||
// hide removed comments
|
||||
comments.retain(|c| !c.comment.deleted && !c.comment.removed);
|
||||
|
||||
Ok(comments)
|
||||
}
|
||||
|
||||
pub fn default_post() -> GetPostResponse {
|
||||
serde_json::from_str(include_str!("../examples/post.json")).unwrap()
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
use lemmy_api_common::{post::{GetPostsResponse, GetPosts}, lemmy_db_views::structs::PostView};
|
||||
|
||||
use crate::components::CLIENT;
|
||||
|
||||
pub fn list_posts(page: i64, community_name: Option<String>) -> std::result::Result<Vec<PostView>, reqwest::Error> {
|
||||
let params = GetPosts {
|
||||
page: Some(page),
|
||||
community_name,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let url = format!("{}/post/list", super::get_api_url());
|
||||
Ok(CLIENT.get(&url).query(¶ms).send()?.json::<GetPostsResponse>()?.posts)
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
use lemmy_api_common::{site::{SearchResponse, Search}, lemmy_db_schema::{SortType, SearchType}};
|
||||
|
||||
use crate::components::CLIENT;
|
||||
|
||||
pub fn fetch_search(page: i64, query: String, search_type: Option<SearchType>) -> std::result::Result<SearchResponse, reqwest::Error> {
|
||||
let params = Search {
|
||||
q: query,
|
||||
sort: Some(SortType::TopMonth),
|
||||
page: Some(page),
|
||||
type_: search_type,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let url = format!("{}/search", super::get_api_url());
|
||||
CLIENT.get(&url).query(¶ms).send()?.json()
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
use lemmy_api_common::{person::{GetPersonDetailsResponse, GetPersonDetails}};
|
||||
use crate::components::CLIENT;
|
||||
|
||||
pub fn get_user(username: String, page: i64) -> std::result::Result<GetPersonDetailsResponse, reqwest::Error> {
|
||||
let params = GetPersonDetails {
|
||||
page: Some(page),
|
||||
username: Some(username),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let url = format!("{}/user", super::get_api_url());
|
||||
CLIENT.get(&url).query(¶ms).send()?.json()
|
||||
}
|
||||
|
||||
pub fn default_person() -> GetPersonDetailsResponse {
|
||||
serde_json::from_str(include_str!("../examples/person.json")).unwrap()
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
use lemmy_api_common::lemmy_db_views::structs::CommentView;
|
||||
use relm4::prelude::*;
|
||||
use gtk::prelude::*;
|
||||
use relm4_components::web_image::WebImage;
|
||||
|
||||
use crate::util::get_web_image_url;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CommentRow {
|
||||
comment: CommentView,
|
||||
avatar: Controller<WebImage>
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CommentRowMsg {
|
||||
OpenPerson,
|
||||
}
|
||||
|
||||
#[relm4::factory(pub)]
|
||||
impl FactoryComponent for CommentRow {
|
||||
type Init = CommentView;
|
||||
type Input = CommentRowMsg;
|
||||
type Output = crate::AppMsg;
|
||||
type CommandOutput = ();
|
||||
type Widgets = PostViewWidgets;
|
||||
type ParentInput = crate::AppMsg;
|
||||
type ParentWidget = gtk::Box;
|
||||
|
||||
view! {
|
||||
root = gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 10,
|
||||
set_margin_end: 10,
|
||||
set_margin_start: 10,
|
||||
set_margin_top: 10,
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 10,
|
||||
set_vexpand: false,
|
||||
|
||||
if self.comment.creator.avatar.is_some() {
|
||||
gtk::Box {
|
||||
set_hexpand: false,
|
||||
#[local_ref]
|
||||
community_image -> gtk::Box {}
|
||||
}
|
||||
} else {
|
||||
gtk::Box {}
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: &self.comment.creator.name,
|
||||
connect_clicked => CommentRowMsg::OpenPerson,
|
||||
},
|
||||
},
|
||||
|
||||
gtk::Label {
|
||||
set_label: &self.comment.comment.content,
|
||||
set_halign: gtk::Align::Start,
|
||||
},
|
||||
|
||||
gtk::Label {
|
||||
set_label: &format!("{} score", self.comment.counts.score),
|
||||
set_halign: gtk::Align::Start,
|
||||
},
|
||||
|
||||
gtk::Separator {}
|
||||
}
|
||||
}
|
||||
|
||||
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 avatar = WebImage::builder().launch(get_web_image_url(value.community.clone().icon)).detach();
|
||||
|
||||
Self { comment: value, avatar }
|
||||
}
|
||||
|
||||
fn init_widgets(
|
||||
&mut self,
|
||||
_index: &Self::Index,
|
||||
root: &Self::Root,
|
||||
_returned_widget: &<Self::ParentWidget as relm4::factory::FactoryView>::ReturnedWidget,
|
||||
sender: FactorySender<Self>,
|
||||
) -> Self::Widgets {
|
||||
let community_image = self.avatar.widget();
|
||||
let widgets = view_output!();
|
||||
widgets
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Self::Input, sender: FactorySender<Self>) {
|
||||
match message {
|
||||
CommentRowMsg::OpenPerson => {
|
||||
sender.output(crate::AppMsg::OpenPerson(self.comment.creator.name.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
use lemmy_api_common::{community::GetCommunityResponse, lemmy_db_views::structs::PostView};
|
||||
use relm4::{prelude::*, factory::FactoryVecDeque};
|
||||
use gtk::prelude::*;
|
||||
use relm4_components::web_image::WebImage;
|
||||
use std::cell::RefCell;
|
||||
|
||||
use crate::{api, util::get_web_image_msg};
|
||||
|
||||
use super::post_row::PostRow;
|
||||
|
||||
pub struct CommunityPage {
|
||||
info: RefCell<GetCommunityResponse>,
|
||||
avatar: Controller<WebImage>,
|
||||
posts: FactoryVecDeque<PostRow>
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CommunityInput {
|
||||
UpdateCommunity(GetCommunityResponse),
|
||||
DoneFetchPosts(Vec<PostView>)
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl SimpleComponent for CommunityPage {
|
||||
type Init = GetCommunityResponse;
|
||||
type Input = CommunityInput;
|
||||
type Output = crate::AppMsg;
|
||||
|
||||
view! {
|
||||
gtk::ScrolledWindow {
|
||||
set_vexpand: false,
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_vexpand: false,
|
||||
set_margin_all: 10,
|
||||
|
||||
#[local_ref]
|
||||
avatar -> gtk::Box {
|
||||
set_size_request: (100, 100),
|
||||
set_margin_bottom: 20,
|
||||
set_margin_top: 20,
|
||||
},
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_text: &model.info.borrow().community_view.community.name,
|
||||
add_css_class: "font-very-bold",
|
||||
},
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_text: &model.info.borrow().clone().community_view.community.description.unwrap_or("".to_string()),
|
||||
},
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_text: &format!("{} subscribers, ", model.info.borrow().community_view.counts.subscribers),
|
||||
},
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_margin_top: 10,
|
||||
set_margin_bottom: 10,
|
||||
set_hexpand: false,
|
||||
set_halign: gtk::Align::Center,
|
||||
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_text: &format!("{} posts, ", model.info.borrow().community_view.counts.posts),
|
||||
},
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_text: &format!("{} comments", model.info.borrow().clone().community_view.counts.comments),
|
||||
},
|
||||
},
|
||||
|
||||
gtk::Separator {},
|
||||
|
||||
#[local_ref]
|
||||
posts -> gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fn init(
|
||||
init: Self::Init,
|
||||
root: &Self::Root,
|
||||
sender: relm4::ComponentSender<Self>,
|
||||
) -> relm4::ComponentParts<Self> {
|
||||
let avatar = WebImage::builder().launch("".to_string()).detach();
|
||||
let posts = FactoryVecDeque::new(gtk::Box::default(), sender.output_sender());
|
||||
let model = CommunityPage { info: RefCell::new(init), avatar, posts };
|
||||
let avatar = model.avatar.widget();
|
||||
let posts = model.posts.widget();
|
||||
let widgets = view_output!();
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
|
||||
match message {
|
||||
CommunityInput::UpdateCommunity(community) => {
|
||||
*self.info.borrow_mut() = community.clone();
|
||||
self.avatar.emit(get_web_image_msg(community.community_view.community.icon));
|
||||
self.posts.guard().clear();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if community.community_view.counts.posts == 0 { return; }
|
||||
let community_posts = api::posts::list_posts(1, Some(community.community_view.community.name));
|
||||
if let Ok(community_posts) = community_posts {
|
||||
sender.input(CommunityInput::DoneFetchPosts(community_posts));
|
||||
}
|
||||
});
|
||||
}
|
||||
CommunityInput::DoneFetchPosts(posts) => {
|
||||
for post in posts {
|
||||
self.posts.guard().push_back(post);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
use lemmy_api_common::lemmy_db_views_actor::structs::CommunityView;
|
||||
use relm4::prelude::*;
|
||||
use gtk::prelude::*;
|
||||
use relm4_components::web_image::WebImage;
|
||||
|
||||
use crate::util::get_web_image_url;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CommunityRow {
|
||||
community: CommunityView,
|
||||
community_image: Controller<WebImage>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CommunityRowMsg {
|
||||
OpenCommunity,
|
||||
}
|
||||
|
||||
#[relm4::factory(pub)]
|
||||
impl FactoryComponent for CommunityRow {
|
||||
type Init = CommunityView;
|
||||
type Input = CommunityRowMsg;
|
||||
type Output = crate::AppMsg;
|
||||
type CommandOutput = ();
|
||||
type Widgets = PostViewWidgets;
|
||||
type ParentInput = crate::AppMsg;
|
||||
type ParentWidget = gtk::Box;
|
||||
|
||||
view! {
|
||||
root = gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 10,
|
||||
set_margin_end: 10,
|
||||
set_margin_start: 10,
|
||||
set_vexpand: false,
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 10,
|
||||
|
||||
if self.community.community.icon.is_some() {
|
||||
gtk::Box {
|
||||
set_hexpand: false,
|
||||
#[local_ref]
|
||||
community_image -> gtk::Box {}
|
||||
}
|
||||
} else {
|
||||
gtk::Box {}
|
||||
},
|
||||
|
||||
gtk::Label {
|
||||
set_label: &self.community.community.title,
|
||||
},
|
||||
|
||||
gtk::Box {
|
||||
set_hexpand: true,
|
||||
},
|
||||
|
||||
gtk::Label {
|
||||
set_label: &format!("{} subscribers, {} posts", self.community.counts.subscribers, self.community.counts.posts),
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: "View",
|
||||
connect_clicked => CommunityRowMsg::OpenCommunity,
|
||||
},
|
||||
},
|
||||
|
||||
gtk::Separator {}
|
||||
}
|
||||
}
|
||||
|
||||
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 community_image= WebImage::builder().launch(get_web_image_url(value.community.clone().icon)).detach();
|
||||
|
||||
Self { community: value, community_image }
|
||||
}
|
||||
|
||||
fn init_widgets(
|
||||
&mut self,
|
||||
_index: &Self::Index,
|
||||
root: &Self::Root,
|
||||
_returned_widget: &<Self::ParentWidget as relm4::factory::FactoryView>::ReturnedWidget,
|
||||
sender: FactorySender<Self>,
|
||||
) -> Self::Widgets {
|
||||
let community_image = self.community_image.widget();
|
||||
let widgets = view_output!();
|
||||
widgets
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Self::Input, sender: FactorySender<Self>) {
|
||||
match message {
|
||||
CommunityRowMsg::OpenCommunity => {
|
||||
sender.output(crate::AppMsg::OpenCommunity(self.community.community.name.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
pub mod post_row;
|
||||
pub mod community_row;
|
||||
pub mod profile_page;
|
||||
pub mod community_page;
|
||||
pub mod post_page;
|
||||
pub mod comment_row;
|
||||
|
||||
use reqwest::blocking::Client;
|
||||
use relm4::once_cell::sync::Lazy;
|
||||
|
||||
pub static CLIENT: Lazy<Client> = Lazy::new(|| {
|
||||
Client::new()
|
||||
});
|
|
@ -0,0 +1,204 @@
|
|||
use lemmy_api_common::{lemmy_db_views::structs::{CommentView}, post::GetPostResponse};
|
||||
use relm4::{prelude::*, factory::FactoryVecDeque};
|
||||
use gtk::prelude::*;
|
||||
use relm4_components::web_image::WebImage;
|
||||
use std::cell::RefCell;
|
||||
|
||||
use crate::{api, util::{get_web_image_msg, get_web_image_url}};
|
||||
|
||||
use super::comment_row::CommentRow;
|
||||
|
||||
pub struct PostPage {
|
||||
info: RefCell<GetPostResponse>,
|
||||
image: Controller<WebImage>,
|
||||
creator_avatar: Controller<WebImage>,
|
||||
community_avatar: Controller<WebImage>,
|
||||
comments: FactoryVecDeque<CommentRow>
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PostInput {
|
||||
UpdatePost(GetPostResponse),
|
||||
DoneFetchComments(Vec<CommentView>),
|
||||
OpenPerson,
|
||||
OpenCommunity,
|
||||
OpenLink
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl SimpleComponent for PostPage {
|
||||
type Init = GetPostResponse;
|
||||
type Input = PostInput;
|
||||
type Output = crate::AppMsg;
|
||||
|
||||
view! {
|
||||
gtk::ScrolledWindow {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_vexpand: false,
|
||||
set_margin_all: 10,
|
||||
|
||||
#[local_ref]
|
||||
image -> gtk::Box {
|
||||
set_height_request: 100,
|
||||
set_margin_bottom: 20,
|
||||
set_margin_top: 20,
|
||||
},
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_text: &model.info.borrow().post_view.post.name,
|
||||
add_css_class: "font-very-bold",
|
||||
},
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_text: &model.info.borrow().clone().post_view.post.body.unwrap_or("".to_string()),
|
||||
set_margin_top: 10,
|
||||
},
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_margin_top: 10,
|
||||
set_spacing: 10,
|
||||
set_vexpand: false,
|
||||
|
||||
gtk::Label {
|
||||
set_text: "posted by "
|
||||
},
|
||||
|
||||
if model.info.borrow().post_view.creator.avatar.is_some() {
|
||||
gtk::Box {
|
||||
set_hexpand: false,
|
||||
set_margin_start: 10,
|
||||
#[local_ref]
|
||||
creator_avatar -> gtk::Box {}
|
||||
}
|
||||
} else {
|
||||
gtk::Box {}
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: &model.info.borrow().post_view.creator.name,
|
||||
connect_clicked => PostInput::OpenPerson,
|
||||
},
|
||||
|
||||
gtk::Label {
|
||||
set_text: " in "
|
||||
},
|
||||
|
||||
if model.info.borrow().community_view.community.icon.is_some() {
|
||||
gtk::Box {
|
||||
set_hexpand: false,
|
||||
#[local_ref]
|
||||
community_avatar -> gtk::Box {}
|
||||
}
|
||||
} else {
|
||||
gtk::Box {}
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: &model.info.borrow().community_view.community.title,
|
||||
connect_clicked => PostInput::OpenCommunity,
|
||||
},
|
||||
|
||||
gtk::Box {
|
||||
set_hexpand: true,
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: "View",
|
||||
connect_clicked => PostInput::OpenLink,
|
||||
}
|
||||
},
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_margin_top: 10,
|
||||
set_margin_bottom: 10,
|
||||
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_text: &format!("{} comments, ", model.info.borrow().post_view.counts.comments),
|
||||
},
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_text: &format!("{} score", model.info.borrow().post_view.counts.score),
|
||||
},
|
||||
},
|
||||
|
||||
gtk::Separator {},
|
||||
|
||||
#[local_ref]
|
||||
comments -> gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init(
|
||||
init: Self::Init,
|
||||
root: &Self::Root,
|
||||
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 creator_avatar = WebImage::builder().launch("".to_string()).detach();
|
||||
let community_avatar = WebImage::builder().launch("".to_string()).detach();
|
||||
let model = PostPage { info: RefCell::new(init), image, comments, creator_avatar, community_avatar };
|
||||
|
||||
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 widgets = view_output!();
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
|
||||
match message {
|
||||
PostInput::UpdatePost(post) => {
|
||||
*self.info.borrow_mut() = post.clone();
|
||||
|
||||
self.image.emit(get_web_image_msg(post.post_view.post.thumbnail_url));
|
||||
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.comments.guard().clear();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if post.post_view.counts.comments == 0 { return; }
|
||||
let comments = api::post::get_comments(post.post_view.post.id);
|
||||
if let Ok(comments) = comments {
|
||||
sender.input(PostInput::DoneFetchComments(comments));
|
||||
}
|
||||
});
|
||||
}
|
||||
PostInput::DoneFetchComments(comments) => {
|
||||
for comment in comments {
|
||||
self.comments.guard().push_back(comment);
|
||||
}
|
||||
}
|
||||
PostInput::OpenPerson => {
|
||||
let name = self.info.borrow().post_view.creator.name.clone();
|
||||
let _ = sender.output(crate::AppMsg::OpenPerson(name));
|
||||
}
|
||||
PostInput::OpenCommunity => {
|
||||
let community_name = self.info.borrow().community_view.community.name.clone();
|
||||
let _ = sender.output(crate::AppMsg::OpenCommunity(community_name));
|
||||
}
|
||||
PostInput::OpenLink => {
|
||||
let post = self.info.borrow().post_view.post.clone();
|
||||
let mut link = get_web_image_url(post.url);
|
||||
if link.is_empty() {
|
||||
link = get_web_image_url(post.thumbnail_url);
|
||||
}
|
||||
if link.is_empty() {
|
||||
link = get_web_image_url(post.embed_video_url);
|
||||
}
|
||||
if link.is_empty() { return; }
|
||||
gtk::show_uri(None::<&relm4::gtk::Window>, &link, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
use lemmy_api_common::lemmy_db_views::structs::PostView;
|
||||
use relm4::prelude::*;
|
||||
use gtk::prelude::*;
|
||||
use relm4_components::web_image::WebImage;
|
||||
|
||||
use crate::util::get_web_image_url;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PostRow {
|
||||
post: PostView,
|
||||
author_image: Controller<WebImage>,
|
||||
community_image: Controller<WebImage>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PostViewMsg {
|
||||
OpenPost,
|
||||
OpenCommunity,
|
||||
OpenPerson
|
||||
}
|
||||
|
||||
#[relm4::factory(pub)]
|
||||
impl FactoryComponent for PostRow {
|
||||
type Init = PostView;
|
||||
type Input = PostViewMsg;
|
||||
type Output = crate::AppMsg;
|
||||
type CommandOutput = ();
|
||||
type Widgets = PostViewWidgets;
|
||||
type ParentInput = crate::AppMsg;
|
||||
type ParentWidget = gtk::Box;
|
||||
|
||||
view! {
|
||||
root = gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 10,
|
||||
set_margin_end: 10,
|
||||
set_margin_start: 10,
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_margin_top: 10,
|
||||
set_spacing: 10,
|
||||
set_vexpand: false,
|
||||
set_hexpand: true,
|
||||
|
||||
if self.post.community.icon.clone().is_some() {
|
||||
gtk::Box {
|
||||
set_hexpand: false,
|
||||
#[local_ref]
|
||||
community_image -> gtk::Box {}
|
||||
}
|
||||
} else {
|
||||
gtk::Box {}
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: &self.post.community.title,
|
||||
connect_clicked => PostViewMsg::OpenCommunity,
|
||||
},
|
||||
|
||||
if self.post.creator.avatar.clone().is_some() {
|
||||
gtk::Box {
|
||||
set_hexpand: false,
|
||||
set_margin_start: 10,
|
||||
#[local_ref]
|
||||
author_image -> gtk::Box {}
|
||||
}
|
||||
} else {
|
||||
gtk::Box {}
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: &self.post.creator.name,
|
||||
connect_clicked => PostViewMsg::OpenPerson,
|
||||
},
|
||||
|
||||
gtk::Box {
|
||||
set_hexpand: true,
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: "View",
|
||||
set_margin_end: 10,
|
||||
connect_clicked => PostViewMsg::OpenPost,
|
||||
}
|
||||
},
|
||||
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
set_text: &self.post.post.name,
|
||||
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::Separator {
|
||||
set_margin_top: 10,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
Self { post: value, author_image, community_image }
|
||||
}
|
||||
|
||||
fn init_widgets(
|
||||
&mut self,
|
||||
_index: &Self::Index,
|
||||
root: &Self::Root,
|
||||
_returned_widget: &<Self::ParentWidget as relm4::factory::FactoryView>::ReturnedWidget,
|
||||
sender: FactorySender<Self>,
|
||||
) -> Self::Widgets {
|
||||
let author_image = self.author_image.widget();
|
||||
let community_image = self.community_image.widget();
|
||||
let widgets = view_output!();
|
||||
widgets
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Self::Input, sender: FactorySender<Self>) {
|
||||
match message {
|
||||
PostViewMsg::OpenCommunity => {
|
||||
sender.output(crate::AppMsg::OpenCommunity(self.post.community.name.clone()))
|
||||
}
|
||||
PostViewMsg::OpenPerson => {
|
||||
sender.output(crate::AppMsg::OpenPerson(self.post.creator.name.clone()))
|
||||
}
|
||||
PostViewMsg::OpenPost => {
|
||||
sender.output(crate::AppMsg::OpenPost(self.post.post.id.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
use lemmy_api_common::person::GetPersonDetailsResponse;
|
||||
use relm4::{prelude::*, factory::FactoryVecDeque};
|
||||
use gtk::prelude::*;
|
||||
use relm4_components::web_image::WebImage;
|
||||
use std::cell::RefCell;
|
||||
|
||||
use crate::util::get_web_image_msg;
|
||||
|
||||
use super::post_row::PostRow;
|
||||
|
||||
pub struct ProfilePage {
|
||||
info: RefCell<GetPersonDetailsResponse>,
|
||||
avatar: Controller<WebImage>,
|
||||
posts: FactoryVecDeque<PostRow>
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ProfileInput {
|
||||
UpdatePerson(GetPersonDetailsResponse),
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl SimpleComponent for ProfilePage {
|
||||
type Init = GetPersonDetailsResponse;
|
||||
type Input = ProfileInput;
|
||||
type Output = crate::AppMsg;
|
||||
|
||||
view! {
|
||||
gtk::ScrolledWindow {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_vexpand: false,
|
||||
set_margin_all: 10,
|
||||
|
||||
#[local_ref]
|
||||
avatar -> gtk::Box {
|
||||
set_size_request: (100, 100),
|
||||
set_margin_bottom: 20,
|
||||
set_margin_top: 20,
|
||||
},
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_text: &model.info.borrow().person_view.person.name,
|
||||
add_css_class: "font-very-bold",
|
||||
},
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_text: &model.info.borrow().clone().person_view.person.bio.unwrap_or("".to_string()),
|
||||
},
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_margin_top: 10,
|
||||
set_margin_bottom: 10,
|
||||
set_hexpand: false,
|
||||
set_halign: gtk::Align::Center,
|
||||
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_text: &format!("{} posts, ", model.info.borrow().person_view.counts.post_count),
|
||||
},
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_text: &format!("{} comments", model.info.borrow().person_view.counts.comment_count),
|
||||
},
|
||||
},
|
||||
|
||||
gtk::Separator {},
|
||||
|
||||
#[local_ref]
|
||||
posts -> gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fn init(
|
||||
init: Self::Init,
|
||||
root: &Self::Root,
|
||||
sender: relm4::ComponentSender<Self>,
|
||||
) -> relm4::ComponentParts<Self> {
|
||||
let avatar = WebImage::builder().launch("".to_string()).detach();
|
||||
let posts = FactoryVecDeque::new(gtk::Box::default(), sender.output_sender());
|
||||
let model = ProfilePage { info: RefCell::new(init), avatar, posts };
|
||||
let avatar = model.avatar.widget();
|
||||
let posts = model.posts.widget();
|
||||
let widgets = view_output!();
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {
|
||||
match message {
|
||||
ProfileInput::UpdatePerson(person) => {
|
||||
*self.info.borrow_mut() = person.clone();
|
||||
self.avatar.emit(get_web_image_msg(person.person_view.person.avatar));
|
||||
self.posts.guard().clear();
|
||||
for post in person.posts {
|
||||
self.posts.guard().push_back(post);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,311 @@
|
|||
pub mod settings;
|
||||
pub mod api;
|
||||
pub mod components;
|
||||
pub mod util;
|
||||
|
||||
use api::{user::default_person, community::default_community, post::default_post};
|
||||
use components::{post_row::PostRow, community_row::CommunityRow, profile_page::{ProfilePage, self}, community_page::{CommunityPage, self}, post_page::{PostPage, self}};
|
||||
use gtk::prelude::*;
|
||||
use lemmy_api_common::{lemmy_db_views_actor::structs::CommunityView, lemmy_db_views::structs::PostView, person::GetPersonDetailsResponse, lemmy_db_schema::newtypes::PostId, post::GetPostResponse, community::GetCommunityResponse};
|
||||
use relm4::{prelude::*, factory::FactoryVecDeque, set_global_css};
|
||||
|
||||
static APP_ID: &str = "com.lemmy-gtk.lemoa";
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum AppState {
|
||||
Loading,
|
||||
Posts,
|
||||
ChooseInstance,
|
||||
Communities,
|
||||
Community,
|
||||
Person,
|
||||
Post
|
||||
}
|
||||
|
||||
struct App {
|
||||
state: AppState,
|
||||
posts: FactoryVecDeque<PostRow>,
|
||||
communities: FactoryVecDeque<CommunityRow>,
|
||||
profile_page: Controller<ProfilePage>,
|
||||
community_page: Controller<CommunityPage>,
|
||||
post_page: Controller<PostPage>
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AppMsg {
|
||||
ChooseInstance,
|
||||
DoneChoosingInstance(String),
|
||||
StartFetchPosts,
|
||||
DoneFetchPosts(Result<Vec<PostView>, reqwest::Error>),
|
||||
DoneFetchCommunities(Result<Vec<CommunityView>, reqwest::Error>),
|
||||
ViewCommunities(Option<String>),
|
||||
OpenCommunity(String),
|
||||
DoneFetchCommunity(GetCommunityResponse),
|
||||
OpenPerson(String),
|
||||
DoneFetchPerson(GetPersonDetailsResponse),
|
||||
OpenPost(PostId),
|
||||
DoneFetchPost(GetPostResponse)
|
||||
}
|
||||
|
||||
#[relm4::component]
|
||||
impl SimpleComponent for App {
|
||||
type Init = ();
|
||||
type Input = AppMsg;
|
||||
type Output = ();
|
||||
|
||||
view! {
|
||||
gtk::Window {
|
||||
set_title: Some("Lemoa"),
|
||||
set_default_size: (300, 100),
|
||||
|
||||
#[wrap(Some)]
|
||||
set_titlebar = >k::HeaderBar {
|
||||
pack_end = match model.state {
|
||||
AppState::ChooseInstance => {
|
||||
>k::Box {}
|
||||
}
|
||||
_ => {
|
||||
>k::Button {
|
||||
set_label: "Reset",
|
||||
connect_clicked => AppMsg::ChooseInstance,
|
||||
}
|
||||
}
|
||||
},
|
||||
pack_start = >k::Button {
|
||||
set_label: "Posts",
|
||||
connect_clicked => AppMsg::StartFetchPosts,
|
||||
},
|
||||
pack_start = >k::Button {
|
||||
set_label: "Communities",
|
||||
connect_clicked => AppMsg::ViewCommunities(None),
|
||||
},
|
||||
},
|
||||
|
||||
#[name(stack)]
|
||||
match model.state {
|
||||
AppState::Posts => gtk::ScrolledWindow {
|
||||
set_vexpand: true,
|
||||
set_hexpand: true,
|
||||
|
||||
#[local_ref]
|
||||
posts_box -> gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 5,
|
||||
}
|
||||
},
|
||||
AppState::Loading => gtk::Box {
|
||||
set_hexpand: true,
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 12,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_halign: gtk::Align::Center,
|
||||
gtk::Spinner {
|
||||
set_spinning: true,
|
||||
set_height_request: 80,
|
||||
},
|
||||
gtk::Label {
|
||||
set_text: "Loading",
|
||||
},
|
||||
},
|
||||
AppState::ChooseInstance => gtk::Box {
|
||||
set_hexpand: true,
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 12,
|
||||
set_margin_all: 20,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_halign: gtk::Align::Center,
|
||||
gtk::Label {
|
||||
set_text: "Please enter the URL of a valid lemmy instance",
|
||||
},
|
||||
#[name(instance_url)]
|
||||
gtk::Entry {
|
||||
set_tooltip_text: Some("Instance"),
|
||||
},
|
||||
gtk::Button {
|
||||
set_label: "Done",
|
||||
connect_clicked[sender, instance_url] => move |_| {
|
||||
let text = instance_url.buffer().text().as_str().to_string();
|
||||
instance_url.buffer().set_text("");
|
||||
sender.input(AppMsg::DoneChoosingInstance(text));
|
||||
},
|
||||
}
|
||||
},
|
||||
AppState::Communities => gtk::Box {
|
||||
gtk::ScrolledWindow {
|
||||
set_vexpand: true,
|
||||
set_hexpand: true,
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 10,
|
||||
|
||||
gtk::Box {
|
||||
set_margin_all: 10,
|
||||
|
||||
#[name(community_search_query)]
|
||||
gtk::Entry {
|
||||
set_hexpand: true,
|
||||
set_tooltip_text: Some("Search"),
|
||||
set_margin_end: 10,
|
||||
},
|
||||
gtk::Button {
|
||||
set_label: "Search",
|
||||
connect_clicked[sender, community_search_query] => move |_| {
|
||||
let text = community_search_query.buffer().text().as_str().to_string();
|
||||
sender.input(AppMsg::ViewCommunities(Some(text)));
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
#[local_ref]
|
||||
communities_box -> gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 5,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
AppState::Person => {
|
||||
gtk::Box {
|
||||
#[local_ref]
|
||||
profile_page -> gtk::ScrolledWindow {}
|
||||
}
|
||||
}
|
||||
AppState::Community => {
|
||||
gtk::Box {
|
||||
#[local_ref]
|
||||
community_page -> gtk::ScrolledWindow {}
|
||||
}
|
||||
}
|
||||
AppState::Post => {
|
||||
gtk::Box {
|
||||
#[local_ref]
|
||||
post_page -> gtk::ScrolledWindow {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the component.
|
||||
fn init(
|
||||
_init: Self::Init,
|
||||
root: &Self::Root,
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let instance_url = settings::get_prefs().instance_url;
|
||||
let state = if instance_url.is_empty() { AppState::ChooseInstance } else { AppState::Loading };
|
||||
|
||||
let posts = FactoryVecDeque::new(gtk::Box::default(), sender.input_sender());
|
||||
let communities = FactoryVecDeque::new(gtk::Box::default(), sender.input_sender());
|
||||
let profile_page = ProfilePage::builder().launch(default_person()).forward(sender.input_sender(), |msg| msg);
|
||||
let community_page = CommunityPage::builder().launch(default_community()).forward(sender.input_sender(), |msg| msg);
|
||||
let post_page = PostPage::builder().launch(default_post()).forward(sender.input_sender(), |msg| msg);
|
||||
|
||||
let model = App { state, posts, communities, profile_page, community_page, post_page };
|
||||
|
||||
if !instance_url.is_empty() { sender.input(AppMsg::StartFetchPosts) };
|
||||
|
||||
let posts_box = model.posts.widget();
|
||||
let communities_box = model.communities.widget();
|
||||
let profile_page = model.profile_page.widget();
|
||||
let community_page = model.community_page.widget();
|
||||
let post_page = model.post_page.widget();
|
||||
|
||||
let widgets = view_output!();
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
|
||||
match msg {
|
||||
AppMsg::DoneChoosingInstance(instance_url) => {
|
||||
if instance_url.trim().is_empty() { return; }
|
||||
let mut preferences = settings::get_prefs();
|
||||
preferences.instance_url = instance_url;
|
||||
settings::save_prefs(&preferences);
|
||||
self.state = AppState::Loading;
|
||||
sender.input(AppMsg::StartFetchPosts);
|
||||
}
|
||||
AppMsg::ChooseInstance => {
|
||||
self.state = AppState::ChooseInstance;
|
||||
}
|
||||
AppMsg::StartFetchPosts => {
|
||||
std::thread::spawn(move || {
|
||||
let posts = api::posts::list_posts(1, None);
|
||||
sender.input(AppMsg::DoneFetchPosts(posts));
|
||||
});
|
||||
}
|
||||
AppMsg::DoneFetchPosts(posts) => {
|
||||
self.state = AppState::Posts;
|
||||
if let Ok(posts) = posts {
|
||||
self.posts.guard().clear();
|
||||
for post in posts {
|
||||
self.posts.guard().push_back(post);
|
||||
}
|
||||
}
|
||||
}
|
||||
AppMsg::ViewCommunities(query) => {
|
||||
self.state = AppState::Communities;
|
||||
if (query.is_none() || query.clone().unwrap().trim().is_empty()) && !self.communities.is_empty() { return; }
|
||||
std::thread::spawn(move || {
|
||||
let communities = api::communities::fetch_communities(1, query);
|
||||
sender.input(AppMsg::DoneFetchCommunities(communities));
|
||||
});
|
||||
}
|
||||
AppMsg::DoneFetchCommunities(communities) => {
|
||||
self.state = AppState::Communities;
|
||||
if let Ok(communities) = communities {
|
||||
self.communities.guard().clear();
|
||||
for community in communities {
|
||||
self.communities.guard().push_back(community);
|
||||
}
|
||||
}
|
||||
}
|
||||
AppMsg::OpenPerson(person_name) => {
|
||||
self.state = AppState::Loading;
|
||||
std::thread::spawn(move || {
|
||||
let person = api::user::get_user(person_name, 1);
|
||||
if let Ok(person) = person {
|
||||
sender.input(AppMsg::DoneFetchPerson(person));
|
||||
}
|
||||
});
|
||||
}
|
||||
AppMsg::DoneFetchPerson(person) => {
|
||||
self.profile_page.sender().emit(profile_page::ProfileInput::UpdatePerson(person));
|
||||
self.state = AppState::Person;
|
||||
}
|
||||
AppMsg::OpenCommunity(community_name) => {
|
||||
self.state = AppState::Loading;
|
||||
std::thread::spawn(move || {
|
||||
let community = api::community::get_community(community_name);
|
||||
if let Ok(community) = community {
|
||||
sender.input(AppMsg::DoneFetchCommunity(community));
|
||||
}
|
||||
});
|
||||
}
|
||||
AppMsg::DoneFetchCommunity(community) => {
|
||||
self.community_page.sender().emit(community_page::CommunityInput::UpdateCommunity(community));
|
||||
self.state = AppState::Community;
|
||||
}
|
||||
AppMsg::OpenPost(post_id) => {
|
||||
self.state = AppState::Loading;
|
||||
std::thread::spawn(move || {
|
||||
let post = api::post::get_post(post_id);
|
||||
if let Ok(post) = post {
|
||||
sender.input(AppMsg::DoneFetchPost(post));
|
||||
}
|
||||
});
|
||||
}
|
||||
AppMsg::DoneFetchPost(post) => {
|
||||
self.post_page.sender().emit(post_page::PostInput::UpdatePost(post));
|
||||
self.state = AppState::Post;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = RelmApp::new(APP_ID);
|
||||
set_global_css(include_str!("style.css"));
|
||||
app.run::<App>(());
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
use std::{fs::File, path::PathBuf};
|
||||
use crate::gtk::glib;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::APP_ID;
|
||||
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
pub struct Preferences {
|
||||
pub instance_url: String,
|
||||
}
|
||||
|
||||
pub fn data_path() -> PathBuf {
|
||||
let mut path = glib::user_data_dir();
|
||||
path.push(APP_ID);
|
||||
std::fs::create_dir_all(&path).expect("Could not create directory.");
|
||||
path.push("data.json");
|
||||
path
|
||||
}
|
||||
|
||||
pub fn save_prefs(prefs: &Preferences) {
|
||||
let file = File::create(data_path()).expect("Could not create json file.");
|
||||
serde_json::to_writer(file, &prefs).expect("Could not write data to json file");
|
||||
}
|
||||
|
||||
pub fn get_prefs() -> Preferences {
|
||||
if let Ok(file) = File::open(data_path()) {
|
||||
// Deserialize data from file to vector
|
||||
let prefs: Result<Preferences, serde_json::Error> = serde_json::from_reader(file);
|
||||
if prefs.is_ok() {
|
||||
return prefs.unwrap();
|
||||
}
|
||||
}
|
||||
return Preferences::default();
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
.font-bold {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.font-very-bold {
|
||||
font-size: 2rem;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
use lemmy_api_common::lemmy_db_schema::newtypes::DbUrl;
|
||||
use relm4_components::web_image::WebImageMsg;
|
||||
|
||||
pub fn get_web_image_msg(url: Option<DbUrl>) -> WebImageMsg {
|
||||
return if let Some(url) = url {
|
||||
WebImageMsg::LoadImage(url.to_string())
|
||||
} else { WebImageMsg::Unload };
|
||||
}
|
||||
|
||||
pub fn get_web_image_url(url: Option<DbUrl>) -> String {
|
||||
return if let Some(url) = url {
|
||||
url.to_string()
|
||||
} else { String::from("") }
|
||||
}
|
Loading…
Reference in New Issue