From 70e03508cf1844227b5fe16ecc9ed61a441784c6 Mon Sep 17 00:00:00 2001 From: tfuxu <73042332+tfuxu@users.noreply.github.com> Date: Sat, 10 Feb 2024 20:49:19 +0100 Subject: [PATCH 01/10] feat: implement bookmarks list window - add `library-symbolic` to icons --- data/resources/icons/library-symbolic.svg | 2 + data/resources/meson.build | 1 + data/resources/resources.gresource.xml | 5 + data/resources/ui/bookmarks.blp | 68 ++++++++++++ src/widgets/bookmarks.rs | 129 ++++++++++++++++++++++ src/widgets/mod.rs | 1 + 6 files changed, 206 insertions(+) create mode 100644 data/resources/icons/library-symbolic.svg create mode 100644 data/resources/ui/bookmarks.blp create mode 100644 src/widgets/bookmarks.rs diff --git a/data/resources/icons/library-symbolic.svg b/data/resources/icons/library-symbolic.svg new file mode 100644 index 0000000..4fdb7f8 --- /dev/null +++ b/data/resources/icons/library-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/data/resources/meson.build b/data/resources/meson.build index f13646b..03f009a 100644 --- a/data/resources/meson.build +++ b/data/resources/meson.build @@ -1,5 +1,6 @@ blueprints = custom_target('blueprints', input: files( + 'ui/bookmarks.blp', 'ui/window.blp', 'ui/shortcuts.blp', 'ui/input_page.blp', diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml index 778c73a..d935220 100644 --- a/data/resources/resources.gresource.xml +++ b/data/resources/resources.gresource.xml @@ -3,9 +3,14 @@ ui/shortcuts.ui + ui/bookmarks.ui ui/window.ui ui/input_page.ui ui/download_page.ui ui/tab.ui + + + icons/library-symbolic.svg + diff --git a/data/resources/ui/bookmarks.blp b/data/resources/ui/bookmarks.blp new file mode 100644 index 0000000..5da5dcf --- /dev/null +++ b/data/resources/ui/bookmarks.blp @@ -0,0 +1,68 @@ +using Gtk 4.0; +using Adw 1; + +template $GeopardBookmarksWindow : Adw.Window { + title: _("Bookmarks"); + default-height: 400; + default-width: 500; + height-request: 290; + width-request: 360; + modal: true; + + Adw.ToastOverlay toast_overlay { + Adw.ToolbarView { + [top] + Adw.HeaderBar { + + [end] + Gtk.Button { + icon-name: "selection-mode-symbolic"; + tooltip-text: _("Select Items"); + } + } + + content: Gtk.Stack stack { + transition-type: crossfade; + + Gtk.StackPage { + name: "no_bookmarks_page"; + + child: Adw.StatusPage { + icon-name: "starred-symbolic"; + title: _("No Bookmarks"); + description: _("Bookmarked sites will appear here"); + }; + } + + Gtk.StackPage { + name: "bookmarks_page"; + + child: Gtk.ScrolledWindow { + child: Gtk.Box { + orientation: vertical; + margin-top: 6; + margin-bottom: 6; + margin-start: 6; + margin-end: 6; + hexpand: true; + vexpand: true; + + Adw.Clamp { + maximum-size: 1024; + + child: Gtk.ListBox bookmarks_list { + activate-on-single-click: true; + selection-mode: none; + + styles [ + "boxed-list" + ] + }; + } + }; + }; + } + }; + } + } +} \ No newline at end of file diff --git a/src/widgets/bookmarks.rs b/src/widgets/bookmarks.rs new file mode 100644 index 0000000..df26dc3 --- /dev/null +++ b/src/widgets/bookmarks.rs @@ -0,0 +1,129 @@ +use std::sync::OnceLock; + +use adw::prelude::*; +use adw::subclass::prelude::*; + +use glib::subclass::{InitializingObject, Signal}; +use gtk::{ + glib::{self, clone, Object}, + CompositeTemplate, +}; + +pub mod imp { + use super::*; + + #[derive(CompositeTemplate, Default)] + #[template(resource = "/com/ranfdev/Geopard/ui/bookmarks.ui")] + pub struct BookmarksWindow { + #[template_child] + pub toast_overlay: TemplateChild, + #[template_child] + pub stack: TemplateChild, + #[template_child] + pub bookmarks_list: TemplateChild, + } + + #[glib::object_subclass] + impl ObjectSubclass for BookmarksWindow { + const NAME: &'static str = "GeopardBookmarksWindow"; + type Type = super::BookmarksWindow; + type ParentType = adw::Window; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &InitializingObject) { + obj.init_template(); + } + } + impl ObjectImpl for BookmarksWindow { + fn signals() -> &'static [Signal] { + static SIGNALS: OnceLock> = OnceLock::new(); + SIGNALS.get_or_init(|| { + vec![Signal::builder("open-bookmark-url") + .param_types([str::static_type()]) + .build()] + }) + } + + fn constructed(&self) { + self.parent_constructed(); + } + } + impl WidgetImpl for BookmarksWindow {} + impl WindowImpl for BookmarksWindow {} + impl AdwWindowImpl for BookmarksWindow {} +} + +glib::wrapper! { + pub struct BookmarksWindow(ObjectSubclass) + @extends adw::Window, gtk::Window, gtk::Widget; +} + +impl BookmarksWindow { + pub fn new(app: >k::Application) -> Self { + let this = Object::builder::() + .property("application", app) + .build(); + + this.setup(); + + this + } + + fn setup(&self) { + let imp = self.imp(); + // TODO: Set to bookmarks_page if there's at least one bookmark + imp.stack.set_visible_child_name("bookmarks_page"); + + for i in 0..10 { + self.add_row( + &format!("Bookmark {i}"), + &format!("gemini://geminispace.info/search?geopard"), + ); + } + } + + // TODO: create_new_row -> adw::ActionRow + fn add_row(&self, title: &str, url: &str) { + let imp = self.imp(); + let title = title.to_string(); + let url = url.to_string(); + + let check_button = gtk::CheckButton::builder() + .visible(false) + .css_classes(vec!["selection-mode"]) + .valign(gtk::Align::Center) + .build(); + + let copy_button = gtk::Button::builder() + .icon_name("edit-copy-symbolic") + .css_classes(vec!["flat"]) + .valign(gtk::Align::Center) + .build(); + + copy_button.connect_clicked(clone!(@weak imp => move |_| { + imp.toast_overlay.add_toast(adw::Toast::new("Copied to clipboard")); + })); + + let row = adw::ActionRow::builder() + .activatable(true) + .title(&title) + .subtitle(&url) + .build(); + row.add_prefix(&check_button); + row.add_suffix(©_button); + + row.connect_activated(clone!(@weak self as this => move |_| { + this.on_row_activated(&url); + })); + + imp.bookmarks_list.append(&row); + } + + fn on_row_activated(&self, url: &str) { + let imp = self.imp().obj(); + imp.emit_by_name::<()>("open-bookmark-url", &[&url]); + } +} diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 03a0952..a9ce3b7 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,3 +1,4 @@ +mod bookmarks; mod pages; #[allow(clippy::await_holding_refcell_ref)] mod tab; From 35d0bcebc23e2f5f61d3e4dd37e697f0aeab2adb Mon Sep 17 00:00:00 2001 From: tfuxu <73042332+tfuxu@users.noreply.github.com> Date: Sat, 10 Feb 2024 20:54:20 +0100 Subject: [PATCH 02/10] feat: add action for showing bookmarks window and add `Bookmarks` button - modify `open_in_new_tab` to allow automatically switching to opened tab - update `open-in-new-tab` signal in signals vector (hypertext.rs) --- data/resources/ui/window.blp | 15 +++++++++++++++ src/widgets/pages/hypertext.rs | 5 ++++- src/widgets/window.rs | 33 +++++++++++++++++++++++++++++---- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/data/resources/ui/window.blp b/data/resources/ui/window.blp index 8cb805a..b1f2b4e 100644 --- a/data/resources/ui/window.blp +++ b/data/resources/ui/window.blp @@ -82,6 +82,13 @@ template $GeopardWindow: Adw.ApplicationWindow { primary: true; } + [end] + Gtk.Button bookmarks_button { + icon-name: "library-symbolic"; + action-name: "win.bookmarks"; + tooltip-text: _("Bookmarks"); + } + [end] Gtk.Button desktop_tab_overview_btn { icon-name: "view-grid-symbolic"; @@ -170,6 +177,13 @@ template $GeopardWindow: Adw.ApplicationWindow { tooltip-text: _("View Open Tabs"); } + [end] + Gtk.Button { + icon-name: "library-symbolic"; + action-name: "win.bookmarks"; + tooltip-text: _("Bookmarks"); + } + [end] Gtk.Button { icon-name: "system-search-symbolic"; @@ -190,6 +204,7 @@ template $GeopardWindow: Adw.ApplicationWindow { next_box.visible: false; refresh_btn.visible: false; tab_new_btn.visible: false; + bookmarks_button.visible: false; desktop_tab_overview_btn.visible: false; toolbar_view.reveal-bottom-bars: true; tab_bar_revealer.reveal-child: false; diff --git a/src/widgets/pages/hypertext.rs b/src/widgets/pages/hypertext.rs index 7175542..bfafa46 100644 --- a/src/widgets/pages/hypertext.rs +++ b/src/widgets/pages/hypertext.rs @@ -195,7 +195,10 @@ pub mod imp { .param_types([SignalType::from(glib::types::Type::STRING)]) .build(), Signal::builder("open-in-new-tab") - .param_types([SignalType::from(glib::types::Type::STRING)]) + .param_types([ + SignalType::from(glib::types::Type::STRING), + SignalType::from(glib::types::Type::BOOL), + ]) .build(), Signal::builder("open-background-tab") .param_types([SignalType::from(glib::types::Type::STRING)]) diff --git a/src/widgets/window.rs b/src/widgets/window.rs index c6187ac..7af86b5 100644 --- a/src/widgets/window.rs +++ b/src/widgets/window.rs @@ -7,7 +7,7 @@ use adw::subclass::application_window::AdwApplicationWindowImpl; use anyhow::Context; use config::APP_ID; use futures::prelude::*; -use glib::{clone, Properties}; +use glib::{clone, closure_local, Properties}; use gtk::subclass::prelude::*; use gtk::{gdk, gio, glib, CompositeTemplate, TemplateChild}; use log::{error, info, warn}; @@ -15,6 +15,7 @@ use url::Url; use crate::common::{bookmarks_url, glibctx, BOOKMARK_FILE_PATH}; use crate::session_provider::SessionProvider; +use crate::widgets::bookmarks::BookmarksWindow; use crate::widgets::tab::{HistoryItem, HistoryStatus, Tab}; use crate::{build_config, config, self_action}; @@ -222,6 +223,8 @@ impl Window { self_action!(self, "close-tab", close_tab); self_action!(self, "focus-url-bar", focus_url_bar); self_action!(self, "shortcuts", present_shortcuts); + // TODO: Switch later to 'show-bookmarks' action + self_action!(self, "bookmarks", present_bookmarks); self_action!(self, "about", present_about); self_action!(self, "focus-previous-tab", focus_previous_tab); self_action!(self, "focus-next-tab", focus_next_tab); @@ -245,7 +248,7 @@ impl Window { let act_open_in_new_tab = gio::SimpleAction::new("open-in-new-tab", Some(glib::VariantTy::STRING)); act_open_in_new_tab.connect_activate( - clone!(@weak self as this => move |_,v| this.open_in_new_tab(v.unwrap().get::().unwrap().as_str())), + clone!(@weak self as this => move |_,v| this.open_in_new_tab(v.unwrap().get::().unwrap().as_str(), false)), ); self.add_action(&act_open_in_new_tab); @@ -372,7 +375,7 @@ impl Window { clone!(@weak self as this => @default-return false, move |_, value, _, _| { if let Ok(files) = value.get::() { for f in files.files() { - this.open_in_new_tab(&format!("file://{}", f.path().unwrap().to_str().unwrap())); + this.open_in_new_tab(&format!("file://{}", f.path().unwrap().to_str().unwrap()), false); } } false @@ -651,9 +654,15 @@ impl Window { Err(e) => error!("Failed to parse url: {:?}", e), } } - fn open_in_new_tab(&self, v: &str) { + fn open_in_new_tab(&self, v: &str, select_page: bool) { + let imp = self.imp(); let w = self.add_tab(); let url = Url::parse(v); + + if select_page { + imp.tab_view.set_selected_page(&w); + } + match url { Ok(url) => self.inner_tab(&w).spawn_open_url(url), Err(e) => error!("Failed to parse url: {:?}", e), @@ -722,6 +731,22 @@ impl Window { fn present_shortcuts(&self) { gtk::Builder::from_resource("/com/ranfdev/Geopard/ui/shortcuts.ui"); } + + fn present_bookmarks(&self) { + let bookmarks = BookmarksWindow::new(&self.application().unwrap()); + bookmarks.set_transient_for(Some(self)); + + bookmarks.connect_closure( + "open-bookmark-url", + false, + closure_local!(@watch self as this => move |_: BookmarksWindow, url: &str| { + this.open_in_new_tab(url, true); + }), + ); + + bookmarks.present(); + } + fn present_about(&self) { let about = adw::AboutWindow::builder() .application_icon(build_config::APP_ID) From d46bc1a76b5951c10048df2776006e0a89eae544 Mon Sep 17 00:00:00 2001 From: tfuxu <73042332+tfuxu@users.noreply.github.com> Date: Thu, 15 Feb 2024 23:32:59 +0100 Subject: [PATCH 03/10] feat: Implement bookmark model structures --- src/bookmarks.rs | 116 +++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 1 + 2 files changed, 117 insertions(+) create mode 100644 src/bookmarks.rs diff --git a/src/bookmarks.rs b/src/bookmarks.rs new file mode 100644 index 0000000..d296ba3 --- /dev/null +++ b/src/bookmarks.rs @@ -0,0 +1,116 @@ +use std::path::Path; + +use anyhow::{Context, Ok}; +use serde::{Deserialize, Serialize}; +use toml; + +#[derive(Clone, Default, Serialize, Deserialize, Debug)] +pub struct Bookmark { + title: String, + description: Option, + url: String, +} + +#[derive(Clone, Default, Debug)] +pub struct BookmarkBuilder { + title: String, + description: Option, + url: String, +} + +#[derive(Clone, Default, Serialize, Deserialize, Debug)] +pub struct Bookmarks { + pub bookmarks: Vec, +} + +impl BookmarkBuilder { + pub fn new() -> Self { + Self::default() + } + + pub fn title(mut self, title: &str) -> Self { + self.title = String::from(title); + self + } + + pub fn description(mut self, description: Option<&str>) -> Self { + match description { + Some(desc) => self.description = Some(String::from(desc)), + None => self.description = None, + } + self + } + + pub fn url(mut self, url: &str) -> Self { + self.url = String::from(url); + self + } + + pub fn build(self) -> Bookmark { + Bookmark { + title: self.title, + description: self.description, + url: self.url, + } + } +} + +impl Bookmark { + pub fn title(&self) -> String { + self.title.clone() + } + + pub fn set_title(&mut self, title: &str) { + self.title = String::from(title); + } + + pub fn description(&self) -> Option { + self.description.as_ref().cloned() + } + + pub fn set_description(&mut self, description: &str) { + self.description = Some(String::from(description)); + } + + pub fn url(&self) -> String { + self.url.clone() + } + + pub fn set_url(&mut self, url: &str) { + self.url = String::from(url); + } +} + +impl Bookmarks { + pub async fn from_file(&self, path: &Path) -> anyhow::Result { + let file_str = async_fs::read_to_string(path) + .await + .context("Reading bookmarks file")?; + + let bookmarks = toml::from_str(&file_str)?; + + Ok(bookmarks) + } + + pub async fn to_file(&self, path: &Path) -> anyhow::Result<()> { + let toml = toml::to_string(self)?; + + async_fs::write(path, toml) + .await + .context("Writting data to bookmarks file")?; + + Ok(()) + } + + pub fn insert_bookmark(&mut self, bookmark: Bookmark) { + self.bookmarks.push(bookmark); + } + + pub fn remove_bookmark(&mut self, index: usize) { + if self.bookmarks.is_empty() { + return; + } + + self.bookmarks.remove(index); + } +} diff --git a/src/main.rs b/src/main.rs index 00f4d30..95e08fc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ #[rustfmt::skip] mod build_config; +mod bookmarks; mod common; mod config; mod lossy_text_read; From 7bcfc0cbb9ada8deb3934f9845d03c07bb0ad25f Mon Sep 17 00:00:00 2001 From: tfuxu <73042332+tfuxu@users.noreply.github.com> Date: Mon, 19 Feb 2024 23:29:39 +0100 Subject: [PATCH 04/10] feat: add `NEW_BOOKMARK_FILE_PATH` path constant --- src/common/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/common/mod.rs b/src/common/mod.rs index 51159be..9eb6f38 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -23,15 +23,21 @@ pub static KNOWN_HOSTS_PATH: Lazy = pub static CONFIG_DIR_PATH: Lazy = Lazy::new(|| glib::user_config_dir().join("geopard")); +//todo!(common): Rename this to OLD_BOOKMARK_FILE_PATH pub static BOOKMARK_FILE_PATH: Lazy = Lazy::new(|| DATA_DIR_PATH.join("bookmarks.gemini")); +//todo!(common): Rename this to BOOKMARK_FILE_PATH +pub static NEW_BOOKMARK_FILE_PATH: Lazy = + Lazy::new(|| DATA_DIR_PATH.join("bookmarks.toml")); + pub static SETTINGS_FILE_PATH: Lazy = Lazy::new(|| CONFIG_DIR_PATH.join("config.toml")); pub static HISTORY_FILE_PATH: Lazy = Lazy::new(|| DATA_DIR_PATH.join("history.gemini")); +//todo!(common): Remove this after implementing new format pub static DEFAULT_BOOKMARKS: &str = r"# Bookmarks This is a gemini file where you can put all your bookmarks. @@ -50,6 +56,7 @@ should remove bookmarks. pub const STREAMABLE_EXTS: [&str; 8] = ["mp3", "mp4", "webm", "opus", "wav", "ogg", "mkv", "flac"]; +//todo!(common): Remove this pub fn bookmarks_url() -> Url { Url::parse(&format!("file://{}", BOOKMARK_FILE_PATH.to_str().unwrap())).unwrap() } From 17e86ce1eda9bb5aeb6ed7e1bf11cb093b76027d Mon Sep 17 00:00:00 2001 From: tfuxu <73042332+tfuxu@users.noreply.github.com> Date: Mon, 19 Feb 2024 23:32:58 +0100 Subject: [PATCH 05/10] feat: switch to `BTreeMap` and add `DEFAULT_BOOKMARKS` constant --- src/bookmarks.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/src/bookmarks.rs b/src/bookmarks.rs index d296ba3..bd021bd 100644 --- a/src/bookmarks.rs +++ b/src/bookmarks.rs @@ -1,9 +1,42 @@ +use std::collections::BTreeMap; use std::path::Path; use anyhow::{Context, Ok}; +use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use toml; +// todo!(bookmarks): replace bookmarks.bookmarks.insert() with bookmarks.insert_bookmark() +pub static DEFAULT_BOOKMARKS: Lazy = Lazy::new(|| { + let mut bookmarks = Bookmarks::default(); + + bookmarks.bookmarks.insert( + 1.to_string(), + BookmarkBuilder::new() + .title("Gemini Project") + .url("gemini://geminiprotocol.net") + .build(), + ); + + bookmarks.bookmarks.insert( + 2.to_string(), + BookmarkBuilder::new() + .title("Spacewalk aggregator") + .url("gemini://rawtext.club:1965/~sloum/spacewalk.gmi") + .build(), + ); + + bookmarks.bookmarks.insert( + 3.to_string(), + BookmarkBuilder::new() + .title("About geopard + help") + .url("about:help") + .build(), + ); + + bookmarks +}); + #[derive(Clone, Default, Serialize, Deserialize, Debug)] pub struct Bookmark { title: String, @@ -20,7 +53,8 @@ pub struct BookmarkBuilder { #[derive(Clone, Default, Serialize, Deserialize, Debug)] pub struct Bookmarks { - pub bookmarks: Vec, + #[serde(rename = "bookmark")] + pub bookmarks: BTreeMap, } impl BookmarkBuilder { @@ -81,6 +115,7 @@ impl Bookmark { } } +//todo!(bookmarks): Add from_gmi() method for migrations impl Bookmarks { pub async fn from_file(&self, path: &Path) -> anyhow::Result { let file_str = async_fs::read_to_string(path) @@ -102,15 +137,20 @@ impl Bookmarks { Ok(()) } + //todo!(bookmarks): key must be the biggest current key + 1 pub fn insert_bookmark(&mut self, bookmark: Bookmark) { - self.bookmarks.push(bookmark); + self.bookmarks.insert(1.to_string(), bookmark); + } + + pub fn update_bookmark(&mut self, key: u32, new_bookmark: Bookmark) { + self.bookmarks.insert(key.to_string(), new_bookmark); } - pub fn remove_bookmark(&mut self, index: usize) { + pub fn remove_bookmark(&mut self, key: u32) { if self.bookmarks.is_empty() { return; } - self.bookmarks.remove(index); + self.bookmarks.remove(&key.to_string()); } } From 2bb6fe0ef64fd6073baf5d20f3151756cbf295d3 Mon Sep 17 00:00:00 2001 From: tfuxu <73042332+tfuxu@users.noreply.github.com> Date: Mon, 19 Feb 2024 23:35:23 +0100 Subject: [PATCH 06/10] feat: implement support for bookmarks in `BookmarksWindow` - update `BookmarksWindow` UI file --- data/resources/ui/bookmarks.blp | 13 +++++++++++-- src/widgets/bookmarks.rs | 18 ++++++++++++------ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/data/resources/ui/bookmarks.blp b/data/resources/ui/bookmarks.blp index 5da5dcf..e3473ae 100644 --- a/data/resources/ui/bookmarks.blp +++ b/data/resources/ui/bookmarks.blp @@ -13,9 +13,18 @@ template $GeopardBookmarksWindow : Adw.Window { Adw.ToolbarView { [top] Adw.HeaderBar { + centering-policy: strict; [end] - Gtk.Button { + Gtk.ToggleButton search_button { + visible: false; // TODO: Remove + icon-name: "edit-find-symbolic"; + tooltip-text: _("Search"); + } + + [end] + Gtk.Button select_items_button { + visible: false; // TODO: Remove icon-name: "selection-mode-symbolic"; tooltip-text: _("Select Items"); } @@ -65,4 +74,4 @@ template $GeopardBookmarksWindow : Adw.Window { }; } } -} \ No newline at end of file +} diff --git a/src/widgets/bookmarks.rs b/src/widgets/bookmarks.rs index df26dc3..a598e1b 100644 --- a/src/widgets/bookmarks.rs +++ b/src/widgets/bookmarks.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::sync::OnceLock; use adw::prelude::*; @@ -9,6 +10,8 @@ use gtk::{ CompositeTemplate, }; +use crate::bookmarks; + pub mod imp { use super::*; @@ -21,6 +24,7 @@ pub mod imp { pub stack: TemplateChild, #[template_child] pub bookmarks_list: TemplateChild, + pub(crate) bookmarks: RefCell, } #[glib::object_subclass] @@ -62,10 +66,12 @@ glib::wrapper! { } impl BookmarksWindow { - pub fn new(app: >k::Application) -> Self { + pub fn new(app: >k::Application, bookmarks: bookmarks::Bookmarks) -> Self { let this = Object::builder::() .property("application", app) .build(); + let imp = this.imp(); + imp.bookmarks.replace(bookmarks); this.setup(); @@ -77,11 +83,10 @@ impl BookmarksWindow { // TODO: Set to bookmarks_page if there's at least one bookmark imp.stack.set_visible_child_name("bookmarks_page"); - for i in 0..10 { - self.add_row( - &format!("Bookmark {i}"), - &format!("gemini://geminispace.info/search?geopard"), - ); + let bookmarks_map = imp.bookmarks.borrow().clone().bookmarks; + + for (_, bookmark) in bookmarks_map.iter() { + self.add_row(&bookmark.title(), &bookmark.url()); } } @@ -99,6 +104,7 @@ impl BookmarksWindow { let copy_button = gtk::Button::builder() .icon_name("edit-copy-symbolic") + .tooltip_text("Copy URL") .css_classes(vec!["flat"]) .valign(gtk::Align::Center) .build(); From 015c36d33ffd52e78698ce4f6ac5a9962fd72664 Mon Sep 17 00:00:00 2001 From: tfuxu <73042332+tfuxu@users.noreply.github.com> Date: Mon, 19 Feb 2024 23:38:08 +0100 Subject: [PATCH 07/10] feat: load bookmarks at init and connect structs to `BookmarksWindow` --- src/main.rs | 14 ++++++++++++-- src/widgets/window.rs | 14 +++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 95e08fc..9a4d991 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,7 @@ use log::error; use crate::common::{ BOOKMARK_FILE_PATH, CONFIG_DIR_PATH, DATA_DIR_PATH, DEFAULT_BOOKMARKS, HISTORY_FILE_PATH, - SETTINGS_FILE_PATH, + NEW_BOOKMARK_FILE_PATH, SETTINGS_FILE_PATH, }; async fn read_config() -> anyhow::Result { @@ -30,6 +30,12 @@ async fn read_config() -> anyhow::Result { .context("Reading config file") } +async fn read_bookmarks() -> anyhow::Result { + let bookmarks = bookmarks::Bookmarks::default(); + + Ok(bookmarks.from_file(&NEW_BOOKMARK_FILE_PATH).await?) +} + async fn create_dir_if_not_exists(path: &std::path::Path) -> anyhow::Result<()> { if !path.exists() { async_fs::create_dir_all(path) @@ -61,9 +67,11 @@ async fn init_file_if_not_exists( async fn create_base_files() -> anyhow::Result<()> { let default_config = toml::to_string(&*config::DEFAULT_CONFIG).unwrap(); + let default_bookmarks = toml::to_string(&*bookmarks::DEFAULT_BOOKMARKS).unwrap(); create_dir_if_not_exists(&DATA_DIR_PATH).await?; create_dir_if_not_exists(&CONFIG_DIR_PATH).await?; + init_file_if_not_exists(&NEW_BOOKMARK_FILE_PATH, Some(default_bookmarks.as_bytes())).await?; init_file_if_not_exists(&BOOKMARK_FILE_PATH, Some(DEFAULT_BOOKMARKS.as_bytes())).await?; init_file_if_not_exists(&HISTORY_FILE_PATH, None).await?; init_file_if_not_exists(&SETTINGS_FILE_PATH, Some(default_config.as_bytes())).await?; @@ -110,13 +118,15 @@ fn main() { read_config().await.unwrap() }); + let bookmarks = futures::executor::block_on(async { read_bookmarks().await.unwrap() }); + let windows = Rc::new(RefCell::new(vec![])); application .connect_activate(move |app| app.open(&[gio::File::for_uri(bookmarks_url().as_str())], "")); application.connect_open(move |app, files, _| { - let window = widgets::Window::new(app, config.clone()); + let window = widgets::Window::new(app, config.clone(), bookmarks.clone()); window.present(); windows.borrow_mut().push(window.clone()); diff --git a/src/widgets/window.rs b/src/widgets/window.rs index 0b561a4..43f16f2 100644 --- a/src/widgets/window.rs +++ b/src/widgets/window.rs @@ -17,7 +17,7 @@ use crate::common::{bookmarks_url, glibctx, BOOKMARK_FILE_PATH}; use crate::session_provider::SessionProvider; use crate::widgets::bookmarks::BookmarksWindow; use crate::widgets::tab::{HistoryItem, HistoryStatus, Tab}; -use crate::{build_config, config, self_action}; +use crate::{bookmarks, build_config, config, self_action}; const ZOOM_CHANGE_FACTOR: f64 = 1.15; const ZOOM_MAX_FACTOR: f64 = 5.0; @@ -74,6 +74,7 @@ pub mod imp { #[template_child] pub(crate) main_menu_button: TemplateChild, pub(crate) config: RefCell, + pub(crate) bookmarks: RefCell, pub(crate) progress_animation: RefCell>, pub(crate) binded_tab_properties: RefCell>, #[property(get, set)] @@ -172,12 +173,17 @@ glib::wrapper! { } impl Window { - pub fn new(app: &adw::Application, config: config::Config) -> Self { + pub fn new( + app: &adw::Application, + config: config::Config, + bookmarks: bookmarks::Bookmarks, + ) -> Self { let this: Self = glib::Object::builder::() .property("application", app) .build(); let imp = this.imp(); imp.config.replace(config); + imp.bookmarks.replace(bookmarks); imp.zoom.borrow_mut().value = 1.0; this.setup_css_providers(); @@ -733,7 +739,9 @@ impl Window { } fn present_bookmarks(&self) { - let bookmarks = BookmarksWindow::new(&self.application().unwrap()); + let imp = self.imp(); + let bookmarks = + BookmarksWindow::new(&self.application().unwrap(), imp.bookmarks.borrow().clone()); bookmarks.set_transient_for(Some(self)); bookmarks.connect_closure( From 342e18c75a60194a298e1e2992c1f1c1ca752ac9 Mon Sep 17 00:00:00 2001 From: tfuxu <73042332+tfuxu@users.noreply.github.com> Date: Thu, 22 Feb 2024 00:17:05 +0100 Subject: [PATCH 08/10] feat: Remove legacy bookmarks code --- data/resources/ui/window.blp | 4 +-- src/common/mod.rs | 29 ++-------------------- src/main.rs | 22 ++++++++--------- src/widgets/window.rs | 48 +++++++++++++----------------------- 4 files changed, 32 insertions(+), 71 deletions(-) diff --git a/data/resources/ui/window.blp b/data/resources/ui/window.blp index 42a1e22..9667ada 100644 --- a/data/resources/ui/window.blp +++ b/data/resources/ui/window.blp @@ -85,7 +85,7 @@ template $GeopardWindow: Adw.ApplicationWindow { [end] Gtk.Button bookmarks_button { icon-name: "library-symbolic"; - action-name: "win.bookmarks"; + action-name: "win.show-bookmarks"; tooltip-text: _("Bookmarks"); } @@ -180,7 +180,7 @@ template $GeopardWindow: Adw.ApplicationWindow { [end] Gtk.Button { icon-name: "library-symbolic"; - action-name: "win.bookmarks"; + action-name: "win.show-bookmarks"; tooltip-text: _("Bookmarks"); } diff --git a/src/common/mod.rs b/src/common/mod.rs index 9eb6f38..aab39c6 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,6 +1,5 @@ use gtk::glib; use once_cell::sync::Lazy; -use url::Url; pub static DOWNLOAD_PATH: Lazy = Lazy::new(|| { let mut download_path = glib::user_special_dir(glib::UserDirectory::Downloads) @@ -23,12 +22,10 @@ pub static KNOWN_HOSTS_PATH: Lazy = pub static CONFIG_DIR_PATH: Lazy = Lazy::new(|| glib::user_config_dir().join("geopard")); -//todo!(common): Rename this to OLD_BOOKMARK_FILE_PATH -pub static BOOKMARK_FILE_PATH: Lazy = +pub static OLD_BOOKMARK_FILE_PATH: Lazy = Lazy::new(|| DATA_DIR_PATH.join("bookmarks.gemini")); -//todo!(common): Rename this to BOOKMARK_FILE_PATH -pub static NEW_BOOKMARK_FILE_PATH: Lazy = +pub static BOOKMARK_FILE_PATH: Lazy = Lazy::new(|| DATA_DIR_PATH.join("bookmarks.toml")); pub static SETTINGS_FILE_PATH: Lazy = @@ -37,30 +34,8 @@ pub static SETTINGS_FILE_PATH: Lazy = pub static HISTORY_FILE_PATH: Lazy = Lazy::new(|| DATA_DIR_PATH.join("history.gemini")); -//todo!(common): Remove this after implementing new format -pub static DEFAULT_BOOKMARKS: &str = r"# Bookmarks - -This is a gemini file where you can put all your bookmarks. -You can even edit this file in a text editor. That's how you -should remove bookmarks. - -## Default bookmarks - -=> gemini://geminiprotocol.net Gemini project -=> gemini://rawtext.club:1965/~sloum/spacewalk.gmi Spacewalk aggregator -=> about:help About geopard + help - -## Custom bookmarks - -"; - pub const STREAMABLE_EXTS: [&str; 8] = ["mp3", "mp4", "webm", "opus", "wav", "ogg", "mkv", "flac"]; -//todo!(common): Remove this -pub fn bookmarks_url() -> Url { - Url::parse(&format!("file://{}", BOOKMARK_FILE_PATH.to_str().unwrap())).unwrap() -} - pub fn glibctx() -> glib::MainContext { glib::MainContext::default() } diff --git a/src/main.rs b/src/main.rs index 9a4d991..989930b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,15 +14,13 @@ use std::{env, process}; use anyhow::Context; use async_fs::File; -use common::bookmarks_url; use futures::prelude::*; use gtk::gio; use gtk::prelude::*; use log::error; use crate::common::{ - BOOKMARK_FILE_PATH, CONFIG_DIR_PATH, DATA_DIR_PATH, DEFAULT_BOOKMARKS, HISTORY_FILE_PATH, - NEW_BOOKMARK_FILE_PATH, SETTINGS_FILE_PATH, + BOOKMARK_FILE_PATH, CONFIG_DIR_PATH, DATA_DIR_PATH, HISTORY_FILE_PATH, SETTINGS_FILE_PATH, }; async fn read_config() -> anyhow::Result { @@ -32,8 +30,7 @@ async fn read_config() -> anyhow::Result { async fn read_bookmarks() -> anyhow::Result { let bookmarks = bookmarks::Bookmarks::default(); - - Ok(bookmarks.from_file(&NEW_BOOKMARK_FILE_PATH).await?) + Ok(bookmarks.from_file(&BOOKMARK_FILE_PATH).await?) } async fn create_dir_if_not_exists(path: &std::path::Path) -> anyhow::Result<()> { @@ -71,8 +68,7 @@ async fn create_base_files() -> anyhow::Result<()> { create_dir_if_not_exists(&DATA_DIR_PATH).await?; create_dir_if_not_exists(&CONFIG_DIR_PATH).await?; - init_file_if_not_exists(&NEW_BOOKMARK_FILE_PATH, Some(default_bookmarks.as_bytes())).await?; - init_file_if_not_exists(&BOOKMARK_FILE_PATH, Some(DEFAULT_BOOKMARKS.as_bytes())).await?; + init_file_if_not_exists(&BOOKMARK_FILE_PATH, Some(default_bookmarks.as_bytes())).await?; init_file_if_not_exists(&HISTORY_FILE_PATH, None).await?; init_file_if_not_exists(&SETTINGS_FILE_PATH, Some(default_config.as_bytes())).await?; @@ -122,15 +118,19 @@ fn main() { let windows = Rc::new(RefCell::new(vec![])); - application + //todo!(main): Modify to open URLs instead of files (issue #50) + /*application .connect_activate(move |app| app.open(&[gio::File::for_uri(bookmarks_url().as_str())], "")); + */ - application.connect_open(move |app, files, _| { + application.connect_activate(move |app| { let window = widgets::Window::new(app, config.clone(), bookmarks.clone()); window.present(); windows.borrow_mut().push(window.clone()); - for f in files { + gtk::prelude::WidgetExt::activate_action(&window, "win.new-tab", None).unwrap(); + + /*for f in files { gtk::prelude::WidgetExt::activate_action(&window, "win.new-empty-tab", None).unwrap(); gtk::prelude::WidgetExt::activate_action( &window, @@ -138,7 +138,7 @@ fn main() { Some(&f.uri().to_variant()), ) .unwrap(); - } + }*/ }); application.set_accels_for_action("win.previous", &["Left", "KP_Left"]); diff --git a/src/widgets/window.rs b/src/widgets/window.rs index 43f16f2..7e7a54d 100644 --- a/src/widgets/window.rs +++ b/src/widgets/window.rs @@ -4,16 +4,13 @@ use std::marker::PhantomData; use adw::prelude::*; use adw::subclass::application_window::AdwApplicationWindowImpl; -use anyhow::Context; use config::APP_ID; -use futures::prelude::*; use glib::{clone, closure_local, Properties}; use gtk::subclass::prelude::*; use gtk::{gdk, gio, glib, CompositeTemplate, TemplateChild}; use log::{error, info, warn}; use url::Url; -use crate::common::{bookmarks_url, glibctx, BOOKMARK_FILE_PATH}; use crate::session_provider::SessionProvider; use crate::widgets::bookmarks::BookmarksWindow; use crate::widgets::tab::{HistoryItem, HistoryStatus, Tab}; @@ -224,13 +221,12 @@ impl Window { self_action!(self, "reload", reload); self_action!(self, "new-tab", new_tab); self_action!(self, "new-empty-tab", new_empty_tab); - self_action!(self, "show-bookmarks", show_bookmarks); + self_action!(self, "show-bookmarks", present_bookmarks); + //todo!(window): Make it show "New Bookmark" popover self_action!(self, "bookmark-current", bookmark_current); self_action!(self, "close-tab", close_tab); self_action!(self, "focus-url-bar", focus_url_bar); self_action!(self, "shortcuts", present_shortcuts); - // TODO: Switch later to 'show-bookmarks' action - self_action!(self, "bookmarks", present_bookmarks); self_action!(self, "about", present_about); self_action!(self, "focus-previous-tab", focus_previous_tab); self_action!(self, "focus-next-tab", focus_next_tab); @@ -561,21 +557,21 @@ impl Window { }; } fn new_tab(&self) { - self.show_bookmarks(); + //todo!(window): Use user preference to determine what to show in new tab + self.new_empty_tab(); self.focus_url_bar(); } + fn new_empty_tab(&self) { let imp = self.imp(); let p = self.add_tab(); + + p.set_title("Blank Tab"); imp.tab_view.set_selected_page(&p); + self.focus_url_bar(); } - fn show_bookmarks(&self) { - let imp = self.imp(); - let p = self.add_tab(); - imp.tab_view.set_selected_page(&p); - self.inner_tab(&p).spawn_open_url(bookmarks_url()); - } + fn close_tab(&self) { let imp = self.imp(); imp.tab_view @@ -589,22 +585,6 @@ impl Window { self.imp().url_bar.grab_focus(); } - async fn append_bookmark(url: &str) -> anyhow::Result<()> { - let mut file = async_fs::OpenOptions::new() - .write(true) - .append(true) - .open(&*BOOKMARK_FILE_PATH) - .await - .context("Opening bookmark.gemini")?; - - let line_to_write = format!("=> {}\n", url); - file.write_all(line_to_write.as_bytes()) - .await - .context("Writing url to favourite.gemini")?; - - file.flush().await?; - Ok(()) - } fn current_tab(&self) -> Tab { let imp = self.imp(); imp.tab_view @@ -627,7 +607,13 @@ impl Window { let imp = self.imp(); let url = imp.url_bar.text().to_string(); - glibctx().spawn_local(clone!(@weak imp => async move { + //todo!(window): Remove later + info!("{} saved to bookmarks", url); + imp.toast_overlay + .add_toast(adw::Toast::new("Page added to bookmarks")); + + //todo!(window): Replace with new bookmarks logic + /*glibctx().spawn_local(clone!(@weak imp => async move { match Self::append_bookmark(&url).await { Ok(_) => { info!("{} saved to bookmarks", url); @@ -638,7 +624,7 @@ impl Window { imp.toast_overlay.add_toast(adw::Toast::new("Failed to bookmark this page")); }, } - })); + }));*/ } fn open_omni(&self, v: &str) { let url = Url::parse(v).or_else(|_| { From cccc2506899beca8669b5fed3f1cda9e9aab4dbc Mon Sep 17 00:00:00 2001 From: tfuxu <73042332+tfuxu@users.noreply.github.com> Date: Thu, 22 Feb 2024 00:18:51 +0100 Subject: [PATCH 09/10] feat: update default bookmark title --- src/bookmarks.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bookmarks.rs b/src/bookmarks.rs index bd021bd..472edd4 100644 --- a/src/bookmarks.rs +++ b/src/bookmarks.rs @@ -29,7 +29,7 @@ pub static DEFAULT_BOOKMARKS: Lazy = Lazy::new(|| { bookmarks.bookmarks.insert( 3.to_string(), BookmarkBuilder::new() - .title("About geopard + help") + .title("About Geopard + help") .url("about:help") .build(), ); From 6e88899df6e11828a64d14b3943aa0d78d11620c Mon Sep 17 00:00:00 2001 From: tfuxu <73042332+tfuxu@users.noreply.github.com> Date: Sat, 2 Mar 2024 13:56:29 +0100 Subject: [PATCH 10/10] chore: change name of `bookmarks` module to `bookmarks_window` --- data/resources/ui/{bookmarks.blp => bookmarks_window.blp} | 0 src/widgets/{bookmarks.rs => bookmarks_window.rs} | 2 +- src/widgets/mod.rs | 2 +- src/widgets/window.rs | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename data/resources/ui/{bookmarks.blp => bookmarks_window.blp} (100%) rename src/widgets/{bookmarks.rs => bookmarks_window.rs} (98%) diff --git a/data/resources/ui/bookmarks.blp b/data/resources/ui/bookmarks_window.blp similarity index 100% rename from data/resources/ui/bookmarks.blp rename to data/resources/ui/bookmarks_window.blp diff --git a/src/widgets/bookmarks.rs b/src/widgets/bookmarks_window.rs similarity index 98% rename from src/widgets/bookmarks.rs rename to src/widgets/bookmarks_window.rs index a598e1b..7795775 100644 --- a/src/widgets/bookmarks.rs +++ b/src/widgets/bookmarks_window.rs @@ -16,7 +16,7 @@ pub mod imp { use super::*; #[derive(CompositeTemplate, Default)] - #[template(resource = "/com/ranfdev/Geopard/ui/bookmarks.ui")] + #[template(resource = "/com/ranfdev/Geopard/ui/bookmarks_window.ui")] pub struct BookmarksWindow { #[template_child] pub toast_overlay: TemplateChild, diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index a9ce3b7..fc37e5f 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,4 +1,4 @@ -mod bookmarks; +mod bookmarks_window; mod pages; #[allow(clippy::await_holding_refcell_ref)] mod tab; diff --git a/src/widgets/window.rs b/src/widgets/window.rs index 7e7a54d..9696f29 100644 --- a/src/widgets/window.rs +++ b/src/widgets/window.rs @@ -12,7 +12,7 @@ use log::{error, info, warn}; use url::Url; use crate::session_provider::SessionProvider; -use crate::widgets::bookmarks::BookmarksWindow; +use crate::widgets::bookmarks_window::BookmarksWindow; use crate::widgets::tab::{HistoryItem, HistoryStatus, Tab}; use crate::{bookmarks, build_config, config, self_action};