From 7aeb74874b0a5ee85489b58c2fdfe2d3cdbcf812 Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Fri, 21 Aug 2020 12:30:01 -0400 Subject: [PATCH] Create component system to help cut down on view duplication, see #31 --- app/bootstrap.php | 53 +++++--- app/templates/character.php | 6 + app/templates/media.php | 12 ++ app/templates/tabs.php | 23 ++++ app/templates/vertical-tabs.php | 25 ++++ app/views/anime/details.php | 122 +++++++++---------- app/views/character/details.php | 101 +++++++-------- app/views/header.php | 3 +- console | 3 + frontEndSrc/css/src/general.css | 1 + src/AnimeClient/Component/Character.php | 31 +++++ src/AnimeClient/Component/ComponentTrait.php | 30 +++++ src/AnimeClient/Component/Media.php | 31 +++++ src/AnimeClient/Component/Tabs.php | 45 +++++++ src/AnimeClient/Component/VerticalTabs.php | 45 +++++++ src/AnimeClient/FormGenerator.php | 21 +++- src/AnimeClient/Helper/Form.php | 2 +- src/AnimeClient/Helper/Menu.php | 3 +- src/AnimeClient/MenuGenerator.php | 75 +++++++----- src/Ion/View/HtmlView.php | 15 +-- 20 files changed, 461 insertions(+), 186 deletions(-) create mode 100644 app/templates/character.php create mode 100644 app/templates/media.php create mode 100644 app/templates/tabs.php create mode 100644 app/templates/vertical-tabs.php create mode 100644 src/AnimeClient/Component/Character.php create mode 100644 src/AnimeClient/Component/ComponentTrait.php create mode 100644 src/AnimeClient/Component/Media.php create mode 100644 src/AnimeClient/Component/Tabs.php create mode 100644 src/AnimeClient/Component/VerticalTabs.php diff --git a/app/bootstrap.php b/app/bootstrap.php index af80becb..f12127c2 100644 --- a/app/bootstrap.php +++ b/app/bootstrap.php @@ -20,6 +20,7 @@ use Aura\Html\HelperLocatorFactory; use Aura\Router\RouterContainer; use Aura\Session\SessionFactory; use Aviat\AnimeClient\API\{Anilist, Kitsu}; +use Aviat\AnimeClient\Component; use Aviat\AnimeClient\Model; use Aviat\Banker\Teller; use Aviat\Ion\Config; @@ -31,6 +32,9 @@ use Monolog\Handler\RotatingFileHandler; use Monolog\Logger; use Psr\SimpleCache\CacheInterface; +define('APP_DIR', __DIR__); +define('TEMPLATE_DIR', APP_DIR . '/templates'); + // ----------------------------------------------------------------------------- // Setup DI container // ----------------------------------------------------------------------------- @@ -72,28 +76,45 @@ return static function (array $configArray = []): Container { // Create Aura Router Object $container->set('aura-router', fn() => new RouterContainer); - // Create Html helper Object + // Create Html helpers $container->set('html-helper', static function(ContainerInterface $container) { $htmlHelper = (new HelperLocatorFactory)->newInstance(); - $htmlHelper->set('menu', static function() use ($container) { - $menuHelper = new Helper\Menu(); - $menuHelper->setContainer($container); - return $menuHelper; - }); - $htmlHelper->set('field', static function() use ($container) { - $formHelper = new Helper\Form(); - $formHelper->setContainer($container); - return $formHelper; - }); - $htmlHelper->set('picture', static function() use ($container) { - $pictureHelper = new Helper\Picture(); - $pictureHelper->setContainer($container); - return $pictureHelper; - }); + $helpers = [ + 'menu' => Helper\Menu::class, + 'field' => Helper\Form::class, + 'picture' => Helper\Picture::class, + ]; + + foreach ($helpers as $name => $class) + { + $htmlHelper->set($name, static function() use ($class, $container) { + $helper = new $class; + $helper->setContainer($container); + return $helper; + }); + } return $htmlHelper; }); + // Create Component helpers + $container->set('component-helper', static function () { + $helper = (new HelperLocatorFactory)->newInstance(); + $components = [ + 'character' => Component\Character::class, + 'media' => Component\Media::class, + 'tabs' => Component\Tabs::class, + 'verticalTabs' => Component\VerticalTabs::class, + ]; + + foreach ($components as $name => $componentClass) + { + $helper->set($name, fn () => new $componentClass); + } + + return $helper; + }); + // Create Request Object $container->set('request', fn () => ServerRequestFactory::fromGlobals( $_SERVER, diff --git a/app/templates/character.php b/app/templates/character.php new file mode 100644 index 00000000..78e00795 --- /dev/null +++ b/app/templates/character.php @@ -0,0 +1,6 @@ +
+
+ +
+ +
\ No newline at end of file diff --git a/app/templates/media.php b/app/templates/media.php new file mode 100644 index 00000000..3cf7f25e --- /dev/null +++ b/app/templates/media.php @@ -0,0 +1,12 @@ +
+ + +
\ No newline at end of file diff --git a/app/templates/tabs.php b/app/templates/tabs.php new file mode 100644 index 00000000..17294e63 --- /dev/null +++ b/app/templates/tabs.php @@ -0,0 +1,23 @@ +
+ $tabData): ?> + + + + /> + +
+ +
+ + +
\ No newline at end of file diff --git a/app/templates/vertical-tabs.php b/app/templates/vertical-tabs.php new file mode 100644 index 00000000..72e3827f --- /dev/null +++ b/app/templates/vertical-tabs.php @@ -0,0 +1,25 @@ +
+ + $tabData): ?> + +
+ + /> + +
+ +
+
+ + +
\ No newline at end of file diff --git a/app/views/anime/details.php b/app/views/anime/details.php index 75a71eea..b6fa4150 100644 --- a/app/views/anime/details.php +++ b/app/views/anime/details.php @@ -1,9 +1,11 @@
-
+
0): ?> -
-

