mirror of
https://github.com/YGGverse/Yoda.git
synced 2025-03-13 06:01:21 +00:00
init window tabs, init gemtext parser, refactor app sceleton
This commit is contained in:
parent
196b30e8d2
commit
ad04be61cb
133
config.json
133
config.json
@ -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 @@
|
||||
<?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 @@
|
||||
<?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 @@
|
||||
<?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 @@
|
||||
<?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 @@
|
||||
<?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 @@
|
||||
<?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 @@
|
||||
<?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 @@
|
||||
<?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 @@
|
||||
<?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 @@
|
||||
<?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 @@
|
||||
<?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 @@
|
||||
<?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 @@
|
||||
<?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 @@
|
||||
<?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 @@
|
||||
<?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 @@
|
||||
<?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
|
||||
);
|
||||
}
|
||||
}
|
525
src/Tab/Page.php
Normal file
525
src/Tab/Page.php
Normal file
@ -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
|
||||
);
|
||||
}
|
||||
}
|
52
src/Yoda.php
52
src/Yoda.php
@ -9,6 +9,56 @@ require_once __DIR__ .
|
||||
// Init app
|
||||
\Gtk::init();
|
||||
|
||||
new \Yggverse\Yoda\Controller\Browser();
|
||||
$config = \Yggverse\Yoda\Model\File::getConfig(); // @TODO
|
||||
|
||||
$window = new \GtkWindow();
|
||||
|
||||
$window->set_size_request(
|
||||
$config->window->width,
|
||||
$config->window->height
|
||||
);
|
||||
|
||||
if ($config->window->header->enabled)
|
||||
{
|
||||
$header = new \GtkHeaderBar();
|
||||
|
||||
$header->set_title(
|
||||
$config->window->title
|
||||
);
|
||||
|
||||
$header->set_show_close_button(
|
||||
$config->window->header->button->close
|
||||
);
|
||||
|
||||
$window->set_titlebar(
|
||||
$header
|
||||
);
|
||||
}
|
||||
|
||||
$window->connect(
|
||||
'destroy',
|
||||
function()
|
||||
{
|
||||
\Gtk::main_quit();
|
||||
}
|
||||
);
|
||||
|
||||
$page = new \Yggverse\Yoda\Tab\Page();
|
||||
|
||||
$page->open(
|
||||
'yoda://welcome'
|
||||
);
|
||||
|
||||
$tab = new \GtkNotebook();
|
||||
|
||||
$tab->add(
|
||||
$page->box
|
||||
);
|
||||
|
||||
$window->add(
|
||||
$tab
|
||||
);
|
||||
|
||||
$window->show_all();
|
||||
|
||||
\Gtk::main();
|
Loading…
x
Reference in New Issue
Block a user