mirror of
https://github.com/YGGverse/Yoda.git
synced 2025-09-14 16:02:05 +00:00
remove Subject
struct, use public Page
API for Client
driver, remove widget
mod
This commit is contained in:
parent
6945aaebc5
commit
ed1dbd421c
@ -142,7 +142,7 @@ impl Tab {
|
|||||||
// Register dynamically created tab components in the HashMap index
|
// Register dynamically created tab components in the HashMap index
|
||||||
self.index
|
self.index
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.insert(item.widget.tab_page.clone(), item.clone());
|
.insert(item.page.tab_page.clone(), item.clone());
|
||||||
|
|
||||||
update_actions(
|
update_actions(
|
||||||
&self.tab_view,
|
&self.tab_view,
|
||||||
@ -305,7 +305,7 @@ impl Tab {
|
|||||||
// Register dynamically created tab item in the HashMap index
|
// Register dynamically created tab item in the HashMap index
|
||||||
self.index
|
self.index
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.insert(item.widget.tab_page.clone(), item.clone());
|
.insert(item.page.tab_page.clone(), item.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => return Err(e.to_string()),
|
Err(e) => return Err(e.to_string()),
|
||||||
@ -333,10 +333,10 @@ impl Tab {
|
|||||||
item.save(
|
item.save(
|
||||||
transaction,
|
transaction,
|
||||||
id,
|
id,
|
||||||
self.tab_view.page_position(&item.widget.tab_page),
|
self.tab_view.page_position(&item.page.tab_page),
|
||||||
item.widget.tab_page.is_pinned(),
|
item.page.tab_page.is_pinned(),
|
||||||
item.widget.tab_page.is_selected(),
|
item.page.tab_page.is_selected(),
|
||||||
item.widget.tab_page.needs_attention(),
|
item.page.tab_page.needs_attention(),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ mod client;
|
|||||||
mod database;
|
mod database;
|
||||||
mod identity;
|
mod identity;
|
||||||
mod page;
|
mod page;
|
||||||
mod widget;
|
|
||||||
|
|
||||||
use super::{Action as TabAction, BrowserAction, Position, WindowAction};
|
use super::{Action as TabAction, BrowserAction, Position, WindowAction};
|
||||||
use crate::Profile;
|
use crate::Profile;
|
||||||
@ -14,14 +13,12 @@ use gtk::prelude::{ActionMapExt, Cast};
|
|||||||
use page::Page;
|
use page::Page;
|
||||||
use sqlite::Transaction;
|
use sqlite::Transaction;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use widget::Widget;
|
|
||||||
|
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
// Multi-protocol handler
|
// Multi-protocol handler
|
||||||
pub client: Rc<Client>,
|
pub client: Rc<Client>,
|
||||||
// Components
|
// Components
|
||||||
pub page: Rc<Page>,
|
pub page: Rc<Page>,
|
||||||
pub widget: Rc<Widget>,
|
|
||||||
pub action: Rc<Action>,
|
pub action: Rc<Action>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +34,7 @@ impl Item {
|
|||||||
&Rc<WindowAction>,
|
&Rc<WindowAction>,
|
||||||
&Rc<TabAction>,
|
&Rc<TabAction>,
|
||||||
),
|
),
|
||||||
(position, request, is_pinned, is_selected, is_attention, is_load): (
|
(position, request, is_pinned, is_selected, is_needs_attention, is_load): (
|
||||||
Position,
|
Position,
|
||||||
Option<&str>,
|
Option<&str>,
|
||||||
bool,
|
bool,
|
||||||
@ -63,18 +60,12 @@ impl Item {
|
|||||||
let page = Rc::new(Page::build(
|
let page = Rc::new(Page::build(
|
||||||
profile,
|
profile,
|
||||||
(browser_action, window_action, tab_action, &action),
|
(browser_action, window_action, tab_action, &action),
|
||||||
));
|
|
||||||
|
|
||||||
let widget = Rc::new(Widget::build(
|
|
||||||
tab_view,
|
tab_view,
|
||||||
&page.g_box,
|
(position, is_pinned, is_selected, is_needs_attention),
|
||||||
None,
|
|
||||||
position,
|
|
||||||
(is_pinned, is_selected, is_attention),
|
|
||||||
));
|
));
|
||||||
|
|
||||||
// Update tab loading indicator
|
// Update tab loading indicator
|
||||||
let client = Rc::new(Client::init(&page, &widget.tab_page));
|
let client = Rc::new(Client::init(&page));
|
||||||
|
|
||||||
// Connect events
|
// Connect events
|
||||||
action.home.connect_activate({
|
action.home.connect_activate({
|
||||||
@ -143,7 +134,6 @@ impl Item {
|
|||||||
Self {
|
Self {
|
||||||
client,
|
client,
|
||||||
page,
|
page,
|
||||||
widget,
|
|
||||||
action,
|
action,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -160,7 +150,6 @@ impl Item {
|
|||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
// Delegate clean action to the item childs
|
// Delegate clean action to the item childs
|
||||||
self.page.clean(transaction, record.id)?;
|
self.page.clean(transaction, record.id)?;
|
||||||
self.widget.clean(transaction, record.id)?;
|
|
||||||
}
|
}
|
||||||
Err(e) => return Err(e.to_string()),
|
Err(e) => return Err(e.to_string()),
|
||||||
}
|
}
|
||||||
@ -203,14 +192,13 @@ impl Item {
|
|||||||
None,
|
None,
|
||||||
record.is_pinned,
|
record.is_pinned,
|
||||||
record.is_selected,
|
record.is_selected,
|
||||||
record.is_attention,
|
record.is_needs_attention,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
// Delegate restore action to the item childs
|
// Delegate restore action to the item childs
|
||||||
item.page.restore(transaction, record.id)?;
|
item.page.restore(transaction, record.id)?;
|
||||||
item.widget.restore(transaction, record.id)?;
|
|
||||||
|
|
||||||
// Result
|
// Result
|
||||||
items.push(item);
|
items.push(item);
|
||||||
@ -229,7 +217,7 @@ impl Item {
|
|||||||
page_position: i32,
|
page_position: i32,
|
||||||
is_pinned: bool,
|
is_pinned: bool,
|
||||||
is_selected: bool,
|
is_selected: bool,
|
||||||
is_attention: bool,
|
is_needs_attention: bool,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
match database::insert(
|
match database::insert(
|
||||||
transaction,
|
transaction,
|
||||||
@ -237,14 +225,13 @@ impl Item {
|
|||||||
page_position,
|
page_position,
|
||||||
is_pinned,
|
is_pinned,
|
||||||
is_selected,
|
is_selected,
|
||||||
is_attention,
|
is_needs_attention,
|
||||||
) {
|
) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let id = database::last_insert_id(transaction);
|
let id = database::last_insert_id(transaction);
|
||||||
|
|
||||||
// Delegate save action to childs
|
// Delegate save action to childs
|
||||||
self.page.save(transaction, id)?;
|
self.page.save(transaction, id)?;
|
||||||
self.widget.save(transaction, id)?;
|
|
||||||
}
|
}
|
||||||
Err(e) => return Err(e.to_string()),
|
Err(e) => return Err(e.to_string()),
|
||||||
}
|
}
|
||||||
@ -262,7 +249,6 @@ pub fn migrate(tx: &Transaction) -> Result<(), String> {
|
|||||||
|
|
||||||
// Delegate migration to childs
|
// Delegate migration to childs
|
||||||
page::migrate(tx)?;
|
page::migrate(tx)?;
|
||||||
widget::migrate(tx)?;
|
|
||||||
|
|
||||||
// Success
|
// Success
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
mod driver;
|
mod driver;
|
||||||
mod feature;
|
mod feature;
|
||||||
mod subject;
|
|
||||||
|
|
||||||
use super::Page;
|
use super::Page;
|
||||||
use adw::TabPage;
|
|
||||||
use driver::Driver;
|
use driver::Driver;
|
||||||
use feature::Feature;
|
use feature::Feature;
|
||||||
use gtk::{
|
use gtk::{
|
||||||
@ -12,29 +10,23 @@ use gtk::{
|
|||||||
prelude::{ActionExt, CancellableExt},
|
prelude::{ActionExt, CancellableExt},
|
||||||
};
|
};
|
||||||
use std::{cell::Cell, rc::Rc};
|
use std::{cell::Cell, rc::Rc};
|
||||||
use subject::Subject;
|
|
||||||
|
|
||||||
/// Multi-protocol client API for tab `Item`
|
/// Multi-protocol client API for tab `Item`
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
cancellable: Cell<Cancellable>,
|
cancellable: Cell<Cancellable>,
|
||||||
driver: Rc<Driver>,
|
driver: Rc<Driver>,
|
||||||
subject: Rc<Subject>,
|
page: Rc<Page>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
/// Create new `Self`
|
/// Create new `Self`
|
||||||
pub fn init(page: &Rc<Page>, tab_page: &TabPage) -> Self {
|
pub fn init(page: &Rc<Page>) -> Self {
|
||||||
let subject = Rc::new(Subject {
|
|
||||||
page: page.clone(),
|
|
||||||
tab_page: tab_page.clone(),
|
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
cancellable: Cell::new(Cancellable::new()),
|
cancellable: Cell::new(Cancellable::new()),
|
||||||
driver: Rc::new(Driver::build(&subject)),
|
driver: Rc::new(Driver::build(page)),
|
||||||
subject,
|
page: page.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,32 +36,29 @@ impl Client {
|
|||||||
/// * or `navigation` entry if the value not provided
|
/// * or `navigation` entry if the value not provided
|
||||||
pub fn handle(&self, request: &str, is_snap_history: bool) {
|
pub fn handle(&self, request: &str, is_snap_history: bool) {
|
||||||
// Move focus out from navigation entry @TODO
|
// Move focus out from navigation entry @TODO
|
||||||
self.subject.page.browser_action.escape.activate(None);
|
self.page.browser_action.escape.activate(None);
|
||||||
|
|
||||||
// Initially disable find action
|
// Initially disable find action
|
||||||
self.subject
|
self.page
|
||||||
.page
|
|
||||||
.window_action
|
.window_action
|
||||||
.find
|
.find
|
||||||
.simple_action
|
.simple_action
|
||||||
.set_enabled(false);
|
.set_enabled(false);
|
||||||
|
|
||||||
// Reset widgets
|
// Reset widgets
|
||||||
self.subject.page.search.unset();
|
self.page.search.unset();
|
||||||
self.subject.page.input.unset();
|
self.page.input.unset();
|
||||||
self.subject.tab_page.set_title("Loading..");
|
self.page.set_title("Loading..");
|
||||||
self.subject.page.navigation.set_progress_fraction(0.1);
|
self.page.set_progress(0.1);
|
||||||
|
|
||||||
self.subject.tab_page.set_loading(true);
|
|
||||||
|
|
||||||
if is_snap_history {
|
if is_snap_history {
|
||||||
snap_history(&self.subject, None);
|
snap_history(&self.page, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// run async resolver to detect Uri, scheme-less host, or search query
|
// run async resolver to detect Uri, scheme-less host, or search query
|
||||||
lookup(request, self.cancellable(), {
|
lookup(request, self.cancellable(), {
|
||||||
let driver = self.driver.clone();
|
let driver = self.driver.clone();
|
||||||
let subject = self.subject.clone();
|
let page = self.page.clone();
|
||||||
move |feature, cancellable, result| {
|
move |feature, cancellable, result| {
|
||||||
match result {
|
match result {
|
||||||
// route by scheme
|
// route by scheme
|
||||||
@ -77,18 +66,16 @@ impl Client {
|
|||||||
"gemini" | "titan" => driver.gemini.handle(uri, feature, cancellable),
|
"gemini" | "titan" => driver.gemini.handle(uri, feature, cancellable),
|
||||||
scheme => {
|
scheme => {
|
||||||
// no scheme match driver, complete with failure message
|
// no scheme match driver, complete with failure message
|
||||||
let status = subject.page.content.to_status_failure();
|
let status = page.content.to_status_failure();
|
||||||
status.set_description(Some(&format!(
|
status.set_description(Some(&format!(
|
||||||
"Scheme `{scheme}` yet not supported"
|
"Scheme `{scheme}` yet not supported"
|
||||||
)));
|
)));
|
||||||
subject.tab_page.set_title(&status.title());
|
page.set_title(&status.title());
|
||||||
subject.page.navigation.set_progress_fraction(0.0);
|
page.set_progress(0.0);
|
||||||
subject.tab_page.set_loading(false);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// begin redirection to new address suggested
|
// begin redirection to new address suggested
|
||||||
Err(uri) => subject
|
Err(uri) => page
|
||||||
.page
|
|
||||||
.item_action
|
.item_action
|
||||||
.load
|
.load
|
||||||
.activate(Some(&uri.to_string()), false),
|
.activate(Some(&uri.to_string()), false),
|
||||||
@ -190,22 +177,22 @@ fn search(query: &str) -> Uri {
|
|||||||
|
|
||||||
/// Make new history record in related components
|
/// Make new history record in related components
|
||||||
/// * optional [Uri](https://docs.gtk.org/glib/struct.Uri.html) reference wanted only for performance reasons, to not parse it twice
|
/// * optional [Uri](https://docs.gtk.org/glib/struct.Uri.html) reference wanted only for performance reasons, to not parse it twice
|
||||||
fn snap_history(subject: &Rc<Subject>, uri: Option<&Uri>) {
|
fn snap_history(page: &Page, uri: Option<&Uri>) {
|
||||||
let request = subject.page.navigation.request();
|
let request = page.navigation.request();
|
||||||
|
|
||||||
// Add new record into the global memory index (used in global menu)
|
// Add new record into the global memory index (used in global menu)
|
||||||
// * if the `Uri` is `None`, try parse it from `request`
|
// * if the `Uri` is `None`, try parse it from `request`
|
||||||
match uri {
|
match uri {
|
||||||
Some(uri) => subject.page.profile.history.memory.request.set(uri.clone()),
|
Some(uri) => page.profile.history.memory.request.set(uri.clone()),
|
||||||
None => {
|
None => {
|
||||||
// this case especially useful for some routes that contain redirects
|
// this case especially useful for some routes that contain redirects
|
||||||
// maybe some parental optimization wanted @TODO
|
// maybe some parental optimization wanted @TODO
|
||||||
if let Some(uri) = subject.page.navigation.uri() {
|
if let Some(uri) = page.navigation.uri() {
|
||||||
subject.page.profile.history.memory.request.set(uri);
|
page.profile.history.memory.request.set(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new record into the page navigation history
|
// Add new record into the page navigation history
|
||||||
subject.page.item_action.history.add(request, true)
|
page.item_action.history.add(request, true)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
mod gemini;
|
mod gemini;
|
||||||
|
|
||||||
use super::{Feature, Subject};
|
use super::{Feature, Page};
|
||||||
use gemini::Gemini;
|
use gemini::Gemini;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
@ -13,9 +13,9 @@ impl Driver {
|
|||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
/// Build new `Self`
|
/// Build new `Self`
|
||||||
pub fn build(subject: &Rc<Subject>) -> Self {
|
pub fn build(page: &Rc<Page>) -> Self {
|
||||||
Driver {
|
Driver {
|
||||||
gemini: Gemini::init(subject),
|
gemini: Gemini::init(page),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
use super::{Feature, Subject};
|
use super::{Feature, Page};
|
||||||
use ggemini::client::{
|
use ggemini::client::{
|
||||||
connection::response::{data::Text, meta::Status},
|
connection::response::{data::Text, meta::Status},
|
||||||
Client, Request,
|
Client, Request,
|
||||||
};
|
};
|
||||||
|
use gtk::glib::Bytes;
|
||||||
use gtk::glib::{GString, UriFlags};
|
use gtk::glib::{GString, UriFlags};
|
||||||
use gtk::{
|
use gtk::{
|
||||||
gdk::Texture,
|
gdk::Texture,
|
||||||
gdk_pixbuf::Pixbuf,
|
gdk_pixbuf::Pixbuf,
|
||||||
gio::{Cancellable, SocketClientEvent},
|
gio::{Cancellable, SocketClientEvent},
|
||||||
glib::{Priority, Uri},
|
glib::{Priority, Uri},
|
||||||
prelude::SocketClientExt,
|
prelude::{FileExt, SocketClientExt},
|
||||||
};
|
};
|
||||||
use gtk::{glib::Bytes, prelude::FileExt};
|
|
||||||
use std::{cell::Cell, path::MAIN_SEPARATOR, rc::Rc, time::Duration};
|
use std::{cell::Cell, path::MAIN_SEPARATOR, rc::Rc, time::Duration};
|
||||||
|
|
||||||
/// Multi-protocol client API for `Page` object
|
/// Multi-protocol client API for `Page` object
|
||||||
@ -21,22 +21,22 @@ pub struct Gemini {
|
|||||||
/// Validate redirection count by Gemini protocol specification
|
/// Validate redirection count by Gemini protocol specification
|
||||||
redirects: Rc<Cell<usize>>,
|
redirects: Rc<Cell<usize>>,
|
||||||
/// Handle target
|
/// Handle target
|
||||||
subject: Rc<Subject>,
|
page: Rc<Page>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Gemini {
|
impl Gemini {
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
/// Create new `Self`
|
/// Create new `Self`
|
||||||
pub fn init(subject: &Rc<Subject>) -> Self {
|
pub fn init(page: &Rc<Page>) -> Self {
|
||||||
// Init supported protocol libraries
|
// Init supported protocol libraries
|
||||||
let client = Rc::new(ggemini::Client::new());
|
let client = Rc::new(ggemini::Client::new());
|
||||||
|
|
||||||
// Listen for [SocketClient](https://docs.gtk.org/gio/class.SocketClient.html) updates
|
// Listen for [SocketClient](https://docs.gtk.org/gio/class.SocketClient.html) updates
|
||||||
client.socket.connect_event({
|
client.socket.connect_event({
|
||||||
let subject = subject.clone();
|
let page = page.clone();
|
||||||
move |_, event, _, _| {
|
move |_, event, _, _| {
|
||||||
let progress_fraction = match event {
|
page.set_progress(match event {
|
||||||
// 0.1 reserved for handle begin
|
// 0.1 reserved for handle begin
|
||||||
SocketClientEvent::Resolving => 0.2,
|
SocketClientEvent::Resolving => 0.2,
|
||||||
SocketClientEvent::Resolved => 0.3,
|
SocketClientEvent::Resolved => 0.3,
|
||||||
@ -49,21 +49,14 @@ impl Gemini {
|
|||||||
SocketClientEvent::TlsHandshaked => 0.9,
|
SocketClientEvent::TlsHandshaked => 0.9,
|
||||||
SocketClientEvent::Complete => 1.0,
|
SocketClientEvent::Complete => 1.0,
|
||||||
_ => todo!(), // alert on API change
|
_ => todo!(), // alert on API change
|
||||||
};
|
})
|
||||||
|
|
||||||
subject.tab_page.set_loading(progress_fraction > 0.0);
|
|
||||||
|
|
||||||
subject
|
|
||||||
.page
|
|
||||||
.navigation
|
|
||||||
.set_progress_fraction(progress_fraction);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
client,
|
client,
|
||||||
redirects: Rc::new(Cell::new(0)),
|
redirects: Rc::new(Cell::new(0)),
|
||||||
subject: subject.clone(),
|
page: page.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,15 +69,15 @@ impl Gemini {
|
|||||||
"gemini" => handle(
|
"gemini" => handle(
|
||||||
Request::Gemini { uri },
|
Request::Gemini { uri },
|
||||||
self.client.clone(),
|
self.client.clone(),
|
||||||
self.subject.clone(),
|
self.page.clone(),
|
||||||
self.redirects.clone(),
|
self.redirects.clone(),
|
||||||
feature,
|
feature,
|
||||||
cancellable,
|
cancellable,
|
||||||
),
|
),
|
||||||
"titan" => {
|
"titan" => {
|
||||||
self.subject.page.input.set_new_titan({
|
self.page.input.set_new_titan({
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let subject = self.subject.clone();
|
let page = self.page.clone();
|
||||||
let redirects = self.redirects.clone();
|
let redirects = self.redirects.clone();
|
||||||
move |data, _label| {
|
move |data, _label| {
|
||||||
handle(
|
handle(
|
||||||
@ -96,7 +89,7 @@ impl Gemini {
|
|||||||
token: None, // @TODO
|
token: None, // @TODO
|
||||||
},
|
},
|
||||||
client.clone(),
|
client.clone(),
|
||||||
subject.clone(),
|
page.clone(),
|
||||||
redirects.clone(),
|
redirects.clone(),
|
||||||
feature.clone(),
|
feature.clone(),
|
||||||
cancellable.clone(),
|
cancellable.clone(),
|
||||||
@ -123,9 +116,8 @@ impl Gemini {
|
|||||||
todo!()*/
|
todo!()*/
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
self.subject.tab_page.set_title("Titan input");
|
self.page.set_title("Titan input");
|
||||||
self.subject.page.navigation.set_progress_fraction(0.0);
|
self.page.set_progress(0.0);
|
||||||
self.subject.tab_page.set_loading(false);
|
|
||||||
}
|
}
|
||||||
_ => panic!(), // unexpected
|
_ => panic!(), // unexpected
|
||||||
}
|
}
|
||||||
@ -135,7 +127,7 @@ impl Gemini {
|
|||||||
fn handle(
|
fn handle(
|
||||||
request: Request,
|
request: Request,
|
||||||
client: Rc<Client>,
|
client: Rc<Client>,
|
||||||
subject: Rc<Subject>,
|
page: Rc<Page>,
|
||||||
redirects: Rc<Cell<usize>>,
|
redirects: Rc<Cell<usize>>,
|
||||||
feature: Rc<Feature>,
|
feature: Rc<Feature>,
|
||||||
cancellable: Cancellable,
|
cancellable: Cancellable,
|
||||||
@ -147,8 +139,7 @@ fn handle(
|
|||||||
cancellable.clone(),
|
cancellable.clone(),
|
||||||
// Search for user certificate match request
|
// Search for user certificate match request
|
||||||
// * @TODO this feature does not support multi-protocol yet
|
// * @TODO this feature does not support multi-protocol yet
|
||||||
match subject
|
match page
|
||||||
.page
|
|
||||||
.profile
|
.profile
|
||||||
.identity
|
.identity
|
||||||
.get(&uri.to_string())
|
.get(&uri.to_string())
|
||||||
@ -160,12 +151,12 @@ fn handle(
|
|||||||
None => None,
|
None => None,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
let subject = subject.clone();
|
let page = page.clone();
|
||||||
let redirects = redirects.clone();
|
let redirects = redirects.clone();
|
||||||
move |result| {
|
move |result| {
|
||||||
// Remove input forms when redirection expected has not been applied (e.g. failure status)
|
// Remove input forms when redirection expected has not been applied (e.g. failure status)
|
||||||
// @TODO implement input data recovery on error (it's also available before unset, but reference lost at this point)
|
// @TODO implement input data recovery on error (it's also available before unset, but reference lost at this point)
|
||||||
subject.page.input.unset();
|
page.input.unset();
|
||||||
|
|
||||||
// Begin result handle
|
// Begin result handle
|
||||||
match result {
|
match result {
|
||||||
@ -178,30 +169,29 @@ fn handle(
|
|||||||
None => Status::Input.to_string(),
|
None => Status::Input.to_string(),
|
||||||
};
|
};
|
||||||
if matches!(response.meta.status, Status::SensitiveInput) {
|
if matches!(response.meta.status, Status::SensitiveInput) {
|
||||||
subject.page.input.set_new_sensitive(
|
page.input.set_new_sensitive(
|
||||||
subject.page.item_action.clone(),
|
page.item_action.clone(),
|
||||||
uri,
|
uri,
|
||||||
Some(&title),
|
Some(&title),
|
||||||
Some(1024),
|
Some(1024),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
subject.page.input.set_new_response(
|
page.input.set_new_response(
|
||||||
subject.page.item_action.clone(),
|
page.item_action.clone(),
|
||||||
uri,
|
uri,
|
||||||
Some(&title),
|
Some(&title),
|
||||||
Some(1024),
|
Some(1024),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
subject.page.navigation.set_progress_fraction(0.0);
|
page.set_progress(0.0);
|
||||||
subject.tab_page.set_loading(false);
|
page.set_title(&title);
|
||||||
subject.tab_page.set_title(&title);
|
|
||||||
redirects.replace(0); // reset
|
redirects.replace(0); // reset
|
||||||
}
|
}
|
||||||
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-20
|
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-20
|
||||||
Status::Success => match *feature {
|
Status::Success => match *feature {
|
||||||
Feature::Download => {
|
Feature::Download => {
|
||||||
// Init download widget
|
// Init download widget
|
||||||
let status = subject.page.content.to_status_download(
|
let status = page.content.to_status_download(
|
||||||
uri_to_title(&uri).trim_matches(MAIN_SEPARATOR), // grab default filename from base URI,
|
uri_to_title(&uri).trim_matches(MAIN_SEPARATOR), // grab default filename from base URI,
|
||||||
// format FS entities
|
// format FS entities
|
||||||
&cancellable,
|
&cancellable,
|
||||||
@ -262,9 +252,8 @@ fn handle(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
subject.page.navigation.set_progress_fraction(0.0);
|
page.set_progress(0.0);
|
||||||
subject.tab_page.set_loading(false);
|
page.set_title(&status.title());
|
||||||
subject.tab_page.set_title(&status.title());
|
|
||||||
redirects.replace(0); // reset
|
redirects.replace(0); // reset
|
||||||
},
|
},
|
||||||
_ => match response.meta.mime {
|
_ => match response.meta.mime {
|
||||||
@ -276,36 +265,34 @@ fn handle(
|
|||||||
move |result| match result {
|
move |result| match result {
|
||||||
Ok(text) => {
|
Ok(text) => {
|
||||||
let widget = if matches!(*feature, Feature::Source) {
|
let widget = if matches!(*feature, Feature::Source) {
|
||||||
subject.page.content.to_text_source(&text.to_string())
|
page.content.to_text_source(&text.to_string())
|
||||||
} else {
|
} else {
|
||||||
subject.page.content.to_text_gemini(&uri, &text.to_string())
|
page.content.to_text_gemini(&uri, &text.to_string())
|
||||||
};
|
};
|
||||||
subject.page.search.set(Some(widget.text_view));
|
page.search.set(Some(widget.text_view));
|
||||||
subject.tab_page.set_title(&match widget.meta.title {
|
page.set_title(&match widget.meta.title {
|
||||||
Some(title) => title.into(), // @TODO
|
Some(title) => title.into(), // @TODO
|
||||||
None => uri_to_title(&uri),
|
None => uri_to_title(&uri),
|
||||||
});
|
});
|
||||||
subject.page.navigation.set_progress_fraction(0.0);
|
page.set_progress(0.0);
|
||||||
subject.tab_page.set_loading(false);
|
page.window_action
|
||||||
subject.page.window_action
|
|
||||||
.find
|
.find
|
||||||
.simple_action
|
.simple_action
|
||||||
.set_enabled(true);
|
.set_enabled(true);
|
||||||
redirects.replace(0); // reset
|
redirects.replace(0); // reset
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let status = subject.page.content.to_status_failure();
|
let status = page.content.to_status_failure();
|
||||||
status.set_description(Some(&e.to_string()));
|
status.set_description(Some(&e.to_string()));
|
||||||
subject.page.navigation.set_progress_fraction(0.0);
|
page.set_progress(0.0);
|
||||||
subject.tab_page.set_loading(false);
|
page.set_title(&status.title());
|
||||||
subject.tab_page.set_title(&status.title());
|
|
||||||
redirects.replace(0); // reset
|
redirects.replace(0); // reset
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
"image/png" | "image/gif" | "image/jpeg" | "image/webp" => {
|
"image/png" | "image/gif" | "image/jpeg" | "image/webp" => {
|
||||||
// Final image size unknown, show loading widget
|
// Final image size unknown, show loading widget
|
||||||
let status = subject.page.content.to_status_loading(
|
let status = page.content.to_status_loading(
|
||||||
Some(Duration::from_secs(1)), // show if download time > 1 second
|
Some(Duration::from_secs(1)), // show if download time > 1 second
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -322,7 +309,7 @@ fn handle(
|
|||||||
move |_, total|
|
move |_, total|
|
||||||
status.set_description(Some(&format!("Download: {total} bytes"))),
|
status.set_description(Some(&format!("Download: {total} bytes"))),
|
||||||
{
|
{
|
||||||
let subject = subject.clone();
|
let page = page.clone();
|
||||||
move |result| match result {
|
move |result| match result {
|
||||||
Ok((memory_input_stream, _)) => {
|
Ok((memory_input_stream, _)) => {
|
||||||
Pixbuf::from_stream_async(
|
Pixbuf::from_stream_async(
|
||||||
@ -331,27 +318,25 @@ fn handle(
|
|||||||
move |result| {
|
move |result| {
|
||||||
match result {
|
match result {
|
||||||
Ok(buffer) => {
|
Ok(buffer) => {
|
||||||
subject.tab_page.set_title(&uri_to_title(&uri));
|
page.set_title(&uri_to_title(&uri));
|
||||||
subject.page.content.to_image(&Texture::for_pixbuf(&buffer));
|
page.content.to_image(&Texture::for_pixbuf(&buffer));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let status = subject.page.content.to_status_failure();
|
let status = page.content.to_status_failure();
|
||||||
status.set_description(Some(e.message()));
|
status.set_description(Some(e.message()));
|
||||||
subject.tab_page.set_title(&status.title());
|
page.set_title(&status.title());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
subject.page.navigation.set_progress_fraction(0.0);
|
page.set_progress(0.0);
|
||||||
subject.tab_page.set_loading(false);
|
|
||||||
redirects.replace(0); // reset
|
redirects.replace(0); // reset
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let status = subject.page.content.to_status_failure();
|
let status = page.content.to_status_failure();
|
||||||
status.set_description(Some(&e.to_string()));
|
status.set_description(Some(&e.to_string()));
|
||||||
subject.page.navigation.set_progress_fraction(0.0);
|
page.set_progress(0.0);
|
||||||
subject.tab_page.set_loading(false);
|
page.set_title(&status.title());
|
||||||
subject.tab_page.set_title(&status.title());
|
|
||||||
redirects.replace(0); // reset
|
redirects.replace(0); // reset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -359,22 +344,20 @@ fn handle(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
mime => {
|
mime => {
|
||||||
let status = subject.page
|
let status = page
|
||||||
.content
|
.content
|
||||||
.to_status_mime(mime, Some((&subject.page.item_action, &uri)));
|
.to_status_mime(mime, Some((&page.item_action, &uri)));
|
||||||
status.set_description(Some(&format!("Content type `{mime}` yet not supported")));
|
status.set_description(Some(&format!("Content type `{mime}` yet not supported")));
|
||||||
subject.page.navigation.set_progress_fraction(0.0);
|
page.set_progress(0.0);
|
||||||
subject.tab_page.set_loading(false);
|
page.set_title(&status.title());
|
||||||
subject.tab_page.set_title(&status.title());
|
|
||||||
redirects.replace(0); // reset
|
redirects.replace(0); // reset
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
let status = subject.page.content.to_status_failure();
|
let status = page.content.to_status_failure();
|
||||||
status.set_description(Some("MIME type not found"));
|
status.set_description(Some("MIME type not found"));
|
||||||
subject.page.navigation.set_progress_fraction(0.0);
|
page.set_progress(0.0);
|
||||||
subject.tab_page.set_loading(false);
|
page.set_title(&status.title());
|
||||||
subject.tab_page.set_title(&status.title());
|
|
||||||
redirects.replace(0); // reset
|
redirects.replace(0); // reset
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -408,48 +391,43 @@ fn handle(
|
|||||||
let total = redirects.take() + 1;
|
let total = redirects.take() + 1;
|
||||||
// Validate total redirects by protocol specification
|
// Validate total redirects by protocol specification
|
||||||
if total > 5 {
|
if total > 5 {
|
||||||
let status = subject.page.content.to_status_failure();
|
let status = page.content.to_status_failure();
|
||||||
status.set_description(Some("Redirection limit reached"));
|
status.set_description(Some("Redirection limit reached"));
|
||||||
subject.page.navigation.set_progress_fraction(0.0);
|
page.set_progress(0.0);
|
||||||
subject.tab_page.set_loading(false);
|
page.set_title(&status.title());
|
||||||
subject.tab_page.set_title(&status.title());
|
|
||||||
redirects.replace(0); // reset
|
redirects.replace(0); // reset
|
||||||
|
|
||||||
// Disallow external redirection by protocol restrictions
|
// Disallow external redirection by protocol restrictions
|
||||||
} else if "gemini" != target.scheme()
|
} else if "gemini" != target.scheme()
|
||||||
|| uri.port() != target.port()
|
|| uri.port() != target.port()
|
||||||
|| uri.host() != target.host() {
|
|| uri.host() != target.host() {
|
||||||
let status = subject.page.content.to_status_failure();
|
let status = page.content.to_status_failure();
|
||||||
status.set_description(Some("External redirects not allowed by protocol specification"));
|
status.set_description(Some("External redirects not allowed by protocol specification"));
|
||||||
subject.page.navigation.set_progress_fraction(0.0);
|
page.set_progress(0.0);
|
||||||
subject.tab_page.set_loading(false);
|
page.set_title(&status.title());
|
||||||
subject.tab_page.set_title(&status.title());
|
|
||||||
redirects.replace(0); // reset
|
redirects.replace(0); // reset
|
||||||
// Valid
|
// Valid
|
||||||
} else {
|
} else {
|
||||||
if matches!(response.meta.status, Status::PermanentRedirect) {
|
if matches!(response.meta.status, Status::PermanentRedirect) {
|
||||||
subject.page.navigation
|
page.navigation.set_request(&uri.to_string());
|
||||||
.set_request(&uri.to_string());
|
|
||||||
}
|
}
|
||||||
redirects.replace(total);
|
redirects.replace(total);
|
||||||
subject.page.item_action.load.activate(Some(&target.to_string()), false);
|
page.item_action.load.activate(Some(&target.to_string()), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let status = subject.page.content.to_status_failure();
|
let status = page.content.to_status_failure();
|
||||||
status.set_description(Some(&e.to_string()));
|
status.set_description(Some(&e.to_string()));
|
||||||
subject.page.navigation.set_progress_fraction(0.0);
|
page.set_progress(0.0);
|
||||||
subject.tab_page.set_loading(false);
|
page.set_title(&status.title());
|
||||||
subject.tab_page.set_title(&status.title());
|
|
||||||
redirects.replace(0); // reset
|
redirects.replace(0); // reset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let status = subject.page.content.to_status_failure();
|
let status = page.content.to_status_failure();
|
||||||
status.set_description(Some("Redirection target not found"));
|
status.set_description(Some("Redirection target not found"));
|
||||||
subject.page.navigation.set_progress_fraction(0.0);
|
page.set_progress(0.0);
|
||||||
subject.tab_page.set_loading(false);
|
page.set_title(&status.title());
|
||||||
subject.tab_page.set_title(&status.title());
|
|
||||||
redirects.replace(0); // reset
|
redirects.replace(0); // reset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -460,33 +438,30 @@ fn handle(
|
|||||||
Status::CertificateUnauthorized |
|
Status::CertificateUnauthorized |
|
||||||
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-62-certificate-not-valid
|
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-62-certificate-not-valid
|
||||||
Status::CertificateInvalid => {
|
Status::CertificateInvalid => {
|
||||||
let status = subject.page.content.to_status_identity();
|
let status = page.content.to_status_identity();
|
||||||
status.set_description(Some(&match response.meta.data {
|
status.set_description(Some(&match response.meta.data {
|
||||||
Some(data) => data.to_string(),
|
Some(data) => data.to_string(),
|
||||||
None => response.meta.status.to_string(),
|
None => response.meta.status.to_string(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
subject.page.navigation.set_progress_fraction(0.0);
|
page.set_progress(0.0);
|
||||||
subject.tab_page.set_loading(false);
|
page.set_title(&status.title());
|
||||||
subject.tab_page.set_title(&status.title());
|
|
||||||
redirects.replace(0); // reset
|
redirects.replace(0); // reset
|
||||||
}
|
}
|
||||||
error => {
|
error => {
|
||||||
let status = subject.page.content.to_status_failure();
|
let status = page.content.to_status_failure();
|
||||||
status.set_description(Some(&error.to_string()));
|
status.set_description(Some(&error.to_string()));
|
||||||
subject.page.navigation.set_progress_fraction(0.0);
|
page.set_progress(0.0);
|
||||||
subject.tab_page.set_loading(false);
|
page.set_title(&status.title());
|
||||||
subject.tab_page.set_title(&status.title());
|
|
||||||
redirects.replace(0); // reset
|
redirects.replace(0); // reset
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let status = subject.page.content.to_status_failure();
|
let status = page.content.to_status_failure();
|
||||||
status.set_description(Some(&e.to_string()));
|
status.set_description(Some(&e.to_string()));
|
||||||
subject.page.navigation.set_progress_fraction(0.0);
|
page.set_progress(0.0);
|
||||||
subject.tab_page.set_loading(false);
|
page.set_title(&status.title());
|
||||||
subject.tab_page.set_title(&status.title());
|
|
||||||
redirects.replace(0); // reset
|
redirects.replace(0); // reset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
use super::Page;
|
|
||||||
use adw::TabPage;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
/// The subject for `Client` handler
|
|
||||||
pub struct Subject {
|
|
||||||
pub page: Rc<Page>,
|
|
||||||
pub tab_page: TabPage,
|
|
||||||
}
|
|
@ -5,7 +5,7 @@ pub struct Table {
|
|||||||
// pub app_browser_window_tab_id: i64, not in use
|
// pub app_browser_window_tab_id: i64, not in use
|
||||||
pub is_pinned: bool,
|
pub is_pinned: bool,
|
||||||
pub is_selected: bool,
|
pub is_selected: bool,
|
||||||
pub is_attention: bool,
|
pub is_needs_attention: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(tx: &Transaction) -> Result<usize, Error> {
|
pub fn init(tx: &Transaction) -> Result<usize, Error> {
|
||||||
@ -17,7 +17,7 @@ pub fn init(tx: &Transaction) -> Result<usize, Error> {
|
|||||||
`page_position` INTEGER NOT NULL,
|
`page_position` INTEGER NOT NULL,
|
||||||
`is_pinned` INTEGER NOT NULL,
|
`is_pinned` INTEGER NOT NULL,
|
||||||
`is_selected` INTEGER NOT NULL,
|
`is_selected` INTEGER NOT NULL,
|
||||||
`is_attention` INTEGER NOT NULL,
|
`is_needs_attention` INTEGER NOT NULL,
|
||||||
|
|
||||||
FOREIGN KEY (`app_browser_window_tab_id`) REFERENCES `app_browser_window_tab`(`id`)
|
FOREIGN KEY (`app_browser_window_tab_id`) REFERENCES `app_browser_window_tab`(`id`)
|
||||||
)",
|
)",
|
||||||
@ -31,7 +31,7 @@ pub fn insert(
|
|||||||
page_position: i32,
|
page_position: i32,
|
||||||
is_pinned: bool,
|
is_pinned: bool,
|
||||||
is_selected: bool,
|
is_selected: bool,
|
||||||
is_attention: bool,
|
is_needs_attention: bool,
|
||||||
) -> Result<usize, Error> {
|
) -> Result<usize, Error> {
|
||||||
tx.execute(
|
tx.execute(
|
||||||
"INSERT INTO `app_browser_window_tab_item` (
|
"INSERT INTO `app_browser_window_tab_item` (
|
||||||
@ -39,14 +39,14 @@ pub fn insert(
|
|||||||
`page_position`,
|
`page_position`,
|
||||||
`is_pinned`,
|
`is_pinned`,
|
||||||
`is_selected`,
|
`is_selected`,
|
||||||
`is_attention`
|
`is_needs_attention`
|
||||||
) VALUES (?, ?, ?, ?, ?)",
|
) VALUES (?, ?, ?, ?, ?)",
|
||||||
[
|
[
|
||||||
app_browser_window_tab_id,
|
app_browser_window_tab_id,
|
||||||
page_position as i64,
|
page_position as i64,
|
||||||
is_pinned as i64,
|
is_pinned as i64,
|
||||||
is_selected as i64,
|
is_selected as i64,
|
||||||
is_attention as i64,
|
is_needs_attention as i64,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -57,7 +57,7 @@ pub fn select(tx: &Transaction, app_browser_window_tab_id: i64) -> Result<Vec<Ta
|
|||||||
`app_browser_window_tab_id`,
|
`app_browser_window_tab_id`,
|
||||||
`is_pinned`,
|
`is_pinned`,
|
||||||
`is_selected`,
|
`is_selected`,
|
||||||
`is_attention`
|
`is_needs_attention`
|
||||||
FROM `app_browser_window_tab_item`
|
FROM `app_browser_window_tab_item`
|
||||||
WHERE `app_browser_window_tab_id` = ?
|
WHERE `app_browser_window_tab_id` = ?
|
||||||
ORDER BY `page_position` ASC", // just order by, no store in struct wanted
|
ORDER BY `page_position` ASC", // just order by, no store in struct wanted
|
||||||
@ -69,7 +69,7 @@ pub fn select(tx: &Transaction, app_browser_window_tab_id: i64) -> Result<Vec<Ta
|
|||||||
// app_browser_window_tab_id: row.get(1)?, not in use
|
// app_browser_window_tab_id: row.get(1)?, not in use
|
||||||
is_pinned: row.get(2)?,
|
is_pinned: row.get(2)?,
|
||||||
is_selected: row.get(3)?,
|
is_selected: row.get(3)?,
|
||||||
is_attention: row.get(4)?,
|
is_needs_attention: row.get(4)?,
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -5,13 +5,15 @@ mod input;
|
|||||||
mod navigation;
|
mod navigation;
|
||||||
mod search;
|
mod search;
|
||||||
|
|
||||||
use super::{Action as ItemAction, BrowserAction, Profile, TabAction, WindowAction};
|
use super::{Action as ItemAction, BrowserAction, Position, Profile, TabAction, WindowAction};
|
||||||
|
use adw::{TabPage, TabView};
|
||||||
use content::Content;
|
use content::Content;
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use gtk::{prelude::BoxExt, Box, Orientation};
|
use gtk::{prelude::BoxExt, Box, Orientation};
|
||||||
use input::Input;
|
use input::Input;
|
||||||
use navigation::Navigation;
|
use navigation::Navigation;
|
||||||
use search::Search;
|
use search::Search;
|
||||||
|
use sourceview::prelude::IsA;
|
||||||
use sqlite::Transaction;
|
use sqlite::Transaction;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
@ -26,7 +28,8 @@ pub struct Page {
|
|||||||
pub search: Rc<Search>,
|
pub search: Rc<Search>,
|
||||||
pub input: Rc<Input>,
|
pub input: Rc<Input>,
|
||||||
pub navigation: Rc<Navigation>,
|
pub navigation: Rc<Navigation>,
|
||||||
pub g_box: Box,
|
// System
|
||||||
|
pub tab_page: TabPage,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Page {
|
impl Page {
|
||||||
@ -40,17 +43,16 @@ impl Page {
|
|||||||
&Rc<TabAction>,
|
&Rc<TabAction>,
|
||||||
&Rc<ItemAction>,
|
&Rc<ItemAction>,
|
||||||
),
|
),
|
||||||
|
tab_view: &TabView,
|
||||||
|
(position, is_pinned, is_selected, is_needs_attention): (Position, bool, bool, bool),
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Init components
|
// Init components
|
||||||
let content = Rc::new(Content::build((window_action, item_action)));
|
let content = Rc::new(Content::build((window_action, item_action)));
|
||||||
|
|
||||||
let search = Rc::new(Search::new());
|
let search = Rc::new(Search::new());
|
||||||
|
|
||||||
let navigation = Rc::new(Navigation::build(
|
let navigation = Rc::new(Navigation::build(
|
||||||
profile,
|
profile,
|
||||||
(window_action, tab_action, item_action),
|
(window_action, tab_action, item_action),
|
||||||
));
|
));
|
||||||
|
|
||||||
let input = Rc::new(Input::new());
|
let input = Rc::new(Input::new());
|
||||||
|
|
||||||
// Init main widget
|
// Init main widget
|
||||||
@ -61,9 +63,29 @@ impl Page {
|
|||||||
g_box.append(&search.g_box);
|
g_box.append(&search.g_box);
|
||||||
g_box.append(&input.clamp);
|
g_box.append(&input.clamp);
|
||||||
|
|
||||||
|
// Generate `TabPage` by append widget into given `TabView`
|
||||||
|
let tab_page = match position {
|
||||||
|
Position::After => match tab_view.selected_page() {
|
||||||
|
Some(page) => add(tab_view, &g_box, tab_view.page_position(&page) + 1),
|
||||||
|
None => tab_view.append(&g_box),
|
||||||
|
},
|
||||||
|
Position::End => tab_view.append(&g_box),
|
||||||
|
Position::Number(value) => add(tab_view, &g_box, value),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Setup
|
||||||
|
tab_page.set_needs_attention(is_needs_attention);
|
||||||
|
tab_page.set_title("New page");
|
||||||
|
|
||||||
|
tab_view.set_page_pinned(&tab_page, is_pinned);
|
||||||
|
if is_selected {
|
||||||
|
tab_view.set_selected_page(&tab_page);
|
||||||
|
}
|
||||||
|
|
||||||
// Done
|
// Done
|
||||||
Self {
|
Self {
|
||||||
profile: profile.clone(),
|
profile: profile.clone(),
|
||||||
|
tab_page: tab_page.clone(),
|
||||||
// Actions
|
// Actions
|
||||||
browser_action: browser_action.clone(),
|
browser_action: browser_action.clone(),
|
||||||
item_action: item_action.clone(),
|
item_action: item_action.clone(),
|
||||||
@ -73,8 +95,6 @@ impl Page {
|
|||||||
search,
|
search,
|
||||||
input,
|
input,
|
||||||
navigation,
|
navigation,
|
||||||
// Widget
|
|
||||||
g_box,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,6 +157,10 @@ impl Page {
|
|||||||
match database::select(transaction, app_browser_window_tab_item_id) {
|
match database::select(transaction, app_browser_window_tab_item_id) {
|
||||||
Ok(records) => {
|
Ok(records) => {
|
||||||
for record in records {
|
for record in records {
|
||||||
|
// Restore main widget
|
||||||
|
if let Some(title) = record.title {
|
||||||
|
self.set_title(title.as_str());
|
||||||
|
}
|
||||||
// Restore self by last record
|
// Restore self by last record
|
||||||
// Delegate restore action to the item childs
|
// Delegate restore action to the item childs
|
||||||
self.navigation.restore(transaction, &record.id)?;
|
self.navigation.restore(transaction, &record.id)?;
|
||||||
@ -158,7 +182,17 @@ impl Page {
|
|||||||
transaction: &Transaction,
|
transaction: &Transaction,
|
||||||
app_browser_window_tab_item_id: i64,
|
app_browser_window_tab_item_id: i64,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
match database::insert(transaction, app_browser_window_tab_item_id) {
|
// Keep value in memory until operation complete
|
||||||
|
let title = self.tab_page.title();
|
||||||
|
|
||||||
|
match database::insert(
|
||||||
|
transaction,
|
||||||
|
app_browser_window_tab_item_id,
|
||||||
|
match title.is_empty() {
|
||||||
|
true => None,
|
||||||
|
false => Some(title.as_str()),
|
||||||
|
},
|
||||||
|
) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let id = database::last_insert_id(transaction);
|
let id = database::last_insert_id(transaction);
|
||||||
|
|
||||||
@ -170,6 +204,19 @@ impl Page {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setters
|
||||||
|
|
||||||
|
/// Set title for `Self`
|
||||||
|
/// * this method allows to keep `tab_page` isolated from driver implementation
|
||||||
|
pub fn set_title(&self, title: &str) {
|
||||||
|
self.tab_page.set_title(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_progress(&self, progress_fraction: f64) {
|
||||||
|
self.navigation.set_progress_fraction(progress_fraction);
|
||||||
|
self.tab_page.set_loading(progress_fraction > 0.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tools
|
// Tools
|
||||||
@ -186,3 +233,17 @@ pub fn migrate(tx: &Transaction) -> Result<(), String> {
|
|||||||
// Success
|
// Success
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create new [TabPage](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.TabPage.html)
|
||||||
|
/// in [TabView](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.TabView.html) at given position
|
||||||
|
///
|
||||||
|
/// * if given `position` match pinned tab, GTK will panic with notice:
|
||||||
|
/// adw_tab_view_insert: assertion 'position >= self->n_pinned_pages'\
|
||||||
|
/// as the solution, prepend new page after pinned tabs in this case
|
||||||
|
fn add(tab_view: &TabView, child: &impl IsA<gtk::Widget>, position: i32) -> TabPage {
|
||||||
|
if position > tab_view.n_pinned_pages() {
|
||||||
|
tab_view.insert(child, position)
|
||||||
|
} else {
|
||||||
|
tab_view.prepend(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,6 +3,7 @@ use sqlite::{Error, Transaction};
|
|||||||
pub struct Table {
|
pub struct Table {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
// pub app_browser_window_tab_item_id: i64, not in use,
|
// pub app_browser_window_tab_item_id: i64, not in use,
|
||||||
|
pub title: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(tx: &Transaction) -> Result<usize, Error> {
|
pub fn init(tx: &Transaction) -> Result<usize, Error> {
|
||||||
@ -11,6 +12,7 @@ pub fn init(tx: &Transaction) -> Result<usize, Error> {
|
|||||||
(
|
(
|
||||||
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
`app_browser_window_tab_item_id` INTEGER NOT NULL,
|
`app_browser_window_tab_item_id` INTEGER NOT NULL,
|
||||||
|
`title` VARCHAR(1024),
|
||||||
|
|
||||||
FOREIGN KEY (`app_browser_window_tab_item_id`) REFERENCES `app_browser_window_tab_item`(`id`)
|
FOREIGN KEY (`app_browser_window_tab_item_id`) REFERENCES `app_browser_window_tab_item`(`id`)
|
||||||
)",
|
)",
|
||||||
@ -18,19 +20,25 @@ pub fn init(tx: &Transaction) -> Result<usize, Error> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(tx: &Transaction, app_browser_window_tab_item_id: i64) -> Result<usize, Error> {
|
pub fn insert(
|
||||||
|
tx: &Transaction,
|
||||||
|
app_browser_window_tab_item_id: i64,
|
||||||
|
title: Option<&str>,
|
||||||
|
) -> Result<usize, Error> {
|
||||||
tx.execute(
|
tx.execute(
|
||||||
"INSERT INTO `app_browser_window_tab_item_page` (
|
"INSERT INTO `app_browser_window_tab_item_page` (
|
||||||
`app_browser_window_tab_item_id`
|
`app_browser_window_tab_item_id`,
|
||||||
) VALUES (?)",
|
`title`
|
||||||
[app_browser_window_tab_item_id],
|
) VALUES (?, ?)",
|
||||||
|
(app_browser_window_tab_item_id, title),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select(tx: &Transaction, app_browser_window_tab_item_id: i64) -> Result<Vec<Table>, Error> {
|
pub fn select(tx: &Transaction, app_browser_window_tab_item_id: i64) -> Result<Vec<Table>, Error> {
|
||||||
let mut stmt = tx.prepare(
|
let mut stmt = tx.prepare(
|
||||||
"SELECT `id`,
|
"SELECT `id`,
|
||||||
`app_browser_window_tab_item_id`
|
`app_browser_window_tab_item_id`,
|
||||||
|
`title`
|
||||||
FROM `app_browser_window_tab_item_page`
|
FROM `app_browser_window_tab_item_page`
|
||||||
WHERE `app_browser_window_tab_item_id` = ?",
|
WHERE `app_browser_window_tab_item_id` = ?",
|
||||||
)?;
|
)?;
|
||||||
@ -39,6 +47,7 @@ pub fn select(tx: &Transaction, app_browser_window_tab_item_id: i64) -> Result<V
|
|||||||
Ok(Table {
|
Ok(Table {
|
||||||
id: row.get(0)?,
|
id: row.get(0)?,
|
||||||
// app_browser_window_tab_item_id: row.get(1)?, not in use
|
// app_browser_window_tab_item_id: row.get(1)?, not in use
|
||||||
|
title: row.get(2)?,
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -1,159 +0,0 @@
|
|||||||
mod database;
|
|
||||||
|
|
||||||
use crate::app::browser::window::action::Position;
|
|
||||||
use adw::{TabPage, TabView};
|
|
||||||
use gtk::prelude::IsA;
|
|
||||||
use sqlite::Transaction;
|
|
||||||
|
|
||||||
const DEFAULT_TITLE: &str = "New page";
|
|
||||||
|
|
||||||
pub struct Widget {
|
|
||||||
pub tab_page: TabPage,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget {
|
|
||||||
// Constructors
|
|
||||||
|
|
||||||
/// Build new `Self`
|
|
||||||
pub fn build(
|
|
||||||
tab_view: &TabView,
|
|
||||||
child: &impl IsA<gtk::Widget>,
|
|
||||||
title: Option<&str>,
|
|
||||||
position: Position,
|
|
||||||
state: (bool, bool, bool),
|
|
||||||
) -> Self {
|
|
||||||
// Define state variables
|
|
||||||
let (is_pinned, is_selected, is_attention) = state;
|
|
||||||
|
|
||||||
// Create new `TabPage` for given `TabView`
|
|
||||||
let tab_page = match position {
|
|
||||||
Position::After => match tab_view.selected_page() {
|
|
||||||
Some(page) => add(tab_view, child, tab_view.page_position(&page) + 1),
|
|
||||||
None => tab_view.append(child),
|
|
||||||
},
|
|
||||||
Position::End => tab_view.append(child),
|
|
||||||
Position::Number(value) => add(tab_view, child, value),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Setup
|
|
||||||
tab_page.set_needs_attention(is_attention);
|
|
||||||
tab_page.set_title(match title {
|
|
||||||
Some(value) => value,
|
|
||||||
None => DEFAULT_TITLE,
|
|
||||||
});
|
|
||||||
|
|
||||||
tab_view.set_page_pinned(&tab_page, is_pinned);
|
|
||||||
|
|
||||||
if is_selected {
|
|
||||||
tab_view.set_selected_page(&tab_page);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done
|
|
||||||
Self { tab_page }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Actions
|
|
||||||
|
|
||||||
pub fn clean(
|
|
||||||
&self,
|
|
||||||
transaction: &Transaction,
|
|
||||||
app_browser_window_tab_item_id: i64,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
match database::select(transaction, app_browser_window_tab_item_id) {
|
|
||||||
Ok(records) => {
|
|
||||||
for record in records {
|
|
||||||
match database::delete(transaction, record.id) {
|
|
||||||
Ok(_) => {
|
|
||||||
// Delegate clean action to the item childs
|
|
||||||
// nothing yet..
|
|
||||||
}
|
|
||||||
Err(e) => return Err(e.to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => return Err(e.to_string()),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn restore(
|
|
||||||
&self,
|
|
||||||
transaction: &Transaction,
|
|
||||||
app_browser_window_tab_item_id: i64,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
match database::select(transaction, app_browser_window_tab_item_id) {
|
|
||||||
Ok(records) => {
|
|
||||||
for record in records {
|
|
||||||
// Record value can be stored as NULL
|
|
||||||
if let Some(title) = record.title {
|
|
||||||
self.tab_page.set_title(title.as_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delegate restore action to the item childs
|
|
||||||
// nothing yet..
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => return Err(e.to_string()),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save(
|
|
||||||
&self,
|
|
||||||
transaction: &Transaction,
|
|
||||||
app_browser_window_tab_item_id: i64,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
// Keep value in memory until operation complete
|
|
||||||
let title = self.tab_page.title();
|
|
||||||
|
|
||||||
match database::insert(
|
|
||||||
transaction,
|
|
||||||
app_browser_window_tab_item_id,
|
|
||||||
match title.is_empty() {
|
|
||||||
true => None,
|
|
||||||
false => Some(title.as_str()),
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Ok(_) => {
|
|
||||||
// let id = database::last_insert_id(transaction);
|
|
||||||
|
|
||||||
// Delegate save action to childs
|
|
||||||
// nothing yet..
|
|
||||||
}
|
|
||||||
Err(e) => return Err(e.to_string()),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tools
|
|
||||||
|
|
||||||
pub fn migrate(tx: &Transaction) -> Result<(), String> {
|
|
||||||
// Migrate self components
|
|
||||||
if let Err(e) = database::init(tx) {
|
|
||||||
return Err(e.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delegate migration to childs
|
|
||||||
// nothing yet..
|
|
||||||
|
|
||||||
// Success
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create new [TabPage](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.TabPage.html)
|
|
||||||
/// in [TabView](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.TabView.html) at given position
|
|
||||||
///
|
|
||||||
/// * if given `position` match pinned tab, GTK will panic with notice:
|
|
||||||
/// adw_tab_view_insert: assertion 'position >= self->n_pinned_pages'\
|
|
||||||
/// as the solution, prepend new page after pinned tabs in this case
|
|
||||||
fn add(tab_view: &TabView, child: &impl IsA<gtk::Widget>, position: i32) -> TabPage {
|
|
||||||
if position > tab_view.n_pinned_pages() {
|
|
||||||
tab_view.insert(child, position)
|
|
||||||
} else {
|
|
||||||
tab_view.prepend(child)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
use sqlite::{Error, Transaction};
|
|
||||||
|
|
||||||
pub struct Table {
|
|
||||||
pub id: i64,
|
|
||||||
// pub app_browser_window_tab_item_id: i64, not in use
|
|
||||||
pub title: Option<String>, // can be stored as NULL
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(tx: &Transaction) -> Result<usize, Error> {
|
|
||||||
tx.execute(
|
|
||||||
"CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item_widget`
|
|
||||||
(
|
|
||||||
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
||||||
`app_browser_window_tab_item_id` INTEGER NOT NULL,
|
|
||||||
`title` VARCHAR(1024),
|
|
||||||
|
|
||||||
FOREIGN KEY (`app_browser_window_tab_item_id`) REFERENCES `app_browser_window_tab_item`(`id`)
|
|
||||||
)",
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert(
|
|
||||||
tx: &Transaction,
|
|
||||||
app_browser_window_tab_item_id: i64,
|
|
||||||
title: Option<&str>,
|
|
||||||
) -> Result<usize, Error> {
|
|
||||||
tx.execute(
|
|
||||||
"INSERT INTO `app_browser_window_tab_item_widget` (
|
|
||||||
`app_browser_window_tab_item_id`,
|
|
||||||
`title`
|
|
||||||
) VALUES (?, ?)",
|
|
||||||
(app_browser_window_tab_item_id, title),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn select(tx: &Transaction, app_browser_window_tab_item_id: i64) -> Result<Vec<Table>, Error> {
|
|
||||||
let mut stmt = tx.prepare(
|
|
||||||
"SELECT `id`,
|
|
||||||
`app_browser_window_tab_item_id`,
|
|
||||||
`title`
|
|
||||||
FROM `app_browser_window_tab_item_widget`
|
|
||||||
WHERE `app_browser_window_tab_item_id` = ?",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let result = stmt.query_map([app_browser_window_tab_item_id], |row| {
|
|
||||||
Ok(Table {
|
|
||||||
id: row.get(0)?,
|
|
||||||
// app_browser_window_tab_item_id: row.get(1)?, not in use
|
|
||||||
title: row.get(2)?,
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut records = Vec::new();
|
|
||||||
|
|
||||||
for record in result {
|
|
||||||
let table = record?;
|
|
||||||
records.push(table);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(records)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> {
|
|
||||||
tx.execute(
|
|
||||||
"DELETE FROM `app_browser_window_tab_item_widget` WHERE `id` = ?",
|
|
||||||
[id],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* not in use
|
|
||||||
pub fn last_insert_id(tx: &Transaction) -> i64 {
|
|
||||||
tx.last_insert_rowid()
|
|
||||||
} */
|
|
Loading…
x
Reference in New Issue
Block a user