Initial commit
This commit is contained in:
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[build]
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
2655
Cargo.lock
generated
Normal file
2655
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
@@ -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" }
|
||||
20
src/api/communities.rs
Normal file
20
src/api/communities.rs
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
17
src/api/community.rs
Normal file
17
src/api/community.rs
Normal file
@@ -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()
|
||||
}
|
||||
14
src/api/mod.rs
Normal file
14
src/api/mod.rs
Normal file
@@ -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()
|
||||
}
|
||||
34
src/api/post.rs
Normal file
34
src/api/post.rs
Normal file
@@ -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()
|
||||
}
|
||||
14
src/api/posts.rs
Normal file
14
src/api/posts.rs
Normal file
@@ -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)
|
||||
}
|
||||
16
src/api/search.rs
Normal file
16
src/api/search.rs
Normal file
@@ -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()
|
||||
}
|
||||
17
src/api/user.rs
Normal file
17
src/api/user.rs
Normal file
@@ -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()
|
||||
}
|
||||
101
src/components/comment_row.rs
Normal file
101
src/components/comment_row.rs
Normal file
@@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
123
src/components/community_page.rs
Normal file
123
src/components/community_page.rs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
102
src/components/community_row.rs
Normal file
102
src/components/community_row.rs
Normal file
@@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/components/mod.rs
Normal file
13
src/components/mod.rs
Normal file
@@ -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()
|
||||
});
|
||||
204
src/components/post_page.rs
Normal file
204
src/components/post_page.rs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
140
src/components/post_row.rs
Normal file
140
src/components/post_row.rs
Normal file
@@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
106
src/components/profile_page.rs
Normal file
106
src/components/profile_page.rs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/examples/community.json
Normal file
1
src/examples/community.json
Normal file
File diff suppressed because one or more lines are too long
1
src/examples/person.json
Normal file
1
src/examples/person.json
Normal file
File diff suppressed because one or more lines are too long
1
src/examples/post.json
Normal file
1
src/examples/post.json
Normal file
File diff suppressed because one or more lines are too long
311
src/main.rs
Normal file
311
src/main.rs
Normal file
@@ -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>(());
|
||||
}
|
||||
33
src/settings.rs
Normal file
33
src/settings.rs
Normal file
@@ -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();
|
||||
}
|
||||
7
src/style.css
Normal file
7
src/style.css
Normal file
@@ -0,0 +1,7 @@
|
||||
.font-bold {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.font-very-bold {
|
||||
font-size: 2rem;
|
||||
}
|
||||
14
src/util.rs
Normal file
14
src/util.rs
Normal file
@@ -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("") }
|
||||
}
|
||||
Reference in New Issue
Block a user