mirror of https://github.com/YGGverse/Yoda.git
yggverse
8 months ago
19 changed files with 664 additions and 884 deletions
@ -1,56 +1,99 @@
@@ -1,56 +1,99 @@
|
||||
{ |
||||
"homepage":"yoda://welcome", |
||||
"interface": |
||||
"window": |
||||
{ |
||||
"title":"Yoda", |
||||
"theme":"Default", |
||||
"window": |
||||
"header": |
||||
{ |
||||
"header": |
||||
"enabled":true, |
||||
"button": |
||||
{ |
||||
"enabled":true, |
||||
"button": |
||||
{ |
||||
"close":true |
||||
} |
||||
}, |
||||
"navigation": |
||||
{ |
||||
"button": |
||||
{ |
||||
"home":true, |
||||
"back":true, |
||||
"forward":true, |
||||
"reload":true, |
||||
"go":true |
||||
} |
||||
}, |
||||
"width":640, |
||||
"height":480 |
||||
} |
||||
}, |
||||
"resolver": |
||||
{ |
||||
"enabled":true, |
||||
"request": |
||||
{ |
||||
"timeout":1, |
||||
"host": |
||||
[ |
||||
"1.1.1.1", |
||||
"8.8.8.8" |
||||
], |
||||
"record": |
||||
[ |
||||
"A", |
||||
"AAAA" |
||||
] |
||||
"close":true |
||||
} |
||||
}, |
||||
"result": |
||||
"width":640, |
||||
"height":480, |
||||
"tab": |
||||
{ |
||||
"shuffle":false, |
||||
"cache": |
||||
"page": |
||||
{ |
||||
"timeout":3600 |
||||
"resolver": |
||||
{ |
||||
"enabled":true, |
||||
"request": |
||||
{ |
||||
"timeout":1, |
||||
"host": |
||||
[ |
||||
"1.1.1.1", |
||||
"8.8.8.8" |
||||
], |
||||
"record": |
||||
[ |
||||
"A", |
||||
"AAAA" |
||||
] |
||||
}, |
||||
"result": |
||||
{ |
||||
"shuffle":false, |
||||
"cache": |
||||
{ |
||||
"timeout":3600 |
||||
} |
||||
} |
||||
}, |
||||
"header": |
||||
{ |
||||
"margin":8, |
||||
"button": |
||||
{ |
||||
"home": |
||||
{ |
||||
"visible":true, |
||||
"label":"Home", |
||||
"url":"yoda://welcome" |
||||
}, |
||||
"back": |
||||
{ |
||||
"visible":true, |
||||
"label":"Back" |
||||
}, |
||||
"forward": |
||||
{ |
||||
"visible":true, |
||||
"label":"Forward" |
||||
}, |
||||
"go": |
||||
{ |
||||
"visible":true, |
||||
"label":"Go" |
||||
} |
||||
}, |
||||
"address": |
||||
{ |
||||
"placeholder":"URL or any search term...", |
||||
"length": |
||||
{ |
||||
"max":1024 |
||||
} |
||||
} |
||||
}, |
||||
"body": |
||||
{ |
||||
"margin":8 |
||||
}, |
||||
"footer": |
||||
{ |
||||
"margin":8, |
||||
"status": |
||||
{ |
||||
"open": |
||||
{ |
||||
"complete":"{TIME_C} | {RESPONSE_META} | {RESPONSE_LENGTH} bytes | {RESPONSE_SECONDS} seconds" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
@ -1,70 +0,0 @@
@@ -1,70 +0,0 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Abstract; |
||||
|
||||
abstract class Window |
||||
{ |
||||
public \GtkWindow $window; |
||||
public \GtkCssProvider $css; |
||||
public \GtkStyleContext $style; |
||||
|
||||
public object $config; |
||||
|
||||
public function __construct( |
||||
object $config |
||||
) { |
||||
$this->config = $config; |
||||
|
||||
$this->window = new \GtkWindow(); |
||||
|
||||
$this->window->set_size_request( |
||||
$this->config->interface->window->width, |
||||
$this->config->interface->window->height |
||||
); |
||||
|
||||
if ($this->config->interface->window->header->enabled) |
||||
{ |
||||
$header = new \GtkHeaderBar(); |
||||
|
||||
$header->set_show_close_button( |
||||
$this->config->interface->window->header->button->close |
||||
); |
||||
|
||||
$this->window->set_titlebar( |
||||
$header |
||||
); |
||||
} |
||||
|
||||
$this->window->set_title( |
||||
'Yoda' |
||||
); |
||||
|
||||
$this->window->connect( |
||||
'destroy', |
||||
function() |
||||
{ |
||||
\Gtk::main_quit(); |
||||
} |
||||
); |
||||
} |
||||
|
||||
public function setTheme( |
||||
string $css |
||||
): void |
||||
{ |
||||
$this->css = new \GtkCssProvider(); |
||||
|
||||
$this->css->load_from_data( |
||||
$css |
||||
); |
||||
|
||||
$this->style = new \GtkStyleContext(); |
||||
|
||||
$this->style->add_provider_for_screen( |
||||
$this->css, |
||||
600 |
||||
); |
||||
} |
||||
} |
@ -1,273 +0,0 @@
@@ -1,273 +0,0 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Controller; |
||||
|
||||
class Browser extends \Yggverse\Yoda\Abstract\Window |
||||
{ |
||||
public \Yggverse\Yoda\Entity\Box\Tab $tab; |
||||
|
||||
public \Yggverse\Yoda\Model\Memory $memory; |
||||
|
||||
public function __construct() |
||||
{ |
||||
parent::__construct( |
||||
\Yggverse\Yoda\Model\File::getConfig() |
||||
); |
||||
|
||||
$this->memory = new \Yggverse\Yoda\Model\Memory(); |
||||
|
||||
$this->tab = new \Yggverse\Yoda\Entity\Box\Tab(); |
||||
|
||||
$this->tab->navigation->address->entry->connect( |
||||
'activate', |
||||
function ($entry) |
||||
{ |
||||
$this->navigate( |
||||
$entry->get_text() |
||||
); |
||||
} |
||||
); |
||||
|
||||
if ($this->config->interface->window->navigation->button->go) |
||||
{ |
||||
$this->tab->navigation->go->button->connect( |
||||
'released', |
||||
function ($entry) |
||||
{ |
||||
$this->navigate( |
||||
$this->tab->navigation->address->entry->get_text() |
||||
); |
||||
} |
||||
); |
||||
} |
||||
|
||||
if ($this->config->interface->window->navigation->button->reload) |
||||
{ |
||||
$this->tab->navigation->reload->button->connect( |
||||
'released', |
||||
function ($entry) |
||||
{ |
||||
$this->navigate( |
||||
$this->tab->navigation->address->entry->get_text() |
||||
); |
||||
} |
||||
); |
||||
} |
||||
|
||||
if ($this->config->interface->window->navigation->button->home && $this->config->homepage) |
||||
{ |
||||
$this->tab->navigation->home->button->connect( |
||||
'released', |
||||
function ($entry) |
||||
{ |
||||
$this->tab->navigation->address->entry->set_text( |
||||
$this->config->homepage |
||||
); |
||||
|
||||
$this->navigate( |
||||
$this->config->homepage |
||||
); |
||||
} |
||||
); |
||||
|
||||
$this->tab->navigation->home->button->set_sensitive( |
||||
!($this->tab->navigation->address->entry->get_text() == $this->config->homepage) |
||||
); |
||||
} |
||||
|
||||
$this->navigate( |
||||
$this->tab->navigation->address->entry->get_text() |
||||
); |
||||
|
||||
$this->window->add( |
||||
$this->tab->box |
||||
); |
||||
|
||||
$this->window->show_all(); |
||||
} |
||||
|
||||
public function navigate(string $url): void |
||||
{ |
||||
switch (true) |
||||
{ |
||||
case str_starts_with($url, 'gemini://'): |
||||
|
||||
$this->_gemini( |
||||
$url |
||||
); |
||||
|
||||
break; |
||||
|
||||
case str_starts_with($url, 'yoda://'): |
||||
|
||||
$this->_yoda( |
||||
$url |
||||
); |
||||
|
||||
break; |
||||
|
||||
default: |
||||
|
||||
$this->_yoda( |
||||
'yoda://oops' |
||||
); |
||||
} |
||||
} |
||||
|
||||
private function _yoda(string $url): void |
||||
{ |
||||
if ($data = \Yggverse\Yoda\Model\Page::get(str_replace('yoda://', '', $url))) |
||||
{ |
||||
$response = new \Yggverse\Gemini\Client\Response( |
||||
$data |
||||
); |
||||
|
||||
$this->tab->content->label->set_text( |
||||
$data |
||||
); |
||||
|
||||
$body = new \Yggverse\Gemini\Gemtext\Body( |
||||
$data |
||||
); |
||||
|
||||
if ($h1 = $body->getH1()) |
||||
{ |
||||
$this->window->set_title( |
||||
$h1[0] |
||||
); |
||||
} |
||||
} |
||||
|
||||
else |
||||
{ |
||||
$data = \Yggverse\Yoda\Model\Page::get('Oops'); |
||||
|
||||
$this->tab->content->label->set_text( |
||||
$data |
||||
); |
||||
|
||||
$body = new \Yggverse\Gemini\Gemtext\Body( |
||||
$data |
||||
); |
||||
|
||||
if ($h1 = $body->getH1()) |
||||
{ |
||||
$this->window->set_title( |
||||
$h1[0] |
||||
); |
||||
} |
||||
} |
||||
|
||||
if ($this->config->interface->window->navigation->button->home && $this->config->homepage) |
||||
{ |
||||
$this->tab->navigation->home->button->set_sensitive( |
||||
!($url == $this->config->homepage) |
||||
); |
||||
} |
||||
} |
||||
|
||||
private function _gemini(string $url): void |
||||
{ |
||||
$this->tab->tray->label->set_text( |
||||
sprintf( |
||||
'Open %s...', |
||||
urldecode( |
||||
$url |
||||
) |
||||
) |
||||
); |
||||
|
||||
$start = microtime(true); |
||||
|
||||
$host = null; |
||||
|
||||
if ($this->config->resolver->enabled) |
||||
{ |
||||
$address = new \Yggverse\Net\Address( |
||||
$url |
||||
); |
||||
|
||||
$name = $address->getHost(); |
||||
|
||||
if (!$host = $this->memory->get($name)) |
||||
{ |
||||
$resolve = new \Yggverse\Net\Resolve( |
||||
$this->config->resolver->request->record, |
||||
$this->config->resolver->request->host, |
||||
$this->config->resolver->request->timeout, |
||||
$this->config->resolver->result->shuffle |
||||
); |
||||
|
||||
$resolved = $resolve->address( |
||||
$address |
||||
); |
||||
|
||||
if ($resolved) |
||||
{ |
||||
$host = $resolved->getHost(); |
||||
|
||||
$this->memory->set( |
||||
$name, |
||||
$host |
||||
); |
||||
} |
||||
} |
||||
} |
||||
|
||||
$request = new \Yggverse\Gemini\Client\Request( |
||||
$url, |
||||
$host |
||||
); |
||||
|
||||
$raw = $request->getResponse(); |
||||
|
||||
$end = microtime(true); |
||||
|
||||
$response = new \Yggverse\Gemini\Client\Response( |
||||
$raw |
||||
); |
||||
|
||||
$this->tab->content->label->set_text( |
||||
$response->getBody() |
||||
); |
||||
|
||||
$body = new \Yggverse\Gemini\Gemtext\Body( |
||||
$response->getBody() |
||||
); |
||||
|
||||
if ($h1 = $body->getH1()) |
||||
{ |
||||
$this->window->set_title( |
||||
sprintf( |
||||
'%s - Yoda', |
||||
empty($h1[0]) ? $address->getHost() : $h1[0] |
||||
) |
||||
); |
||||
} |
||||
|
||||
$this->tab->tray->label->set_text( |
||||
sprintf( |
||||
'%s | %s | %d bytes | %s seconds', |
||||
date('c'), |
||||
$response->getMeta() ? $response->getMeta() : $response->getCode(), |
||||
number_format( |
||||
mb_strlen( |
||||
$raw |
||||
) |
||||
), |
||||
round( |
||||
$end - $start, 2 |
||||
) |
||||
) |
||||
); |
||||
|
||||
if ($this->config->interface->window->navigation->button->home && $this->config->homepage) |
||||
{ |
||||
$this->tab->navigation->home->button->set_sensitive( |
||||
!($url == $this->config->homepage) |
||||
); |
||||
} |
||||
} |
||||
} |
@ -1,30 +0,0 @@
@@ -1,30 +0,0 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Entity\Box; |
||||
|
||||
class Menu |
||||
{ |
||||
public \GtkBox $box; |
||||
|
||||
public \Yggverse\Yoda\Entity\Menu\Bar\Main $main; |
||||
|
||||
public function __construct( |
||||
string $name = 'boxMenu' |
||||
) { |
||||
$this->box = new \GtkBox( |
||||
\GtkOrientation::VERTICAL |
||||
); |
||||
|
||||
$this->box->set_name( |
||||
$name |
||||
); |
||||
|
||||
$this->main = new \Yggverse\Yoda\Entity\Menu\Bar\Main(); |
||||
|
||||
$this->box->add( |
||||
$this->main->bar |
||||
); |
||||
} |
||||
} |
@ -1,123 +0,0 @@
@@ -1,123 +0,0 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Entity\Box; |
||||
|
||||
class Navigation |
||||
{ |
||||
public \GtkBox $box; |
||||
|
||||
public \Yggverse\Yoda\Entity\Entry\Address $address; |
||||
|
||||
public \Yggverse\Yoda\Entity\Button\Home $home; |
||||
public \Yggverse\Yoda\Entity\Button\Back $back; |
||||
public \Yggverse\Yoda\Entity\Button\Forward $forward; |
||||
public \Yggverse\Yoda\Entity\Button\Reload $reload; |
||||
public \Yggverse\Yoda\Entity\Button\Go $go; |
||||
|
||||
public object $config; |
||||
|
||||
public function __construct( |
||||
string $name = 'boxNavigation' |
||||
) { |
||||
$this->config = \Yggverse\Yoda\Model\File::getConfig(); |
||||
|
||||
$this->box = new \GtkBox( |
||||
\GtkOrientation::HORIZONTAL |
||||
); |
||||
|
||||
$this->box->set_name( |
||||
$name |
||||
); |
||||
|
||||
if ($this->config->interface->window->navigation->button->home && $this->config->homepage) |
||||
{ |
||||
$this->home = new \Yggverse\Yoda\Entity\Button\Home(); |
||||
|
||||
$this->box->pack_start( |
||||
$this->home->button, |
||||
false, |
||||
false, |
||||
8 |
||||
); |
||||
} |
||||
|
||||
if ($this->config->interface->window->navigation->button->back || $this->config->interface->window->navigation->button->forward) |
||||
{ |
||||
$boxBackForward = new \GtkButtonBox( |
||||
\GtkOrientation::HORIZONTAL |
||||
); |
||||
|
||||
$boxBackForward->set_layout( |
||||
\GtkButtonBoxStyle::EXPAND |
||||
); |
||||
|
||||
if ($this->config->interface->window->navigation->button->back) |
||||
{ |
||||
$this->back = new \Yggverse\Yoda\Entity\Button\Back(); |
||||
|
||||
$boxBackForward->pack_start( |
||||
$this->back->button, |
||||
false, |
||||
true, |
||||
0 |
||||
); |
||||
} |
||||
|
||||
if ($this->config->interface->window->navigation->button->forward) |
||||
{ |
||||
$this->forward = new \Yggverse\Yoda\Entity\Button\Forward(); |
||||
|
||||
$boxBackForward->pack_end( |
||||
$this->forward->button, |
||||
false, |
||||
true, |
||||
0 |
||||
); |
||||
} |
||||
|
||||
$this->box->pack_start( |
||||
$boxBackForward, |
||||
false, |
||||
false, |
||||
8 |
||||
); |
||||
} |
||||
|
||||
if ($this->config->interface->window->navigation->button->reload) |
||||
{ |
||||
$this->reload = new \Yggverse\Yoda\Entity\Button\Reload(); |
||||
|
||||
$this->box->pack_start( |
||||
$this->reload->button, |
||||
false, |
||||
false, |
||||
8 |
||||
); |
||||
} |
||||
|
||||
$this->address = new \Yggverse\Yoda\Entity\Entry\Address( |
||||
$this->config->homepage |
||||
); |
||||
|
||||
$this->box->pack_start( |
||||
$this->address->entry, |
||||
true, |
||||
true, |
||||
8 |
||||
); |
||||
|
||||
if ($this->config->interface->window->navigation->button->go) |
||||
{ |
||||
$this->go = new \Yggverse\Yoda\Entity\Button\Go(); |
||||
|
||||
$this->box->pack_end( |
||||
$this->go->button, |
||||
false, |
||||
false, |
||||
8 |
||||
); |
||||
} |
||||
} |
||||
} |
@ -1,71 +0,0 @@
@@ -1,71 +0,0 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Entity\Box; |
||||
|
||||
class Tab |
||||
{ |
||||
public \GtkBox $box; |
||||
|
||||
public \Yggverse\Yoda\Entity\Box\Menu $menu; |
||||
public \Yggverse\Yoda\Entity\Box\Navigation $navigation; |
||||
public \Yggverse\Yoda\Entity\Label\Content $content; |
||||
public \Yggverse\Yoda\Entity\Label\Tray $tray; |
||||
|
||||
public function __construct( |
||||
string $name = 'boxTab' |
||||
) { |
||||
// Init container |
||||
$this->box = new \GtkBox( |
||||
\GtkOrientation::VERTICAL |
||||
); |
||||
|
||||
$this->box->set_name( |
||||
$name |
||||
); |
||||
|
||||
// Init dependencies |
||||
$this->menu = new \Yggverse\Yoda\Entity\Box\Menu(); |
||||
|
||||
$this->box->pack_start( |
||||
$this->menu->box, |
||||
false, |
||||
true, |
||||
0 |
||||
); |
||||
|
||||
$this->navigation = new \Yggverse\Yoda\Entity\Box\Navigation(); |
||||
|
||||
$this->box->pack_start( |
||||
$this->navigation->box, |
||||
false, |
||||
true, |
||||
8 |
||||
); |
||||
|
||||
$this->content = new \Yggverse\Yoda\Entity\Label\Content(); |
||||
|
||||
$scroll = new \GtkScrolledWindow(); |
||||
|
||||
$scroll->add( |
||||
$this->content->label |
||||
); |
||||
|
||||
$this->box->pack_start( |
||||
$scroll, |
||||
true, |
||||
true, |
||||
0 |
||||
); |
||||
|
||||
$this->tray = new \Yggverse\Yoda\Entity\Label\Tray(); |
||||
|
||||
$this->box->pack_start( |
||||
$this->tray->label, |
||||
false, |
||||
true, |
||||
0 |
||||
); |
||||
} |
||||
} |
@ -1,18 +0,0 @@
@@ -1,18 +0,0 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Entity\Button; |
||||
|
||||
class Back |
||||
{ |
||||
public \GtkButton $button; |
||||
|
||||
public function __construct( |
||||
?string $label = 'Back' |
||||
) { |
||||
$this->button = \GtkButton::new_with_label( |
||||
$label |
||||
); |
||||
} |
||||
} |
@ -1,18 +0,0 @@
@@ -1,18 +0,0 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Entity\Button; |
||||
|
||||
class Forward |
||||
{ |
||||
public \GtkButton $button; |
||||
|
||||
public function __construct( |
||||
?string $label = 'Forward' |
||||
) { |
||||
$this->button = \GtkButton::new_with_label( |
||||
$label |
||||
); |
||||
} |
||||
} |
@ -1,18 +0,0 @@
@@ -1,18 +0,0 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Entity\Button; |
||||
|
||||
class Go |
||||
{ |
||||
public \GtkButton $button; |
||||
|
||||
public function __construct( |
||||
?string $label = 'Go' |
||||
) { |
||||
$this->button = \GtkButton::new_with_label( |
||||
$label |
||||
); |
||||
} |
||||
} |
@ -1,18 +0,0 @@
@@ -1,18 +0,0 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Entity\Button; |
||||
|
||||
class Home |
||||
{ |
||||
public \GtkButton $button; |
||||
|
||||
public function __construct( |
||||
?string $label = 'Home' |
||||
) { |
||||
$this->button = \GtkButton::new_with_label( |
||||
$label |
||||
); |
||||
} |
||||
} |
@ -1,18 +0,0 @@
@@ -1,18 +0,0 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Entity\Button; |
||||
|
||||
class Reload |
||||
{ |
||||
public \GtkButton $button; |
||||
|
||||
public function __construct( |
||||
?string $label = 'Reload' |
||||
) { |
||||
$this->button = \GtkButton::new_with_label( |
||||
$label |
||||
); |
||||
} |
||||
} |
@ -1,29 +0,0 @@
@@ -1,29 +0,0 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Entity\Entry; |
||||
|
||||
class Address |
||||
{ |
||||
public \GtkEntry $entry; |
||||
|
||||
public function __construct( |
||||
?string $text = null, |
||||
?string $placeholder = 'URL or any search term...' |
||||
) { |
||||
$this->entry = new \GtkEntry(); |
||||
|
||||
$this->entry->set_text( |
||||
$text |
||||
); |
||||
|
||||
$this->entry->set_placeholder_text( |
||||
$placeholder |
||||
); |
||||
|
||||
$this->entry->set_max_length( |
||||
1024 |
||||
); |
||||
} |
||||
} |
@ -1,37 +0,0 @@
@@ -1,37 +0,0 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Entity\Label; |
||||
|
||||
class Content |
||||
{ |
||||
public \GtkLabel $label; |
||||
|
||||
public function __construct(string $value = '') |
||||
{ |
||||
$this->label = new \GtkLabel( |
||||
$value |
||||
); |
||||
|
||||
$this->label->set_use_markup( |
||||
true |
||||
); |
||||
|
||||
$this->label->set_selectable( |
||||
true |
||||
); |
||||
|
||||
$this->label->set_line_wrap( |
||||
true |
||||
); |
||||
|
||||
$this->label->set_xalign( |
||||
0 |
||||
); |
||||
|
||||
$this->label->set_yalign( |
||||
0 |
||||
); |
||||
} |
||||
} |
@ -1,33 +0,0 @@
@@ -1,33 +0,0 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Entity\Label; |
||||
|
||||
class Tray |
||||
{ |
||||
public \GtkLabel $label; |
||||
|
||||
public function __construct(string $value = '') |
||||
{ |
||||
$this->label = new \GtkLabel( |
||||
$value |
||||
); |
||||
|
||||
$this->label->set_use_markup( |
||||
true |
||||
); |
||||
|
||||
$this->label->set_selectable( |
||||
false |
||||
); |
||||
|
||||
$this->label->set_xalign( |
||||
0 |
||||
); |
||||
|
||||
$this->label->set_yalign( |
||||
0 |
||||
); |
||||
} |
||||
} |
@ -1,23 +0,0 @@
@@ -1,23 +0,0 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Entity\Menu\Bar; |
||||
|
||||
class Main |
||||
{ |
||||
public \GtkMenuBar $bar; |
||||
|
||||
public \Yggverse\Yoda\Entity\Menu\Item\Yoda $yoda; |
||||
|
||||
public function __construct() |
||||
{ |
||||
$this->bar = new \GtkMenuBar(); |
||||
|
||||
$this->yoda = new \Yggverse\Yoda\Entity\Menu\Item\Yoda(); |
||||
|
||||
$this->bar->append( |
||||
$this->yoda->item |
||||
); |
||||
} |
||||
} |
@ -1,30 +0,0 @@
@@ -1,30 +0,0 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Entity\Menu\Item; |
||||
|
||||
class Quit |
||||
{ |
||||
public \GtkMenuItem $item; |
||||
|
||||
public function __construct(string $label = 'Quit') |
||||
{ |
||||
$this->item = \GtkMenuItem::new_with_label( |
||||
$label |
||||
); |
||||
|
||||
$this->activate(); |
||||
} |
||||
|
||||
public function activate(): void |
||||
{ |
||||
$this->item->connect( |
||||
'activate', |
||||
function () |
||||
{ |
||||
\Gtk::main_quit(); |
||||
} |
||||
); |
||||
} |
||||
} |
@ -1,29 +0,0 @@
@@ -1,29 +0,0 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Entity\Menu\Item; |
||||
|
||||
class Yoda |
||||
{ |
||||
public \GtkMenuItem $item; |
||||
|
||||
public function __construct(string $label = 'Yoda') |
||||
{ |
||||
$this->item = \GtkMenuItem::new_with_label( |
||||
$label |
||||
); |
||||
|
||||
$children = new \GtkMenu(); |
||||
|
||||
$quit = new \Yggverse\Yoda\Entity\Menu\Item\Quit(); |
||||
|
||||
$children->append( |
||||
$quit->item |
||||
); |
||||
|
||||
$this->item->set_submenu( |
||||
$children |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,525 @@
@@ -0,0 +1,525 @@
|
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Yggverse\Yoda\Tab; |
||||
|
||||
class Page |
||||
{ |
||||
public \Yggverse\Yoda\Model\Memory $memory; |
||||
|
||||
public \GtkBox $box, |
||||
$header, |
||||
$body, |
||||
$footer; |
||||
|
||||
public \GtkButton $home, |
||||
$back, |
||||
$forward, |
||||
$go; |
||||
|
||||
public \GtkEntry $address; |
||||
|
||||
public \GtkLabel $content, |
||||
$status; |
||||
|
||||
public \GtkScrolledWindow $container; |
||||
|
||||
public object $config; |
||||
|
||||
public function __construct( |
||||
?string $url = null |
||||
) { |
||||
// Init config |
||||
$this->config = \Yggverse\Yoda\Model\File::getConfig()->window->tab->page; |
||||
|
||||
// Init memory |
||||
$this->memory = new \Yggverse\Yoda\Model\Memory(); |
||||
|
||||
// Compose header |
||||
$this->header = new \GtkBox( |
||||
\GtkOrientation::HORIZONTAL |
||||
); |
||||
|
||||
$this->header->set_margin_top( |
||||
$this->config->header->margin |
||||
); |
||||
|
||||
$this->header->set_margin_bottom( |
||||
$this->config->header->margin |
||||
); |
||||
|
||||
$this->header->set_margin_start( |
||||
$this->config->header->margin |
||||
); |
||||
|
||||
$this->header->set_margin_end( |
||||
$this->config->header->margin |
||||
); |
||||
|
||||
$this->header->set_spacing( |
||||
$this->config->header->margin |
||||
); |
||||
|
||||
// Home button |
||||
$this->home = \GtkButton::new_with_label( |
||||
$this->config->header->button->home->label |
||||
); |
||||
|
||||
$this->home->set_sensitive( |
||||
!($url == $this->config->header->button->home->url) |
||||
); |
||||
|
||||
$this->home->connect( |
||||
'released', |
||||
function ($entry) |
||||
{ |
||||
$this->open( |
||||
$this->config->header->button->home->url |
||||
); |
||||
} |
||||
); |
||||
|
||||
if ($this->config->header->button->home->visible) |
||||
{ |
||||
$this->header->add( |
||||
$this->home |
||||
); |
||||
} |
||||
|
||||
// Back button |
||||
$this->back = \GtkButton::new_with_label( |
||||
$this->config->header->button->back->label |
||||
); |
||||
|
||||
// Forward button |
||||
$this->forward = \GtkButton::new_with_label( |
||||
$this->config->header->button->forward->label |
||||
); |
||||
|
||||
/// Group buttons |
||||
if ($this->config->header->button->back->visible || $this->config->header->button->forward->visible) |
||||
{ |
||||
$buttonGroup = new \GtkButtonBox( |
||||
\GtkOrientation::HORIZONTAL |
||||
); |
||||
|
||||
$buttonGroup->set_layout( |
||||
\GtkButtonBoxStyle::EXPAND |
||||
); |
||||
|
||||
if ($this->config->header->button->back->visible) |
||||
{ |
||||
$buttonGroup->add( |
||||
$this->back |
||||
); |
||||
} |
||||
|
||||
if ($this->config->header->button->forward->visible) |
||||
{ |
||||
$buttonGroup->add( |
||||
$this->forward |
||||
); |
||||
} |
||||
|
||||
$this->header->add( |
||||
$buttonGroup |
||||
); |
||||
} |
||||
|
||||
// Address field |
||||
$this->address = new \GtkEntry(); |
||||
|
||||
if ($url) |
||||
{ |
||||
$this->address->set_text( |
||||
$url |
||||
); |
||||
} |
||||
|
||||
$this->address->set_placeholder_text( |
||||
$this->config->header->address->placeholder |
||||
); |
||||
|
||||
$this->address->set_max_length( |
||||
$this->config->header->address->length->max |
||||
); |
||||
|
||||
$this->address->connect( |
||||
'activate', |
||||
function ($entry) |
||||
{ |
||||
$this->open( |
||||
$entry->get_text() |
||||
); |
||||
} |
||||
); |
||||
|
||||
$this->header->pack_start( |
||||
$this->address, |
||||
true, |
||||
true, |
||||
0 |
||||
); |
||||
|
||||
// Go button |
||||
$this->go = \GtkButton::new_with_label( |
||||
$this->config->header->button->go->label |
||||
); |
||||
|
||||
$this->go->connect( |
||||
'released', |
||||
function ($entry) |
||||
{ |
||||
$this->open( |
||||
$this->address->get_text() |
||||
); |
||||
} |
||||
); |
||||
|
||||
if ($this->config->header->button->go->visible) |
||||
{ |
||||
$this->header->add( |
||||
$this->go |
||||
); |
||||
} |
||||
|
||||
// Compose body |
||||
$this->content = new \GtkLabel(); |
||||
|
||||
$this->content->set_use_markup( |
||||
true |
||||
); |
||||
|
||||
$this->content->set_selectable( |
||||
true |
||||
); |
||||
|
||||
$this->content->set_line_wrap( |
||||
true |
||||
); |
||||
|
||||
$this->content->set_xalign( |
||||
0 |
||||
); |
||||
|
||||
$this->content->set_yalign( |
||||
0 |
||||
); |
||||
|
||||
// Init scrolled container |
||||
$this->container = new \GtkScrolledWindow(); |
||||
|
||||
$this->container->add( |
||||
$this->content |
||||
); |
||||
|
||||
$this->body = new \GtkBox( |
||||
\GtkOrientation::VERTICAL |
||||
); |
||||
|
||||
$this->body->set_margin_start( |
||||
$this->config->body->margin |
||||
); |
||||
|
||||
$this->body->pack_start( |
||||
$this->container, |
||||
true, |
||||
true, |
||||
0 |
||||
); |
||||
|
||||
// Compose footer |
||||
$this->footer = new \GtkBox( |
||||
\GtkOrientation::HORIZONTAL |
||||
); |
||||
|
||||
$this->footer->set_margin_top( |
||||
$this->config->footer->margin |
||||
); |
||||
|
||||
$this->footer->set_margin_bottom( |
||||
$this->config->footer->margin |
||||
); |
||||
|
||||
$this->footer->set_margin_start( |
||||
$this->config->footer->margin |
||||
); |
||||
|
||||
$this->footer->set_margin_end( |
||||
$this->config->footer->margin |
||||
); |
||||
|
||||
$this->footer->set_spacing( |
||||
$this->config->footer->margin |
||||
); |
||||
|
||||
$this->status = new \GtkLabel(); |
||||
|
||||
$this->footer->add( |
||||
$this->status |
||||
); |
||||
|
||||
// Compose page |
||||
$this->box = new \GtkBox( |
||||
\GtkOrientation::VERTICAL |
||||
); |
||||
|
||||
$this->box->add( |
||||
$this->header |
||||
); |
||||
|
||||
$this->box->pack_start( |
||||
$this->body, |
||||
true, |
||||
true, |
||||
0 |
||||
); |
||||
|
||||
$this->box->add( |
||||
$this->footer |
||||
); |
||||
} |
||||
|
||||
public function open( |
||||
string $url |
||||
): void |
||||
{ |
||||
// Update address field by requested |
||||
$this->address->set_text( |
||||
$url |
||||
); |
||||
|
||||
// Update home button sensitivity on match requested |
||||
$this->home->set_sensitive( |
||||
!($url == $this->config->header->button->home->url) |
||||
); |
||||
|
||||
// Open current address |
||||
switch (true) |
||||
{ |
||||
case str_starts_with($url, 'gemini://'): |
||||
|
||||
$this->_gemini( |
||||
$url |
||||
); |
||||
|
||||
break; |
||||
|
||||
default: |
||||
|
||||
$this->_yoda( |
||||
$url |
||||
); |
||||
} |
||||
} |
||||
|
||||
private function _gemini(string $url): void |
||||
{ |
||||
// Track response time |
||||
$start = microtime(true); |
||||
|
||||
// Init custom resolver |
||||
$host = null; |
||||
|
||||
if ($this->config->resolver->enabled) |
||||
{ |
||||
$address = new \Yggverse\Net\Address( |
||||
$url |
||||
); |
||||
|
||||
$name = $address->getHost(); |
||||
|
||||
if (!$host = $this->memory->get($name)) |
||||
{ |
||||
$resolve = new \Yggverse\Net\Resolve( |
||||
$this->config->resolver->request->record, |
||||
$this->config->resolver->request->host, |
||||
$this->config->resolver->request->timeout, |
||||
$this->config->resolver->result->shuffle |
||||
); |
||||
|
||||
$resolved = $resolve->address( |
||||
$address |
||||
); |
||||
|
||||
if ($resolved) |
||||
{ |
||||
$host = $resolved->getHost(); |
||||
|
||||
$this->memory->set( |
||||
$name, |
||||
$host |
||||
); |
||||
} |
||||
} |
||||
} |
||||
|
||||
$request = new \Yggverse\Gemini\Client\Request( |
||||
$url, |
||||
$host |
||||
); |
||||
|
||||
$raw = $request->getResponse(); |
||||
|
||||
$end = microtime(true); |
||||
|
||||
$response = new \Yggverse\Gemini\Client\Response( |
||||
$raw |
||||
); |
||||
|
||||
$this->content->set_markup( |
||||
$this->_gemtext( |
||||
$response->getBody() |
||||
) |
||||
); |
||||
|
||||
/* @TODO |
||||
$body = new \Yggverse\Gemini\Gemtext\Body( |
||||
$response->getBody() |
||||
); |
||||
|
||||
if ($h1 = $body->getH1()) |
||||
{ |
||||
$this->window->set_title( |
||||
sprintf( |
||||
'%s - Yoda', |
||||
empty($h1[0]) ? $address->getHost() : $h1[0] |
||||
) |
||||
); |
||||
} |
||||
*/ |
||||
|
||||
$this->status->set_text( |
||||
str_replace( // Custom macros mask from config.json |
||||
[ |
||||
'{NAVIGATION_ADDRESS}', |
||||
'{TIME_C}', |
||||
'{RESPONSE_META}', |
||||
'{RESPONSE_LENGTH}', |
||||
'{RESPONSE_SECONDS}' |
||||
], |
||||
[ |
||||
urlencode( |
||||
$url |
||||
), |
||||
date( |
||||
'c' |
||||
), |
||||
$response->getMeta() ? $response->getMeta() : $response->getCode(), |
||||
number_format( |
||||
mb_strlen( |
||||
$raw |
||||
) |
||||
), |
||||
round( |
||||
$end - $start, 2 |
||||
) |
||||
], |
||||
$this->config->footer->status->open->complete |
||||
) |
||||
); |
||||
} |
||||
|
||||
private function _yoda( |
||||
string $url |
||||
): void |
||||
{ |
||||
// Load local page |
||||
if (!$data = \Yggverse\Yoda\Model\Page::get(str_replace('yoda://', '', $url))) |
||||
{ |
||||
$data = \Yggverse\Yoda\Model\Page::get('Oops'); |
||||
} |
||||
|
||||
$this->content->set_markup( |
||||
$this->_gemtext( |
||||
$data |
||||
) |
||||
); |
||||
|
||||
// Parse gemtext |
||||
/* @TODO |
||||
$body = new \Yggverse\Gemini\Gemtext\Body( |
||||
$data |
||||
); |
||||
|
||||
if ($h1 = $body->getH1()) |
||||
{ |
||||
$this->window->set_title( |
||||
$h1[0] |
||||
); |
||||
} |
||||
*/ |
||||
} |
||||
|
||||
private function _gemtext( |
||||
string $gemtext |
||||
): string |
||||
{ |
||||
// Format body |
||||
$body = new \Yggverse\Gemini\Gemtext\Body( |
||||
$gemtext |
||||
); |
||||
|
||||
$lines = $body->getLines(); |
||||
|
||||
$escaped = []; |
||||
|
||||
/// Format H1 |
||||
foreach ($body->getH1() as $index => $h1) |
||||
{ |
||||
$lines[$index] = sprintf( |
||||
'<span size="xx-large">%s</span>', |
||||
htmlentities( |
||||
$h1 |
||||
) |
||||
); |
||||
|
||||
$escaped[] = $index; |
||||
} |
||||
|
||||
/// Format H2 |
||||
foreach ($body->getH2() as $index => $h2) |
||||
{ |
||||
$lines[$index] = sprintf( |
||||
'<span size="x-large">%s</span>', |
||||
htmlentities( |
||||
$h2 |
||||
) |
||||
); |
||||
|
||||
$escaped[] = $index; |
||||
} |
||||
|
||||
/// Format H3 |
||||
foreach ($body->getH3() as $index => $h3) |
||||
{ |
||||
$lines[$index] = sprintf( |
||||
'<span size="large">%s</span>', |
||||
htmlentities( |
||||
$h3 |
||||
) |
||||
); |
||||
|
||||
$escaped[] = $index; |
||||
} |
||||
|
||||
/// Escape entities |
||||
foreach ($lines as $index => $line) |
||||
{ |
||||
if (!in_array($index, $escaped)) |
||||
{ |
||||
$lines[$index] = htmlentities( |
||||
$line |
||||
); |
||||
} |
||||
} |
||||
|
||||
// @TODO links, code, escape entities |
||||
|
||||
return implode( |
||||
PHP_EOL, |
||||
$lines |
||||
); |
||||
} |
||||
} |
Loading…
Reference in new issue