Characters

+
+

Characters

-
- - $list): ?> - /> - -
- $char): ?> - - - - -
- - -
-
+ tabs('character-types', $data['characters'], static function ($characterList, $role) + use ($component, $url, $helper) { + $rendered = []; + foreach ($characterList as $id => $character): + if (empty($character['image']['original'])) + { + continue; + } + $rendered[] = $component->character( + $character['name'], + $url->generate('character', ['slug' => $character['slug']]), + $helper->picture("images/characters/{$id}.webp"), + (strtolower($role) !== 'main') ? 'small-character' : 'character' + ); + endforeach; + + return implode('', array_map('mb_trim', $rendered)); + }) ?> +
0): ?> -
-

Staff

+
+

Staff

-
- - $people): ?> -
- /> - -
- $person): ?> - - -
-
- - -
-
+ verticalTabs('staff-role', $data['staff'], static function ($staffList) + use ($component, $url, $helper) { + $rendered = []; + foreach ($staffList as $id => $person): + if (empty($person['image']['original'])) + { + continue; + } + $rendered[] = $component->character( + $person['name'], + $url->generate('person', ['id' => $person['id'], 'slug' => $person['slug']]), + $helper->picture(getLocalImg($person['image']['original'] ?? NULL)), + 'character small-person', + ); + endforeach; + + return implode('', array_map('mb_trim', $rendered)); + }) ?> +
\ No newline at end of file diff --git a/app/views/character/details.php b/app/views/character/details.php index 8d1b8a65..8e513f61 100644 --- a/app/views/character/details.php +++ b/app/views/character/details.php @@ -156,65 +156,50 @@ use Aviat\AnimeClient\API\Kitsu;

Voice Actors

-
- + tabs('character-vas', $vas, static function ($casting) use ($url, $component, $helper) { + $castings = []; + foreach ($casting as $id => $c): + $person = $component->character( + $c['person']['name'], + $url->generate('person', [ + 'id' => $c['person']['id'], + 'slug' => $c['person']['slug'] + ]), + $helper->picture(getLocalImg($c['person']['image'])) + ); + $medias = array_map(fn ($series) => $component->media( + array_merge([$series['title']], $series['titles']), + $url->generate('anime.details', ['id' => $series['slug']]), + $helper->picture(getLocalImg($series['posterImage'], TRUE)) + ), $c['series']); + $media = implode('', array_map('mb_trim', $medias)); - $casting): ?> - type="radio" id="character-va" - name="character-vas" - /> - -
- - - - - - - - - - - -
Cast MemberSeries
- - -
- - - -
-
-
- - -
+ $castings[] = << + {$person} + +
+ {$media} +
+ + +HTML; + endforeach; + + $languages = implode('', array_map('mb_trim', $castings)); + + return << + + + Cast Member + Series + + + {$languages} + +HTML; + }, 'content') ?> diff --git a/app/views/header.php b/app/views/header.php index 25828f09..16fc6d55 100644 --- a/app/views/header.php +++ b/app/views/header.php @@ -26,7 +26,7 @@ -
+
-
\ No newline at end of file diff --git a/console b/console index a0999077..e499c9b1 100755 --- a/console +++ b/console @@ -9,6 +9,9 @@ use ConsoleKit\Console; $_SERVER['HTTP_HOST'] = 'localhost'; +define('APP_DIR', __DIR__ . '/app'); +define('TEMPLATE_DIR', APP_DIR . '/templates'); + // ----------------------------------------------------------------------------- // Start console script // ----------------------------------------------------------------------------- diff --git a/frontEndSrc/css/src/general.css b/frontEndSrc/css/src/general.css index 587e0582..29efb1f5 100644 --- a/frontEndSrc/css/src/general.css +++ b/frontEndSrc/css/src/general.css @@ -94,6 +94,7 @@ a:hover, a:active { iframe { display: block; margin: 0 auto; + border: 0; } /* ----------------------------------------------------------------------------- diff --git a/src/AnimeClient/Component/Character.php b/src/AnimeClient/Component/Character.php new file mode 100644 index 00000000..df64471b --- /dev/null +++ b/src/AnimeClient/Component/Character.php @@ -0,0 +1,31 @@ + + * @copyright 2015 - 2020 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 5.1 + * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient + */ + +namespace Aviat\AnimeClient\Component; + +final class Character { + use ComponentTrait; + + public function __invoke(string $name, string $link, string $picture, string $className = 'character'): string + { + return $this->render('character.php', [ + 'name' => $name, + 'link' => $link, + 'picture' => $picture, + 'className' => $className, + ]); + } +} \ No newline at end of file diff --git a/src/AnimeClient/Component/ComponentTrait.php b/src/AnimeClient/Component/ComponentTrait.php new file mode 100644 index 00000000..d4f909ae --- /dev/null +++ b/src/AnimeClient/Component/ComponentTrait.php @@ -0,0 +1,30 @@ + + * @copyright 2015 - 2020 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 5.1 + * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient + */ + +namespace Aviat\AnimeClient\Component; + +/** + * Shared logic for component-based functionality, like Tabs + */ +trait ComponentTrait { + public function render(string $path, array $data): string + { + ob_start(); + extract($data, EXTR_OVERWRITE); + include \TEMPLATE_DIR . '/' .$path; + return ob_get_clean(); + } +} \ No newline at end of file diff --git a/src/AnimeClient/Component/Media.php b/src/AnimeClient/Component/Media.php new file mode 100644 index 00000000..5da84759 --- /dev/null +++ b/src/AnimeClient/Component/Media.php @@ -0,0 +1,31 @@ + + * @copyright 2015 - 2020 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 5.1 + * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient + */ + +namespace Aviat\AnimeClient\Component; + +final class Media { + use ComponentTrait; + + public function __invoke(array $titles, string $link, string $picture, string $className = 'media'): string + { + return $this->render('media.php', [ + 'titles' => $titles, + 'link' => $link, + 'picture' => $picture, + 'className' => $className, + ]); + } +} \ No newline at end of file diff --git a/src/AnimeClient/Component/Tabs.php b/src/AnimeClient/Component/Tabs.php new file mode 100644 index 00000000..91c9d572 --- /dev/null +++ b/src/AnimeClient/Component/Tabs.php @@ -0,0 +1,45 @@ + + * @copyright 2015 - 2020 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 5.1 + * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient + */ + +namespace Aviat\AnimeClient\Component; + +final class Tabs { + use ComponentTrait; + + /** + * Creates a tabbed content view + * + * @param string $name the name attribute for the input[type-option] form elements + * also used to generate id attributes + * @param array $tabData The data used to create the tab content, indexed by the tab label + * @param callable $cb The function to generate the tab content + * @return string + */ + public function __invoke( + string $name, + array $tabData, + callable $cb, + string $className = 'content media-wrap flex flex-wrap flex-justify-start' + ): string + { + return $this->render('tabs.php', [ + 'name' => $name, + 'data' => $tabData, + 'callback' => $cb, + 'className' => $className, + ]); + } +} \ No newline at end of file diff --git a/src/AnimeClient/Component/VerticalTabs.php b/src/AnimeClient/Component/VerticalTabs.php new file mode 100644 index 00000000..de05abf8 --- /dev/null +++ b/src/AnimeClient/Component/VerticalTabs.php @@ -0,0 +1,45 @@ + + * @copyright 2015 - 2020 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 5.1 + * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient + */ + +namespace Aviat\AnimeClient\Component; + +final class VerticalTabs { + use ComponentTrait; + + /** + * Creates a vertical tab content view + * + * @param string $name the name attribute for the input[type-option] form elements + * also used to generate id attributes + * @param array $tabData The data used to create the tab content, indexed by the tab label + * @param callable $cb The function to generate the tab content + * @return string + */ + public function __invoke( + string $name, + array $tabData, + callable $cb, + string $className='content media-wrap flex flex-wrap flex-justify-start' + ): string + { + return $this->render('vertical-tabs.php', [ + 'name' => $name, + 'data' => $tabData, + 'callback' => $cb, + 'className' => $className, + ]); + } +} \ No newline at end of file diff --git a/src/AnimeClient/FormGenerator.php b/src/AnimeClient/FormGenerator.php index 52873e24..94bbc28e 100644 --- a/src/AnimeClient/FormGenerator.php +++ b/src/AnimeClient/FormGenerator.php @@ -39,11 +39,30 @@ final class FormGenerator { * @throws ContainerException * @throws NotFoundException */ - public function __construct(ContainerInterface $container) + private function __construct(ContainerInterface $container) { $this->helper = $container->get('html-helper'); } + /** + * Create a new FormGenerator + * + * @param ContainerInterface $container + * @return $this + */ + public static function new(ContainerInterface $container): self + { + try + { + return new static($container); + } + catch (\Throwable $e) + { + dump($e); + die(); + } + } + /** * Generate the html structure of the form * diff --git a/src/AnimeClient/Helper/Form.php b/src/AnimeClient/Helper/Form.php index 7438a7f6..b1a60f93 100644 --- a/src/AnimeClient/Helper/Form.php +++ b/src/AnimeClient/Helper/Form.php @@ -35,6 +35,6 @@ final class Form { */ public function __invoke(string $name, array $form) { - return (new FormGenerator($this->container))->generate($name, $form); + return FormGenerator::new($this->container)->generate($name, $form); } } diff --git a/src/AnimeClient/Helper/Menu.php b/src/AnimeClient/Helper/Menu.php index 979892af..ad3fa263 100644 --- a/src/AnimeClient/Helper/Menu.php +++ b/src/AnimeClient/Helper/Menu.php @@ -34,8 +34,7 @@ final class Menu { */ public function __invoke($menuName) { - $generator = new MenuGenerator($this->container); - return $generator->generate($menuName); + return MenuGenerator::new($this->container)->generate($menuName); } } diff --git a/src/AnimeClient/MenuGenerator.php b/src/AnimeClient/MenuGenerator.php index 87d8c411..65e5a98c 100644 --- a/src/AnimeClient/MenuGenerator.php +++ b/src/AnimeClient/MenuGenerator.php @@ -44,40 +44,20 @@ final class MenuGenerator extends UrlGenerator { protected RequestInterface $request; /** - * MenuGenerator constructor. - * * @param ContainerInterface $container - * @throws ContainerException - * @throws NotFoundException + * @return static */ - public function __construct(ContainerInterface $container) + public static function new(ContainerInterface $container): self { - parent::__construct($container); - $this->helper = $container->get('html-helper'); - $this->request = $container->get('request'); - } - - /** - * Generate the full menu structure from the config files - * - * @param array $menus - * @return array - */ - protected function parseConfig(array $menus) : array - { - $parsed = []; - - foreach ($menus as $name => $menu) + try { - $parsed[$name] = []; - foreach ($menu['items'] as $pathName => $partialPath) - { - $title = (string)StringType::from($pathName)->humanize()->titleize(); - $parsed[$name][$title] = (string)StringType::from($menu['route_prefix'])->append($partialPath); - } + return new static($container); + } + catch (\Throwable $e) + { + dump($e); + die(); } - - return $parsed; } /** @@ -120,5 +100,42 @@ final class MenuGenerator extends UrlGenerator { // Create the menu html return (string) $this->helper->ul(); } + + /** + * MenuGenerator constructor. + * + * @param ContainerInterface $container + * @throws ContainerException + * @throws NotFoundException + */ + private function __construct(ContainerInterface $container) + { + parent::__construct($container); + $this->helper = $container->get('html-helper'); + $this->request = $container->get('request'); + } + + /** + * Generate the full menu structure from the config files + * + * @param array $menus + * @return array + */ + private function parseConfig(array $menus) : array + { + $parsed = []; + + foreach ($menus as $name => $menu) + { + $parsed[$name] = []; + foreach ($menu['items'] as $pathName => $partialPath) + { + $title = (string)StringType::from($pathName)->humanize()->titleize(); + $parsed[$name][$title] = (string)StringType::from($menu['route_prefix'])->append($partialPath); + } + } + + return $parsed; + } } // End of MenuGenerator.php \ No newline at end of file diff --git a/src/Ion/View/HtmlView.php b/src/Ion/View/HtmlView.php index f7fcca05..e208ff45 100644 --- a/src/Ion/View/HtmlView.php +++ b/src/Ion/View/HtmlView.php @@ -16,7 +16,6 @@ namespace Aviat\Ion\View; -use Aura\Html\HelperLocator; use Aviat\Ion\Di\ContainerAware; use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\Exception\ContainerException; @@ -30,13 +29,6 @@ use const EXTR_OVERWRITE; class HtmlView extends HttpView { use ContainerAware; - /** - * HTML generator/escaper helper - * - * @var HelperLocator - */ - protected HelperLocator $helper; - /** * Response mime type * @@ -56,7 +48,6 @@ class HtmlView extends HttpView { parent::__construct(); $this->setContainer($container); - $this->helper = $container->get('html-helper'); $this->response = new HtmlResponse(''); } @@ -69,8 +60,10 @@ class HtmlView extends HttpView { */ public function renderTemplate(string $path, array $data): string { - $data['helper'] = $this->helper; - $data['escape'] = $this->helper->escape(); + $helper = $this->container->get('html-helper'); + $data['component'] = $this->container->get('component-helper'); + $data['helper'] = $helper; + $data['escape'] = $helper->escape(); $data['container'] = $this->container; ob_start();