very minor improvements. I know the AI generated icon is cringe, but it's a placeholder for now. (Doesn't even work on Wayland anyways)
Successfully migrated to iced::application for more fine-grained control over settings. Added "all-items" view that breaks things slightly.
This commit is contained in:
parent
d877e9e127
commit
e94905c9e6
8 changed files with 92 additions and 48 deletions
|
|
@ -7,17 +7,13 @@ edition = "2021"
|
||||||
iced = { git = "https://github.com/iced-rs/iced", branch = "master", features = ["image", "markdown", "svg"] }
|
iced = { git = "https://github.com/iced-rs/iced", branch = "master", features = ["image", "markdown", "svg"] }
|
||||||
reqwest = { version = "0.12", features= ["blocking","socks"]}
|
reqwest = { version = "0.12", features= ["blocking","socks"]}
|
||||||
rss = "2.0"
|
rss = "2.0"
|
||||||
web-sys = "0.3.70"
|
|
||||||
mdka = "1.2.10" #Not Sure if this is useful...
|
|
||||||
rusqlite = {version=">=0.34",features=['bundled']}
|
rusqlite = {version=">=0.34",features=['bundled']}
|
||||||
html2md = "0.2"
|
scraper = "0.23.1"
|
||||||
regex = "1"
|
|
||||||
directories = "6.0.0"
|
directories = "6.0.0"
|
||||||
chrono = "0.4.41"
|
chrono = "0.4.41"
|
||||||
rss_content = { git = "https://code.gabe.rocks/gabriel/rss_content", version = "0.1.1" }
|
rss_content = { git = "https://code.gabe.rocks/gabriel/rss_content", version = "0.1.1" }
|
||||||
bytes = "1.10.1"
|
|
||||||
scraper = "0.23.1"
|
|
||||||
url = "2.5.4"
|
url = "2.5.4"
|
||||||
|
#rfd = "0.15.4" (for importing files)
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
debug=true
|
debug=true
|
||||||
|
|
|
||||||
BIN
assets/icon_placeholder.png
Normal file
BIN
assets/icon_placeholder.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
|
|
@ -21,7 +21,7 @@ At minimum I need to build a functionally complete RSS reader which will include
|
||||||
|
|
||||||
### Darknet Support
|
### Darknet Support
|
||||||
Censorship resistance matters!
|
Censorship resistance matters!
|
||||||
Tor & I2P sites should be first-class citizens and handled accordingly. This is [currently supported](https://code.gabe.rocks/gabriel/rss-tool/commit/240cb19589bc9d42fbae4331562e0f24413c108c) via the system daemons, but in the future will use mechanisms like [arti-client](https://crates.io/crates/arti-client)
|
Tor & I2P sites should be first-class citizens and handled accordingly. This is [currently supported](https://code.gabe.rocks/gabriel/rss-tool/commit/240cb19589bc9d42fbae4331562e0f24413c108c) via the system-wide daemons, but in the future will use mechanisms like [arti-client](https://crates.io/crates/arti-client)
|
||||||
|
|
||||||
|
|
||||||
### Information Management
|
### Information Management
|
||||||
|
|
|
||||||
41
src/db.rs
41
src/db.rs
|
|
@ -2,6 +2,7 @@ use super::files::*;
|
||||||
use super::net::*;
|
use super::net::*;
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
use rusqlite::Row;
|
||||||
//Maybe use a different time?
|
//Maybe use a different time?
|
||||||
use rusqlite::{Connection, Result};
|
use rusqlite::{Connection, Result};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
@ -205,6 +206,7 @@ pub fn store_items(feed: rss::Channel, feed_id: usize) {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
conn.close().unwrap();
|
conn.close().unwrap();
|
||||||
|
println!("Finished storing items")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -219,21 +221,40 @@ pub struct FeedItem {
|
||||||
pub media: Option<String>, //date missing! needed for ordering!!!
|
pub media: Option<String>, //date missing! needed for ordering!!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn row2feed_item(row: &Row) -> FeedItem{
|
||||||
|
FeedItem {
|
||||||
|
item_id: row.get(0).unwrap(),
|
||||||
|
title: row.get(1).unwrap_or("NO TITLE!".to_owned()),
|
||||||
|
url: row.get(2).unwrap(),
|
||||||
|
date: row.get(3).unwrap(),
|
||||||
|
media: row.get(4).unwrap(),
|
||||||
|
description: row.get(5).unwrap(),
|
||||||
|
content: row.get(6).unwrap(),
|
||||||
|
icon: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_all_items() -> Vec<FeedItem> {
|
||||||
|
let conn = get_db();
|
||||||
|
let mut stmt = conn.prepare("select itemID,title,url,date,media,description,content from items").unwrap();
|
||||||
|
let items: Result<Vec<FeedItem>> = stmt.query_map([],|row| {
|
||||||
|
Ok(row2feed_item(row))
|
||||||
|
}).unwrap().collect();
|
||||||
|
match items {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => {
|
||||||
|
panic!("No Items found!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_feed_items(feed_id: usize) -> Vec<FeedItem> {
|
pub fn get_feed_items(feed_id: usize) -> Vec<FeedItem> {
|
||||||
let conn = get_db();
|
let conn = get_db();
|
||||||
let mut stmt = conn.prepare("select itemID,title,url,date,media,description,content from items where feedID = ?1 order by date(date)").unwrap();
|
let mut stmt = conn.prepare("select itemID,title,url,date,media,description,content from items where feedID = ?1 order by date(date)").unwrap();
|
||||||
let items: Result<Vec<FeedItem>> = stmt
|
let items: Result<Vec<FeedItem>> = stmt
|
||||||
.query_map([feed_id], |row| {
|
.query_map([feed_id], |row| {
|
||||||
Ok(FeedItem {
|
Ok(row2feed_item(row))
|
||||||
item_id: row.get(0).unwrap(),
|
|
||||||
title: row.get(1).unwrap(),
|
|
||||||
url: row.get(2).unwrap(),
|
|
||||||
date: row.get(3).unwrap(),
|
|
||||||
media: row.get(4).unwrap(),
|
|
||||||
description: row.get(5).unwrap(),
|
|
||||||
content: row.get(6).unwrap(),
|
|
||||||
icon: None,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
|
||||||
23
src/html.rs
23
src/html.rs
|
|
@ -1,23 +0,0 @@
|
||||||
use html2md;
|
|
||||||
use regex::Regex;
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
Content needs to be processed in chunks.
|
|
||||||
Separate out content into blocks that can be used by the UI framework
|
|
||||||
p -> text
|
|
||||||
a -> link (does this exist?)
|
|
||||||
img - > image widget
|
|
||||||
|
|
||||||
and so on...
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* this works, but...
|
|
||||||
- images are omitted
|
|
||||||
- Blockquotes don't display
|
|
||||||
*/
|
|
||||||
pub fn process(content: String) -> String {
|
|
||||||
let re = Regex::new(r"(?s)<iframe[^>]*>.*?</iframe>").unwrap();
|
|
||||||
let cleaned = re.replace_all(&content,"");
|
|
||||||
html2md::parse_html(&cleaned)
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub mod net;
|
pub mod net;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
pub mod html;
|
|
||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod files;
|
pub mod files;
|
||||||
pub mod widgets;
|
pub mod widgets;
|
||||||
|
|
|
||||||
46
src/ui.rs
46
src/ui.rs
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::db::FeedItem;
|
use crate::db::FeedItem;
|
||||||
use crate::widgets::content_view;
|
use crate::widgets::content_view;
|
||||||
use crate::widgets::media_view;
|
use crate::widgets::media_view;
|
||||||
|
use crate::widgets::navbar;
|
||||||
|
|
||||||
use super::db;
|
use super::db;
|
||||||
use super::widgets;
|
use super::widgets;
|
||||||
use iced::widget::markdown::Url;
|
|
||||||
use iced::widget::row;
|
use iced::widget::row;
|
||||||
use iced::widget::scrollable;
|
use iced::widget::scrollable;
|
||||||
use iced::widget::text_input;
|
use iced::widget::text_input;
|
||||||
|
|
@ -16,9 +16,18 @@ use iced::{
|
||||||
};
|
};
|
||||||
use rss_content::parse_content;
|
use rss_content::parse_content;
|
||||||
use rss_content::Content;
|
use rss_content::Content;
|
||||||
|
const ICON: &[u8] = include_bytes!("../assets/icon_placeholder.png");
|
||||||
|
|
||||||
pub fn user_interface() -> iced::Result {
|
pub fn user_interface() -> iced::Result {
|
||||||
iced::run(update, view)
|
//iced::run(update, view)
|
||||||
|
let icon = iced::window::icon::from_file_data(ICON, None).ok();
|
||||||
|
let app = iced::application(State::default,update,view)
|
||||||
|
.title("RSSCar")
|
||||||
|
.theme(iced::Theme::Dark)
|
||||||
|
.window(iced::window::Settings{
|
||||||
|
icon, ..Default::default()
|
||||||
|
});
|
||||||
|
app.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -27,6 +36,7 @@ pub enum Page {
|
||||||
FeedView,
|
FeedView,
|
||||||
AllItems,
|
AllItems,
|
||||||
ItemView,
|
ItemView,
|
||||||
|
CategoryView,
|
||||||
Testing,
|
Testing,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,7 +69,7 @@ pub enum Message {
|
||||||
RemoveFeed(usize),
|
RemoveFeed(usize),
|
||||||
LoadItem(usize),
|
LoadItem(usize),
|
||||||
FieldUpdated(AppField, String),
|
FieldUpdated(AppField, String),
|
||||||
LinkClicked(Url),
|
LinkClicked(String),
|
||||||
Done(String),
|
Done(String),
|
||||||
ResetDB,
|
ResetDB,
|
||||||
}
|
}
|
||||||
|
|
@ -144,12 +154,14 @@ fn view(state: &State) -> Element<'_, Message> {
|
||||||
Page::FeedView => feed_layout(&state),
|
Page::FeedView => feed_layout(&state),
|
||||||
Page::AllItems => item_list(&state),
|
Page::AllItems => item_list(&state),
|
||||||
Page::ItemView => item_view(&state),
|
Page::ItemView => item_view(&state),
|
||||||
|
Page::CategoryView => category_view(&state),
|
||||||
Page::Testing => testing(&state),
|
Page::Testing => testing(&state),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn home(_state: &State) -> Element<'_, Message> {
|
fn home(state: &State) -> Element<'_, Message> {
|
||||||
container(column!(
|
container(column!(
|
||||||
|
widgets::navbar(state),
|
||||||
scrollable(widgets::list_feeds())
|
scrollable(widgets::list_feeds())
|
||||||
.width(iced::Fill)
|
.width(iced::Fill)
|
||||||
.height(iced::Fill),
|
.height(iced::Fill),
|
||||||
|
|
@ -193,15 +205,35 @@ fn item_view(state: &State) -> Element<'_, Message> {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn item_list(_: &State) -> Element<'_, Message> {
|
fn item_list(state: &State) -> Element<'_, Message> {
|
||||||
todo!()
|
container(
|
||||||
|
column!(
|
||||||
|
navbar(state),
|
||||||
|
scrollable(
|
||||||
|
column(
|
||||||
|
db::get_all_items().iter().map(|i|{
|
||||||
|
widgets::list_item(i.item_id, i.title.clone(), i.description.clone().unwrap_or("".to_owned()))
|
||||||
|
}).map(iced::Element::from)
|
||||||
|
).align_x(iced::Alignment::Center)).width(Fill).spacing(5)
|
||||||
|
),
|
||||||
|
|
||||||
|
).width(Fill).height(Fill).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn category_view(state: &State) -> Element<'_,Message> {
|
||||||
|
column!(
|
||||||
|
navbar(state)
|
||||||
|
|
||||||
|
).spacing(5).into()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
fn testing(state: &State) -> Element<'_, Message> {
|
fn testing(state: &State) -> Element<'_, Message> {
|
||||||
column!(
|
column!(
|
||||||
text("Dev Panel"),
|
text("Dev Panel"),
|
||||||
button("Add gabe.rocks").on_press(Message::AddFeed(String::from("https://gabe.rocks/rss"))),
|
button("Add gabe.rocks").on_press(Message::AddFeed(String::from("https://gabe.rocks/rss"))),
|
||||||
button("Add LSN").on_press(Message::AddFeed(String::from(
|
button("Add LSN").on_press(Message::AddFeed(String::from(
|
||||||
"https://libresolutions.network/rss"
|
"https://libresolutions.network/archive/index.xml"
|
||||||
))),
|
))),
|
||||||
row!(
|
row!(
|
||||||
text_input("Add a feed", &state.feed_input)
|
text_input("Add a feed", &state.feed_input)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
use crate::net;
|
use crate::net;
|
||||||
|
use crate::ui::Page;
|
||||||
|
|
||||||
use super::db;
|
use super::db;
|
||||||
use super::ui;
|
use super::ui;
|
||||||
|
use iced::widget::Text;
|
||||||
use iced::widget::markdown;
|
use iced::widget::markdown;
|
||||||
use iced::widget::row;
|
use iced::widget::row;
|
||||||
use iced::widget::scrollable;
|
use iced::widget::scrollable;
|
||||||
|
|
@ -98,6 +100,23 @@ pub fn media_view(state: &'_ ui::State) -> Element<'_, Message> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn navbar (state: &'_ ui::State) -> Element<'_,Message>{
|
||||||
|
row!(
|
||||||
|
button("Home").on_press(Message::ChangePage(Page::Home)),
|
||||||
|
button("All Items").on_press(Message::ChangePage(Page::AllItems)),
|
||||||
|
button("Categories").on_press(Message::ChangePage(Page::CategoryView))
|
||||||
|
).spacing(5).padding(5).width(iced::Fill).into()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_item(id: usize,title: String, description: String) -> Column<'static,Message>{
|
||||||
|
Column::new()
|
||||||
|
.push(button(Text::new(title)).on_press(Message::LoadItem(id)))
|
||||||
|
.push(text(description))
|
||||||
|
.align_x(iced::Alignment::Center)
|
||||||
|
.spacing(5)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn content_view(state: &'_ ui::State) -> iced::widget::Scrollable<'_, Message> {
|
pub fn content_view(state: &'_ ui::State) -> iced::widget::Scrollable<'_, Message> {
|
||||||
let item = state.current_item.clone().unwrap();
|
let item = state.current_item.clone().unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue