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"] }
|
||||
reqwest = { version = "0.12", features= ["blocking","socks"]}
|
||||
rss = "2.0"
|
||||
web-sys = "0.3.70"
|
||||
mdka = "1.2.10" #Not Sure if this is useful...
|
||||
rusqlite = {version=">=0.34",features=['bundled']}
|
||||
html2md = "0.2"
|
||||
regex = "1"
|
||||
scraper = "0.23.1"
|
||||
directories = "6.0.0"
|
||||
chrono = "0.4.41"
|
||||
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"
|
||||
#rfd = "0.15.4" (for importing files)
|
||||
|
||||
[profile.dev]
|
||||
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
|
||||
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
|
||||
|
|
|
|||
41
src/db.rs
41
src/db.rs
|
|
@ -2,6 +2,7 @@ use super::files::*;
|
|||
use super::net::*;
|
||||
use chrono::DateTime;
|
||||
use chrono::Utc;
|
||||
use rusqlite::Row;
|
||||
//Maybe use a different time?
|
||||
use rusqlite::{Connection, Result};
|
||||
use std::path::PathBuf;
|
||||
|
|
@ -205,6 +206,7 @@ pub fn store_items(feed: rss::Channel, feed_id: usize) {
|
|||
};
|
||||
});
|
||||
conn.close().unwrap();
|
||||
println!("Finished storing items")
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -219,21 +221,40 @@ pub struct FeedItem {
|
|||
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> {
|
||||
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 items: Result<Vec<FeedItem>> = stmt
|
||||
.query_map([feed_id], |row| {
|
||||
Ok(FeedItem {
|
||||
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,
|
||||
})
|
||||
Ok(row2feed_item(row))
|
||||
})
|
||||
.unwrap()
|
||||
.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)]
|
||||
pub mod net;
|
||||
pub mod ui;
|
||||
pub mod html;
|
||||
pub mod db;
|
||||
pub mod files;
|
||||
pub mod widgets;
|
||||
|
|
|
|||
46
src/ui.rs
46
src/ui.rs
|
|
@ -1,10 +1,10 @@
|
|||
use crate::db::FeedItem;
|
||||
use crate::widgets::content_view;
|
||||
use crate::widgets::media_view;
|
||||
use crate::widgets::navbar;
|
||||
|
||||
use super::db;
|
||||
use super::widgets;
|
||||
use iced::widget::markdown::Url;
|
||||
use iced::widget::row;
|
||||
use iced::widget::scrollable;
|
||||
use iced::widget::text_input;
|
||||
|
|
@ -16,9 +16,18 @@ use iced::{
|
|||
};
|
||||
use rss_content::parse_content;
|
||||
use rss_content::Content;
|
||||
const ICON: &[u8] = include_bytes!("../assets/icon_placeholder.png");
|
||||
|
||||
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)]
|
||||
|
|
@ -27,6 +36,7 @@ pub enum Page {
|
|||
FeedView,
|
||||
AllItems,
|
||||
ItemView,
|
||||
CategoryView,
|
||||
Testing,
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +69,7 @@ pub enum Message {
|
|||
RemoveFeed(usize),
|
||||
LoadItem(usize),
|
||||
FieldUpdated(AppField, String),
|
||||
LinkClicked(Url),
|
||||
LinkClicked(String),
|
||||
Done(String),
|
||||
ResetDB,
|
||||
}
|
||||
|
|
@ -144,12 +154,14 @@ fn view(state: &State) -> Element<'_, Message> {
|
|||
Page::FeedView => feed_layout(&state),
|
||||
Page::AllItems => item_list(&state),
|
||||
Page::ItemView => item_view(&state),
|
||||
Page::CategoryView => category_view(&state),
|
||||
Page::Testing => testing(&state),
|
||||
}
|
||||
}
|
||||
|
||||
fn home(_state: &State) -> Element<'_, Message> {
|
||||
fn home(state: &State) -> Element<'_, Message> {
|
||||
container(column!(
|
||||
widgets::navbar(state),
|
||||
scrollable(widgets::list_feeds())
|
||||
.width(iced::Fill)
|
||||
.height(iced::Fill),
|
||||
|
|
@ -193,15 +205,35 @@ fn item_view(state: &State) -> Element<'_, Message> {
|
|||
.into()
|
||||
}
|
||||
|
||||
fn item_list(_: &State) -> Element<'_, Message> {
|
||||
todo!()
|
||||
fn item_list(state: &State) -> Element<'_, Message> {
|
||||
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> {
|
||||
column!(
|
||||
text("Dev Panel"),
|
||||
button("Add gabe.rocks").on_press(Message::AddFeed(String::from("https://gabe.rocks/rss"))),
|
||||
button("Add LSN").on_press(Message::AddFeed(String::from(
|
||||
"https://libresolutions.network/rss"
|
||||
"https://libresolutions.network/archive/index.xml"
|
||||
))),
|
||||
row!(
|
||||
text_input("Add a feed", &state.feed_input)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
use crate::net;
|
||||
use crate::ui::Page;
|
||||
|
||||
use super::db;
|
||||
use super::ui;
|
||||
use iced::widget::Text;
|
||||
use iced::widget::markdown;
|
||||
use iced::widget::row;
|
||||
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> {
|
||||
let item = state.current_item.clone().unwrap();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue