More components, resolve #31
All checks were successful
timw4mail/HummingBirdAnimeClient/pipeline/pr-master This commit looks good

This commit is contained in:
Timothy Warren 2020-08-26 17:26:42 -04:00
parent 9c01f3fbd0
commit 1b74df5269
20 changed files with 276 additions and 195 deletions

View File

@ -101,9 +101,11 @@ return static function (array $configArray = []): Container {
});
// Create Component helpers
$container->set('component-helper', static function () {
$container->set('component-helper', static function (ContainerInterface $container) {
$helper = (new HelperLocatorFactory)->newInstance();
$components = [
'animeCover' => Component\AnimeCover::class,
'mangaCover' => Component\MangaCover::class,
'character' => Component\Character::class,
'media' => Component\Media::class,
'tabs' => Component\Tabs::class,
@ -112,7 +114,11 @@ return static function (array $configArray = []): Container {
foreach ($components as $name => $componentClass)
{
$helper->set($name, fn () => new $componentClass);
$helper->set($name, static function () use ($container, $componentClass) {
$helper = new $componentClass;
$helper->setContainer($container);
return $helper;
});
}
return $helper;

View File

@ -1,7 +1,7 @@
<article
class="media"
data-kitsu-id="<?= $item['id'] ?>"
data-mal-id="<?= $item['mal_id'] ?>"
data-kitsu-id="<?= $item['id'] ?>"
data-mal-id="<?= $item['mal_id'] ?>"
>
<?php if ($auth->isAuthenticated()): ?>
<button title="Increment episode count" class="plus-one" hidden>+1 Episode</button>
@ -31,13 +31,13 @@
<?php if ($item['rewatched'] > 0): ?>
<div class="row">
<?php if ($item['rewatched'] == 1): ?>
<div>Rewatched once</div>
<div>Rewatched once</div>
<?php elseif ($item['rewatched'] == 2): ?>
<div>Rewatched twice</div>
<div>Rewatched twice</div>
<?php elseif ($item['rewatched'] == 3): ?>
<div>Rewatched thrice</div>
<div>Rewatched thrice</div>
<?php else: ?>
<div>Rewatched <?= $item['rewatched'] ?> times</div>
<div>Rewatched <?= $item['rewatched'] ?> times</div>
<?php endif ?>
</div>
<?php endif ?>

View File

@ -0,0 +1,74 @@
<article class="media" data-kitsu-id="<?= $item['id'] ?>" data-mal-id="<?= $item['mal_id'] ?>">
<?php if ($auth->isAuthenticated()): ?>
<div class="edit-buttons" hidden>
<button class="plus-one-chapter">+1 Chapter</button>
<?php /* <button class="plus-one-volume">+1 Volume</button> */ ?>
</div>
<?php endif ?>
<?= $helper->picture("images/manga/{$item['manga']['id']}.webp") ?>
<div class="name">
<a href="<?= $url->generate('manga.details', ['id' => $item['manga']['slug']]) ?>">
<?= $escape->html($item['manga']['title']) ?>
<?php foreach($item['manga']['titles'] as $title): ?>
<br /><small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
<div class="table">
<?php if ($auth->isAuthenticated()): ?>
<div class="row">
<span class="edit">
<a class="bracketed"
title="Edit information about this manga"
href="<?= $url->generate('edit', [
'controller' => 'manga',
'id' => $item['id'],
'status' => $name
]) ?>">
Edit
</a>
</span>
</div>
<?php endif ?>
<div class="row">
<div><?= $item['manga']['type'] ?></div>
<div class="user-rating">Rating: <?= $item['user_rating'] ?> / 10</div>
</div>
<?php if ($item['rereading']): ?>
<div class="row">
<?php foreach(['rereading'] as $attr): ?>
<?php if($item[$attr]): ?>
<span class="item-<?= $attr ?>"><?= ucfirst($attr) ?></span>
<?php endif ?>
<?php endforeach ?>
</div>
<?php endif ?>
<?php if ($item['reread'] > 0): ?>
<div class="row">
<?php if ($item['reread'] == 1): ?>
<div>Reread once</div>
<?php elseif ($item['reread'] == 2): ?>
<div>Reread twice</div>
<?php elseif ($item['reread'] == 3): ?>
<div>Reread thrice</div>
<?php else: ?>
<div>Reread <?= $item['reread'] ?> times</div>
<?php endif ?>
</div>
<?php endif ?>
<div class="row">
<div class="chapter_completion">
Chapters: <span class="chapters_read"><?= $item['chapters']['read'] ?></span> /
<span class="chapter_count"><?= $item['chapters']['total'] ?></span>
</div>
<?php /* </div>
<div class="row"> */ ?>
<div class="volume_completion">
Volumes: <span class="volume_count"><?= $item['volumes']['total'] ?></span>
</div>
</div>
</div>
</article>

View File

@ -11,6 +11,11 @@
<?= ($i === 0) ? 'checked="checked"' : '' ?>
/>
<label for="<?= $id ?>"><?= ucfirst($tabName) ?></label>
<?php if ($hasSectionWrapper): ?>
<div class="content full-height">
<?php endif ?>
<section
id="_<?= $id ?>"
role="tabpanel"
@ -18,6 +23,10 @@
>
<?= $callback($tabData, $tabName) ?>
</section>
<?php if ($hasSectionWrapper): ?>
</div>
<?php endif ?>
<?php endif ?>
<?php $i++; endforeach ?>
</div>

View File

@ -7,7 +7,7 @@
type="radio"
role='tab'
aria-controls="_<?= $id ?>"
name="staff-roles"
name="<?= $name ?>"
id="<?= $id ?>"
<?= $i === 0 ? 'checked="checked"' : '' ?>
/>

View File

@ -20,7 +20,7 @@
<section class="media-wrap">
<?php foreach($items as $item): ?>
<?php if ($item['private'] && ! $auth->isAuthenticated()) continue; ?>
<?php include __DIR__ . '/cover-item.php' ?>
<?= $component->animeCover($item) ?>
<?php endforeach ?>
</section>
</section>

View File

@ -19,7 +19,7 @@
<tr>
<td class="align-right"><label for="media_id">Media</label></td>
<td class='align-left'>
<?php include '_media-select-list.php' ?>
<?php include 'media-select-list.php' ?>
</td>
</tr>
<tr>

View File

@ -1,3 +1,4 @@
<?php use function Aviat\AnimeClient\renderTemplate; ?>
<main class="media-list">
<?php if ($auth->isAuthenticated()): ?>
<a class="bracketed" href="<?= $url->generate($collection_type . '.collection.add.get') ?>">Add Item</a>
@ -8,30 +9,20 @@
<br />
<label>Filter: <input type='text' class='media-filter' /></label>
<br />
<div class="tabs">
<?php $i = 0; ?>
<?php foreach ($sections as $name => $items): ?>
<input type="radio" id="collection-tab-<?= $i ?>" name="collection-tabs" />
<label for="collection-tab-<?= $i ?>"><h2><?= $name ?></h2></label>
<div class="content full-height">
<section class="media-wrap">
<?php foreach ($items as $item): ?>
<?php include __DIR__ . '/cover-item.php'; ?>
<?php endforeach ?>
</section>
</div>
<?php $i++; ?>
<?php endforeach ?>
<!-- All Tab -->
<input type='radio' checked='checked' id='collection-tab-<?= $i ?>' name='collection-tabs' />
<label for='collection-tab-<?= $i ?>'><h2>All</h2></label>
<div class='content full-height'>
<section class="media-wrap">
<?php foreach ($all as $item): ?>
<?php include __DIR__ . '/cover-item.php'; ?>
<?php endforeach ?>
</section>
</div>
</div>
<?= $component->tabs('collection-tab', $sections, static function ($items) use ($auth, $collection_type, $helper, $url, $component) {
$rendered = [];
foreach ($items as $item)
{
$rendered[] = renderTemplate(__DIR__ . '/cover-item.php', [
'auth' => $auth,
'collection_type' => $collection_type,
'helper' => $helper,
'item' => $item,
'url' => $url,
]);
}
return implode('', array_map('mb_trim', $rendered));
}, 'media-wrap', true) ?>
<?php endif ?>
</main>

View File

@ -1,3 +1,4 @@
<?php use function Aviat\AnimeClient\renderTemplate ?>
<?php if ($auth->isAuthenticated()): ?>
<main>
<h2>Edit Anime Collection Item</h2>
@ -24,7 +25,7 @@
<tr>
<td class="align-right"><label for="media_id">Media</label></td>
<td class="align-left">
<?php include '_media-select-list.php' ?>
<?php include 'media-select-list.php' ?>
</td>
</tr>
<tr>

View File

@ -1,44 +0,0 @@
<input type='radio' checked='checked' id='collection-tab-<?= $i ?>' name='collection-tabs' />
<label for='collection-tab-<?= $i ?>'><h2>All</h2></label>
<div class="content full-height">
<table class="full-width media-wrap">
<thead>
<tr>
<?php if ($auth->isAuthenticated()): ?><td>&nbsp;</td><?php endif ?>
<th>Title</th>
<th>Media</th>
<th>Episode Count</th>
<th>Episode Length</th>
<th>Show Type</th>
<th>Age Rating</th>
<th>Notes</th>
<th>Genres</th>
</tr>
</thead>
<tbody>
<?php foreach ($all as $item): ?>
<?php $editLink = $url->generate($collection_type . '.collection.edit.get', ['id' => $item['hummingbird_id']]); ?>
<tr>
<?php if ($auth->isAuthenticated()): ?>
<td>
<a class="bracketed" href="<?= $editLink ?>">Edit</a>
</td>
<?php endif ?>
<td class="align-left">
<a href="<?= $url->generate('anime.details', ['id' => $item['slug']]) ?>">
<?= $item['title'] ?>
</a>
<?= ! empty($item['alternate_title']) ? ' <br /><small> ' . $item['alternate_title'] . '</small>' : '' ?>
</td>
<td><?= implode(', ', $item['media']) ?></td>
<td><?= ($item['episode_count'] > 1) ? $item['episode_count'] : '-' ?></td>
<td><?= $item['episode_length'] ?></td>
<td><?= $item['show_type'] ?></td>
<td><?= $item['age_rating'] ?></td>
<td class="align-left"><?= nl2br($item['notes'], TRUE) ?></td>
<td class="align-left"><?= implode(', ', $item['genres']) ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
</div>

View File

@ -11,6 +11,9 @@
</a>
<?= ! empty($item['alternate_title']) ? ' <br /><small> ' . $item['alternate_title'] . '</small>' : '' ?>
</td>
<?php if ($hasMedia): ?>
<td><?= implode(', ', $item['media']) ?></td>
<?php endif ?>
<td><?= ($item['episode_count'] > 1) ? $item['episode_count'] : '-' ?></td>
<td><?= $item['episode_length'] ?></td>
<td><?= $item['show_type'] ?></td>

View File

@ -1,4 +1,4 @@
<?php use function Aviat\AnimeClient\colNotEmpty; ?>
<?php use function Aviat\AnimeClient\{colNotEmpty, renderTemplate}; ?>
<main>
<?php if ($auth->isAuthenticated()): ?>
<a class="bracketed" href="<?= $url->generate($collection_type . '.collection.add.get') ?>">Add Item</a>
@ -9,38 +9,48 @@
<br />
<label>Filter: <input type='text' class='media-filter' /></label>
<br />
<?php $i = 0; ?>
<div class="tabs">
<?php foreach ($sections as $name => $items): ?>
<?php $hasNotes = colNotEmpty($items, 'notes') ?>
<input type="radio" id="collection-tab-<?= $i ?>" name="collection-tabs" />
<label for="collection-tab-<?= $i ?>"><h2><?= $name ?></h2></label>
<div class="content full-height">
<table class="full-width media-wrap">
<thead>
<tr>
<?php if ($auth->isAuthenticated()): ?><td>&nbsp;</td><?php endif ?>
<th>Title</th>
<th>Episode Count</th>
<th>Episode Length</th>
<th>Show Type</th>
<th>Age Rating</th>
<?php if ($hasNotes): ?><th>Notes</th><?php endif ?>
<th>Genres</th>
</tr>
</thead>
<tbody>
<?php foreach ($items as $item): ?>
<?php include 'list-item.php' ?>
<?php endforeach ?>
</tbody>
</table>
</div>
<?php $i++ ?>
<?php endforeach ?>
<!-- All -->
<?php include 'list-all.php' ?>
</div>
<?= $component->tabs('collection-tab', $sections, static function ($items, $section) use ($auth, $helper, $url, $collection_type) {
$hasNotes = colNotEmpty($items, 'notes');
$hasMedia = $section === 'All';
$firstTh = ($auth->isAuthenticated()) ? '<td>&nbsp;</td>' : '';
$mediaTh = ($hasMedia) ? '<th>Media</th>' : '';
$noteTh = ($hasNotes) ? '<th>Notes</th>' : '';
$rendered = [];
foreach ($items as $item)
{
$rendered[] = renderTemplate(__DIR__ . '/list-item.php', [
'auth' => $auth,
'collection_type' => $collection_type,
'hasMedia' => $hasMedia,
'hasNotes' => $hasNotes,
'helper' => $helper,
'item' => $item,
'url' => $url,
]);
}
$rows = implode('', array_map('mb_trim', $rendered));
return <<<HTML
<table class="full-width media-wrap">
<thead>
<tr>
{$firstTh}
<th>Title</th>
{$mediaTh}
<th>Episode Count</th>
<th>Episode Length</th>
<th>Show Type</th>
<th>Age Rating</th>
{$noteTh}
<th>Genres</th>
</tr>
</thead>
<tbody>{$rows}</tbody>
</table>
HTML;
}) ?>
<?php endif ?>
</main>
<script defer="defer" src="<?= $urlGenerator->assetUrl('js/tables.min.js') ?>"></script>

View File

@ -19,80 +19,7 @@
<h2><?= $escape->html($name) ?></h2>
<section class="media-wrap">
<?php foreach($items as $item): ?>
<article class="media" data-kitsu-id="<?= $item['id'] ?>" data-mal-id="<?= $item['mal_id'] ?>">
<?php if ($auth->isAuthenticated()): ?>
<div class="edit-buttons" hidden>
<button class="plus-one-chapter">+1 Chapter</button>
<?php /* <button class="plus-one-volume">+1 Volume</button> */ ?>
</div>
<?php endif ?>
<?= $helper->picture("images/manga/{$item['manga']['id']}.webp") ?>
<div class="name">
<a href="<?= $url->generate('manga.details', ['id' => $item['manga']['slug']]) ?>">
<?= $escape->html($item['manga']['title']) ?>
<?php foreach($item['manga']['titles'] as $title): ?>
<br /><small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
<div class="table">
<?php if ($auth->isAuthenticated()): ?>
<div class="row">
<span class="edit">
<a class="bracketed"
title="Edit information about this manga"
href="<?= $url->generate('edit', [
'controller' => 'manga',
'id' => $item['id'],
'status' => $name
]) ?>">
Edit
</a>
</span>
</div>
<?php endif ?>
<div class="row">
<div><?= $item['manga']['type'] ?></div>
<div class="user-rating">Rating: <?= $item['user_rating'] ?> / 10</div>
</div>
<?php if ($item['rereading']): ?>
<div class="row">
<?php foreach(['rereading'] as $attr): ?>
<?php if($item[$attr]): ?>
<span class="item-<?= $attr ?>"><?= ucfirst($attr) ?></span>
<?php endif ?>
<?php endforeach ?>
</div>
<?php endif ?>
<?php if ($item['reread'] > 0): ?>
<div class="row">
<?php if ($item['reread'] == 1): ?>
<div>Reread once</div>
<?php elseif ($item['reread'] == 2): ?>
<div>Reread twice</div>
<?php elseif ($item['reread'] == 3): ?>
<div>Reread thrice</div>
<?php else: ?>
<div>Reread <?= $item['reread'] ?> times</div>
<?php endif ?>
</div>
<?php endif ?>
<div class="row">
<div class="chapter_completion">
Chapters: <span class="chapters_read"><?= $item['chapters']['read'] ?></span> /
<span class="chapter_count"><?= $item['chapters']['total'] ?></span>
</div>
<?php /* </div>
<div class="row"> */ ?>
<div class="volume_completion">
Volumes: <span class="volume_count"><?= $item['volumes']['total'] ?></span>
</div>
</div>
</div>
</article>
<?= $component->mangaCover($item, $name) ?>
<?php endforeach ?>
</section>
</section>

View File

@ -370,3 +370,18 @@ function clearCache(CacheInterface $cache): bool
return $cleared && $saved;
}
/**
* Render a PHP code template as a string
*
* @param string $path
* @param array $data
* @return string
*/
function renderTemplate(string $path, array $data): string
{
ob_start();
extract($data, EXTR_OVERWRITE);
include $path;
return ob_get_clean();
}

View File

@ -0,0 +1,30 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 7.4
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @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;
use Aviat\AnimeClient\Types\AnimeListItem;
final class AnimeCover {
use ComponentTrait;
public function __invoke(AnimeListItem $item): string
{
return $this->render('anime-cover.php', [
'item' => $item,
]);
}
}

View File

@ -16,15 +16,35 @@
namespace Aviat\AnimeClient\Component;
use Aviat\Ion\Di\ContainerAware;
use const TEMPLATE_DIR;
use function Aviat\AnimeClient\renderTemplate;
/**
* Shared logic for component-based functionality, like Tabs
*/
trait ComponentTrait {
use ContainerAware;
/**
* Render a template with common container values
*
* @param string $path
* @param array $data
* @return string
*/
public function render(string $path, array $data): string
{
ob_start();
extract($data, EXTR_OVERWRITE);
include \TEMPLATE_DIR . '/' .$path;
return ob_get_clean();
$container = $this->getContainer();
$helper = $container->get('html-helper');
$baseData = [
'auth' => $container->get('auth'),
'escape' => $helper->escape(),
'helper' => $helper,
'url' => $container->get('aura-router')->getGenerator(),
];
return renderTemplate(TEMPLATE_DIR . '/' . $path, array_merge($baseData, $data));
}
}

View File

@ -0,0 +1,31 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 7.4
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @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;
use Aviat\AnimeClient\Types\MangaListItem;
final class MangaCover {
use ComponentTrait;
public function __invoke(MangaListItem $item, string $name): string
{
return $this->render('manga-cover.php', [
'item' => $item,
'name' => $name,
]);
}
}

View File

@ -26,13 +26,16 @@ final class Tabs {
* 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
* @param string $className
* @param bool $hasSectionWrapper
* @return string
*/
public function __invoke(
string $name,
array $tabData,
callable $cb,
string $className = 'content media-wrap flex flex-wrap flex-justify-start'
string $className = 'content media-wrap flex flex-wrap flex-justify-start',
bool $hasSectionWrapper = false
): string
{
return $this->render('tabs.php', [
@ -40,6 +43,7 @@ final class Tabs {
'data' => $tabData,
'callback' => $cb,
'className' => $className,
'hasSectionWrapper' => $hasSectionWrapper,
]);
}
}

View File

@ -101,10 +101,14 @@ final class AnimeCollection extends BaseController {
'list' => 'list'
];
$sections = array_merge(
['All' => $this->animeCollectionModel->getFlatCollection()],
$this->animeCollectionModel->getCollection()
);
$this->outputHTML('collection/' . $viewMap[$view], [
'title' => $this->config->get('whose_list') . "'s Anime Collection",
'sections' => $this->animeCollectionModel->getCollection(),
'all' => $this->animeCollectionModel->getFlatCollection(),
'sections' => $sections,
]);
}