mirror of
https://github.com/YGGverse/Yoda.git
synced 2025-02-04 15:34:14 +00:00
implement recent bookmarks menu item
This commit is contained in:
parent
c335207b28
commit
9e86e9b29f
@ -8,6 +8,7 @@ description = "Browser for Gemini Protocol"
|
|||||||
keywords = ["gemini", "gemini-protocol", "gtk", "browser", "client"]
|
keywords = ["gemini", "gemini-protocol", "gtk", "browser", "client"]
|
||||||
categories = ["network-programming", "gui"]
|
categories = ["network-programming", "gui"]
|
||||||
repository = "https://github.com/YGGverse/Yoda"
|
repository = "https://github.com/YGGverse/Yoda"
|
||||||
|
|
||||||
# homepage = "https://yggverse.github.io"
|
# homepage = "https://yggverse.github.io"
|
||||||
|
|
||||||
[dependencies.adw]
|
[dependencies.adw]
|
||||||
@ -51,6 +52,9 @@ version = "0.1.1"
|
|||||||
[dependencies.libspelling]
|
[dependencies.libspelling]
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
itertools = "0.14.0"
|
||||||
|
|
||||||
# development
|
# development
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
# ggemini = { git = "https://github.com/YGGverse/ggemini.git" }
|
# ggemini = { git = "https://github.com/YGGverse/ggemini.git" }
|
||||||
|
@ -15,7 +15,7 @@ GTK 4 / Libadwaita client written in Rust
|
|||||||
* [x] Hotkeys
|
* [x] Hotkeys
|
||||||
* [ ] Bookmarks
|
* [ ] Bookmarks
|
||||||
* [ ] Browser window
|
* [ ] Browser window
|
||||||
* [ ] Recent list
|
* [x] Recent bookmarks list
|
||||||
* [ ] Build-in multimedia support
|
* [ ] Build-in multimedia support
|
||||||
* [x] [Images](#images)
|
* [x] [Images](#images)
|
||||||
* [ ] [Audio](#audio)
|
* [ ] [Audio](#audio)
|
||||||
|
@ -4,7 +4,7 @@ mod header;
|
|||||||
mod tab;
|
mod tab;
|
||||||
mod widget;
|
mod widget;
|
||||||
|
|
||||||
use action::Action;
|
use action::{Action, Position};
|
||||||
use header::Header;
|
use header::Header;
|
||||||
use sqlite::Transaction;
|
use sqlite::Transaction;
|
||||||
use tab::Tab;
|
use tab::Tab;
|
||||||
@ -29,7 +29,11 @@ impl Window {
|
|||||||
|
|
||||||
// Init components
|
// Init components
|
||||||
let tab = Rc::new(Tab::new(&profile, (&browser_action, &action)));
|
let tab = Rc::new(Tab::new(&profile, (&browser_action, &action)));
|
||||||
let header = Header::new((&browser_action, &action), &profile, &tab.widget.tab_view);
|
let header = Rc::new(Header::new(
|
||||||
|
(&browser_action, &action),
|
||||||
|
&profile,
|
||||||
|
&tab.widget.tab_view,
|
||||||
|
));
|
||||||
let widget = Rc::new(Widget::new(&header.widget.gobject, &tab.widget.tab_view));
|
let widget = Rc::new(Widget::new(&header.widget.gobject, &tab.widget.tab_view));
|
||||||
|
|
||||||
// Init events
|
// Init events
|
||||||
@ -110,6 +114,13 @@ impl Window {
|
|||||||
} // @TODO rename destination method
|
} // @TODO rename destination method
|
||||||
});
|
});
|
||||||
|
|
||||||
|
action.open.on_activate({
|
||||||
|
let tab = tab.clone();
|
||||||
|
move |_, request| {
|
||||||
|
tab.append(Position::End, Some(request), false, true, false, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Init struct
|
// Init struct
|
||||||
Self {
|
Self {
|
||||||
action,
|
action,
|
||||||
|
@ -6,6 +6,7 @@ mod find;
|
|||||||
mod history_back;
|
mod history_back;
|
||||||
mod history_forward;
|
mod history_forward;
|
||||||
mod home;
|
mod home;
|
||||||
|
mod open;
|
||||||
mod pin;
|
mod pin;
|
||||||
mod reload;
|
mod reload;
|
||||||
mod save_as;
|
mod save_as;
|
||||||
@ -19,6 +20,7 @@ use find::Find;
|
|||||||
use history_back::HistoryBack;
|
use history_back::HistoryBack;
|
||||||
use history_forward::HistoryForward;
|
use history_forward::HistoryForward;
|
||||||
use home::Home;
|
use home::Home;
|
||||||
|
use open::Open;
|
||||||
use pin::Pin;
|
use pin::Pin;
|
||||||
use reload::Reload;
|
use reload::Reload;
|
||||||
use save_as::SaveAs;
|
use save_as::SaveAs;
|
||||||
@ -44,6 +46,7 @@ pub struct Action {
|
|||||||
pub history_back: Rc<HistoryBack>,
|
pub history_back: Rc<HistoryBack>,
|
||||||
pub history_forward: Rc<HistoryForward>,
|
pub history_forward: Rc<HistoryForward>,
|
||||||
pub home: Rc<Home>,
|
pub home: Rc<Home>,
|
||||||
|
pub open: Rc<Open>,
|
||||||
pub pin: Rc<Pin>,
|
pub pin: Rc<Pin>,
|
||||||
pub reload: Rc<Reload>,
|
pub reload: Rc<Reload>,
|
||||||
pub save_as: Rc<SaveAs>,
|
pub save_as: Rc<SaveAs>,
|
||||||
@ -67,6 +70,7 @@ impl Action {
|
|||||||
let history_back = Rc::new(HistoryBack::new());
|
let history_back = Rc::new(HistoryBack::new());
|
||||||
let history_forward = Rc::new(HistoryForward::new());
|
let history_forward = Rc::new(HistoryForward::new());
|
||||||
let home = Rc::new(Home::new());
|
let home = Rc::new(Home::new());
|
||||||
|
let open = Rc::new(Open::new());
|
||||||
let pin = Rc::new(Pin::new());
|
let pin = Rc::new(Pin::new());
|
||||||
let reload = Rc::new(Reload::new());
|
let reload = Rc::new(Reload::new());
|
||||||
let save_as = Rc::new(SaveAs::new());
|
let save_as = Rc::new(SaveAs::new());
|
||||||
@ -87,6 +91,7 @@ impl Action {
|
|||||||
simple_action_group.add_action(&history_back.simple_action);
|
simple_action_group.add_action(&history_back.simple_action);
|
||||||
simple_action_group.add_action(&history_forward.simple_action);
|
simple_action_group.add_action(&history_forward.simple_action);
|
||||||
simple_action_group.add_action(&home.simple_action);
|
simple_action_group.add_action(&home.simple_action);
|
||||||
|
simple_action_group.add_action(&open.simple_action);
|
||||||
simple_action_group.add_action(&pin.simple_action);
|
simple_action_group.add_action(&pin.simple_action);
|
||||||
simple_action_group.add_action(&reload.simple_action);
|
simple_action_group.add_action(&reload.simple_action);
|
||||||
simple_action_group.add_action(&save_as.simple_action);
|
simple_action_group.add_action(&save_as.simple_action);
|
||||||
@ -102,6 +107,7 @@ impl Action {
|
|||||||
history_back,
|
history_back,
|
||||||
history_forward,
|
history_forward,
|
||||||
home,
|
home,
|
||||||
|
open,
|
||||||
pin,
|
pin,
|
||||||
reload,
|
reload,
|
||||||
save_as,
|
save_as,
|
||||||
|
44
src/app/browser/window/action/open.rs
Normal file
44
src/app/browser/window/action/open.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// Defaults
|
||||||
|
|
||||||
|
use gtk::{
|
||||||
|
gio::SimpleAction,
|
||||||
|
glib::{uuid_string_random, SignalHandlerId},
|
||||||
|
prelude::StaticVariantType,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Open [SimpleAction](https://docs.gtk.org/gio/class.SimpleAction.html)
|
||||||
|
pub struct Open {
|
||||||
|
pub simple_action: SimpleAction,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Open {
|
||||||
|
// Constructors
|
||||||
|
|
||||||
|
/// Create new `Self`
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
simple_action: SimpleAction::new(
|
||||||
|
&uuid_string_random(),
|
||||||
|
Some(&String::static_variant_type()),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
|
||||||
|
/// Formatted action connector for external implementation
|
||||||
|
pub fn on_activate(
|
||||||
|
&self,
|
||||||
|
callback: impl Fn(&SimpleAction, String) + 'static,
|
||||||
|
) -> SignalHandlerId {
|
||||||
|
self.simple_action.connect_activate(move |this, message| {
|
||||||
|
callback(
|
||||||
|
this,
|
||||||
|
message
|
||||||
|
.expect("Variant required to call this action")
|
||||||
|
.get::<String>()
|
||||||
|
.expect("Parameter does not match `String` type"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -13,14 +13,15 @@ pub struct Header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Header {
|
impl Header {
|
||||||
// Construct
|
// Constructors
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
(browser_action, window_action): (&Rc<BrowserAction>, &Rc<WindowAction>),
|
(browser_action, window_action): (&Rc<BrowserAction>, &Rc<WindowAction>),
|
||||||
profile: &Rc<Profile>,
|
profile: &Rc<Profile>,
|
||||||
tab_view: &TabView,
|
tab_view: &TabView,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Init components
|
// Init components
|
||||||
let bar = Bar::new((browser_action, window_action), profile, tab_view);
|
let bar = Rc::new(Bar::new((browser_action, window_action), profile, tab_view));
|
||||||
|
|
||||||
// Return new struct
|
// Return new struct
|
||||||
Self {
|
Self {
|
||||||
|
@ -26,11 +26,11 @@ impl Bar {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
let control = Control::new();
|
let control = Control::new();
|
||||||
let tab = Tab::new(window_action, view);
|
let tab = Tab::new(window_action, view);
|
||||||
let menu = Menu::new((browser_action, window_action), profile);
|
let menu = Rc::new(Menu::new((browser_action, window_action), profile));
|
||||||
Self {
|
Self {
|
||||||
widget: Rc::new(Widget::new(
|
widget: Rc::new(Widget::new(
|
||||||
&control.widget.gobject,
|
&control.window_controls,
|
||||||
&menu.widget.gobject,
|
&menu.menu_button,
|
||||||
&tab.widget.tab_bar,
|
&tab.widget.tab_bar,
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
mod widget;
|
use gtk::{PackType, WindowControls};
|
||||||
use widget::Widget;
|
|
||||||
|
|
||||||
use std::rc::Rc;
|
const MARGIN: i32 = 4;
|
||||||
|
|
||||||
pub struct Control {
|
pub struct Control {
|
||||||
pub widget: Rc<Widget>,
|
pub window_controls: WindowControls,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Control {
|
impl Control {
|
||||||
// Construct
|
// Construct
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
widget: Rc::new(Widget::new()),
|
window_controls: WindowControls::builder()
|
||||||
|
.margin_end(MARGIN)
|
||||||
|
.side(PackType::End)
|
||||||
|
.build(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
use gtk::{PackType, WindowControls};
|
|
||||||
|
|
||||||
pub struct Widget {
|
|
||||||
pub gobject: WindowControls,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget {
|
|
||||||
// Construct
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
gobject: WindowControls::builder()
|
|
||||||
.side(PackType::End)
|
|
||||||
.margin_end(4)
|
|
||||||
.build(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +1,24 @@
|
|||||||
mod widget;
|
|
||||||
|
|
||||||
use widget::Widget;
|
|
||||||
|
|
||||||
use super::{BrowserAction, Profile, WindowAction};
|
use super::{BrowserAction, Profile, WindowAction};
|
||||||
use gtk::{
|
use gtk::{
|
||||||
gio::{self},
|
gio::{self},
|
||||||
prelude::ActionExt,
|
prelude::{ActionExt, ToVariant},
|
||||||
|
Align, MenuButton,
|
||||||
};
|
};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
// Config options
|
||||||
|
|
||||||
|
const RECENT_BOOKMARKS: usize = 50;
|
||||||
|
|
||||||
pub struct Menu {
|
pub struct Menu {
|
||||||
pub widget: Rc<Widget>,
|
pub menu_button: MenuButton,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rustfmt::skip] // @TODO template builder?
|
#[rustfmt::skip] // @TODO template builder?
|
||||||
impl Menu {
|
impl Menu {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
(browser_action, window_action): (&Rc<BrowserAction>, &Rc<WindowAction>),
|
(browser_action, window_action): (&Rc<BrowserAction>, &Rc<WindowAction>),
|
||||||
_profile: &Rc<Profile>,
|
profile: &Rc<Profile>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Main
|
// Main
|
||||||
let main = gio::Menu::new();
|
let main = gio::Menu::new();
|
||||||
@ -122,6 +124,12 @@ impl Menu {
|
|||||||
|
|
||||||
main.append_submenu(Some("Page"), &main_page);
|
main.append_submenu(Some("Page"), &main_page);
|
||||||
|
|
||||||
|
// Main > Bookmark
|
||||||
|
// * menu items dynamically generated using profile memory pool and `set_create_popup_func`
|
||||||
|
let main_bookmarks = gio::Menu::new();
|
||||||
|
|
||||||
|
main.append_submenu(Some("Bookmarks"), &main_bookmarks);
|
||||||
|
|
||||||
// Main > Tool
|
// Main > Tool
|
||||||
let main_tool = gio::Menu::new();
|
let main_tool = gio::Menu::new();
|
||||||
|
|
||||||
@ -152,7 +160,42 @@ impl Menu {
|
|||||||
browser_action.close.simple_action.name()
|
browser_action.close.simple_action.name()
|
||||||
)));
|
)));
|
||||||
|
|
||||||
|
// Init main widget
|
||||||
|
let menu_button = MenuButton::builder()
|
||||||
|
.css_classes(["flat"])
|
||||||
|
.icon_name("open-menu-symbolic")
|
||||||
|
.menu_model(&main)
|
||||||
|
.tooltip_text("Menu")
|
||||||
|
.valign(Align::Center)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Generate dynamical menu items
|
||||||
|
menu_button.set_create_popup_func({
|
||||||
|
let profile = profile.clone();
|
||||||
|
let main_bookmarks = main_bookmarks.clone();
|
||||||
|
let window_action = window_action.clone();
|
||||||
|
move |_| {
|
||||||
|
main_bookmarks.remove_all();
|
||||||
|
for bookmark in profile.bookmark.memory.recent(RECENT_BOOKMARKS) {
|
||||||
|
let menu_item = gio::MenuItem::new(Some(&bookmark), None);
|
||||||
|
menu_item.set_action_and_target_value(Some(&format!(
|
||||||
|
"{}.{}",
|
||||||
|
window_action.id,
|
||||||
|
window_action.open.simple_action.name()
|
||||||
|
)), Some(&bookmark.to_variant()));
|
||||||
|
|
||||||
|
main_bookmarks.append_item(&menu_item);
|
||||||
|
}
|
||||||
|
// Show all bookmarks menu item
|
||||||
|
// if profile.bookmark.memory.total() > RECENT_BOOKMARKS {
|
||||||
|
// @TODO
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Result
|
// Result
|
||||||
Self { widget:Rc::new(Widget::new(&main)) }
|
Self {
|
||||||
|
menu_button
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
use gtk::{gio::Menu, Align, MenuButton};
|
|
||||||
|
|
||||||
pub struct Widget {
|
|
||||||
pub gobject: MenuButton,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget {
|
|
||||||
// Construct
|
|
||||||
pub fn new(model: &Menu) -> Self {
|
|
||||||
Self {
|
|
||||||
gobject: MenuButton::builder()
|
|
||||||
.css_classes(["flat"])
|
|
||||||
.icon_name("open-menu-symbolic")
|
|
||||||
.menu_model(model)
|
|
||||||
.tooltip_text("Menu")
|
|
||||||
.valign(Align::Center)
|
|
||||||
.build(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -11,8 +11,8 @@ use sqlite::{Connection, Transaction};
|
|||||||
use std::{rc::Rc, sync::RwLock};
|
use std::{rc::Rc, sync::RwLock};
|
||||||
|
|
||||||
pub struct Bookmark {
|
pub struct Bookmark {
|
||||||
database: Rc<Database>, // permanent storage
|
pub database: Rc<Database>, // permanent storage
|
||||||
memory: Rc<Memory>, // fast search index
|
pub memory: Rc<Memory>, // fast search index
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bookmark {
|
impl Bookmark {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
mod error;
|
mod error;
|
||||||
use error::Error;
|
use error::Error;
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
use std::{cell::RefCell, collections::HashMap};
|
use std::{cell::RefCell, collections::HashMap};
|
||||||
|
|
||||||
/// Reduce disk usage by cache Bookmarks index in memory
|
/// Reduce disk usage by cache Bookmarks index in memory
|
||||||
@ -54,4 +55,24 @@ impl Memory {
|
|||||||
None => Err(Error::Unexpected), // @TODO
|
None => Err(Error::Unexpected), // @TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get recent requests vector sorted DESC by `ID`
|
||||||
|
pub fn recent(&self, limit: usize) -> Vec<String> {
|
||||||
|
let mut recent: Vec<String> = Vec::new();
|
||||||
|
for (request, _) in self
|
||||||
|
.index
|
||||||
|
.borrow()
|
||||||
|
.iter()
|
||||||
|
.sorted_by(|a, b| Ord::cmp(&b.1, &a.1))
|
||||||
|
.take(limit)
|
||||||
|
{
|
||||||
|
recent.push(request.to_string())
|
||||||
|
}
|
||||||
|
recent
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get total records in memory pool
|
||||||
|
pub fn total(&self) -> usize {
|
||||||
|
self.index.borrow().len()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user