More components, resolve #31

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,80 +19,7 @@
<h2><?= $escape->html($name) ?></h2> <h2><?= $escape->html($name) ?></h2>
<section class="media-wrap"> <section class="media-wrap">
<?php foreach($items as $item): ?> <?php foreach($items as $item): ?>
<article class="media" data-kitsu-id="<?= $item['id'] ?>" data-mal-id="<?= $item['mal_id'] ?>"> <?= $component->mangaCover($item, $name) ?>
<?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>
<?php endforeach ?> <?php endforeach ?>
</section> </section>
</section> </section>

View File

@ -370,3 +370,18 @@ function clearCache(CacheInterface $cache): bool
return $cleared && $saved; 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; 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 * Shared logic for component-based functionality, like Tabs
*/ */
trait ComponentTrait { 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 public function render(string $path, array $data): string
{ {
ob_start(); $container = $this->getContainer();
extract($data, EXTR_OVERWRITE); $helper = $container->get('html-helper');
include \TEMPLATE_DIR . '/' .$path;
return ob_get_clean(); $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 * also used to generate id attributes
* @param array $tabData The data used to create the tab content, indexed by the tab label * @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 callable $cb The function to generate the tab content
* @param string $className
* @param bool $hasSectionWrapper
* @return string * @return string
*/ */
public function __invoke( public function __invoke(
string $name, string $name,
array $tabData, array $tabData,
callable $cb, 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 ): string
{ {
return $this->render('tabs.php', [ return $this->render('tabs.php', [
@ -40,6 +43,7 @@ final class Tabs {
'data' => $tabData, 'data' => $tabData,
'callback' => $cb, 'callback' => $cb,
'className' => $className, 'className' => $className,
'hasSectionWrapper' => $hasSectionWrapper,
]); ]);
} }
} }

View File

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