Do you wish to register an account?

All in GraphQL #34

Merged
timw4mail merged 87 commits from develop into master 8 months ago
  1. 5
      CHANGELOG.md
  2. 2
      RoboFile.php
  3. 19
      app/appConf/routes.php
  4. 101
      app/bootstrap.php
  5. 12
      app/templates/anime-cover.php
  6. 6
      app/templates/character.php
  7. 74
      app/templates/manga-cover.php
  8. 12
      app/templates/media.php
  9. 5
      app/templates/single-tab.php
  10. 32
      app/templates/tabs.php
  11. 25
      app/templates/vertical-tabs.php
  12. 2
      app/views/anime/cover.php
  13. 174
      app/views/anime/details.php
  14. 2
      app/views/anime/edit.php
  15. 9
      app/views/anime/list.php
  16. 168
      app/views/character/details.php
  17. 2
      app/views/collection/add.php
  18. 41
      app/views/collection/cover.php
  19. 3
      app/views/collection/edit.php
  20. 44
      app/views/collection/list-all.php
  21. 3
      app/views/collection/list-item.php
  22. 76
      app/views/collection/list.php
  23. 0
      app/views/collection/media-select-list.php
  24. 1
      app/views/header.php
  25. 75
      app/views/manga/cover.php
  26. 109
      app/views/manga/details.php
  27. 4
      app/views/manga/list.php
  28. 67
      app/views/person/character-mapping.php
  29. 91
      app/views/person/details.php
  30. 12
      app/views/settings/settings.php
  31. 114
      app/views/user/details.php
  32. 2
      build/header_comment.txt
  33. 6
      composer.json
  34. 3
      console
  35. 11
      frontEndSrc/css/src/components.css
  36. 3
      frontEndSrc/css/src/dark-override.css
  37. 1
      frontEndSrc/css/src/general.css
  38. 5
      frontEndSrc/js/anime-client.js
  39. 18
      frontEndSrc/js/anime.js
  40. 1
      frontEndSrc/js/index.js
  41. 18
      frontEndSrc/js/manga.js
  42. 41
      frontEndSrc/js/session-check.js
  43. 26
      frontEndSrc/js/template-helpers.js
  44. 2
      index.php
  45. 2
      public/css/auto.min.css
  46. 2
      public/css/dark.min.css
  47. 2
      public/css/light.min.css
  48. 5
      public/es/anon.js
  49. 107
      public/es/scripts.js
  50. 2
      public/images/streaming-logos/amazon.svg
  51. 1
      public/images/streaming-logos/animelab.svg
  52. 1
      public/images/streaming-logos/vrv.svg
  53. 2
      public/js/anon.min.js
  54. 2
      public/js/anon.min.js.map
  55. 29
      public/js/scripts.min.js
  56. 2
      public/js/scripts.min.js.map
  57. 13
      src/AnimeClient/API.php
  58. 76
      src/AnimeClient/API/APIRequestBuilder.php
  59. 10
      src/AnimeClient/API/AbstractListItem.php
  60. 2
      src/AnimeClient/API/Anilist.php
  61. 15
      src/AnimeClient/API/Anilist/.graphqlconfig
  62. 47
      src/AnimeClient/API/Anilist/AnilistRequestBuilder.php
  63. 159
      src/AnimeClient/API/Anilist/GraphQL/Queries/Unused/AnimeDetails.graphql
  64. 9
      src/AnimeClient/API/Anilist/GraphQL/Queries/Unused/ListItemIdByMalId.graphql
  65. 101
      src/AnimeClient/API/Anilist/GraphQL/Queries/Unused/MangaDetails.graphql
  66. 5
      src/AnimeClient/API/Anilist/GraphQL/Queries/Unused/MangaIdByMalId.graphql
  67. 56
      src/AnimeClient/API/Anilist/GraphQL/Queries/Unused/UserAnimeList.graphql
  68. 56
      src/AnimeClient/API/Anilist/GraphQL/Queries/Unused/UserMangaList.graphql
  69. 16
      src/AnimeClient/API/Anilist/ListItem.php
  70. 2
      src/AnimeClient/API/Anilist/MissingIdException.php
  71. 24
      src/AnimeClient/API/Anilist/Model.php
  72. 0
      src/AnimeClient/API/Anilist/Mutations/CreateFullMediaListEntry.graphql
  73. 0
      src/AnimeClient/API/Anilist/Mutations/CreateMediaListEntry.graphql
  74. 0
      src/AnimeClient/API/Anilist/Mutations/DeleteMediaListEntry.graphql
  75. 0
      src/AnimeClient/API/Anilist/Mutations/IncrementMediaListEntry.graphql
  76. 0
      src/AnimeClient/API/Anilist/Mutations/UpdateMediaListEntry.graphql
  77. 0
      src/AnimeClient/API/Anilist/Queries/CheckLogin.graphql
  78. 0
      src/AnimeClient/API/Anilist/Queries/ListItemIdByMediaId.graphql
  79. 0
      src/AnimeClient/API/Anilist/Queries/MediaIdByMalId.graphql
  80. 0
      src/AnimeClient/API/Anilist/Queries/MediaListItem.graphql
  81. 0
      src/AnimeClient/API/Anilist/Queries/SyncUserList.graphql
  82. 107
      src/AnimeClient/API/Anilist/RequestBuilder.php
  83. 41
      src/AnimeClient/API/Anilist/RequestBuilderTrait.php
  84. 2
      src/AnimeClient/API/Anilist/Transformer/AnimeListTransformer.php
  85. 2
      src/AnimeClient/API/Anilist/Transformer/MangaListTransformer.php
  86. 18
      src/AnimeClient/API/Anilist/Types/MediaListEntry.php
  87. 4427
      src/AnimeClient/API/Anilist/schema.graphql
  88. 22
      src/AnimeClient/API/CacheTrait.php
  89. 2
      src/AnimeClient/API/Enum/AnimeWatchingStatus/Anilist.php
  90. 2
      src/AnimeClient/API/Enum/AnimeWatchingStatus/Kitsu.php
  91. 2
      src/AnimeClient/API/Enum/AnimeWatchingStatus/Route.php
  92. 2
      src/AnimeClient/API/Enum/AnimeWatchingStatus/Title.php
  93. 2
      src/AnimeClient/API/Enum/MangaReadingStatus/Anilist.php
  94. 2
      src/AnimeClient/API/Enum/MangaReadingStatus/Kitsu.php
  95. 2
      src/AnimeClient/API/Enum/MangaReadingStatus/Route.php
  96. 2
      src/AnimeClient/API/Enum/MangaReadingStatus/Title.php
  97. 2
      src/AnimeClient/API/FailedResponseException.php
  98. 354
      src/AnimeClient/API/JsonAPI.php
  99. 270
      src/AnimeClient/API/Kitsu.php
  100. 15
      src/AnimeClient/API/Kitsu/.graphqlconfig

5
CHANGELOG.md

@ -1,5 +1,10 @@
# Changelog
## Version 5.1
* Added session check, so when coming back to a page, if the session is expired, the page will refresh.
* Updated logging config so that much fewer, much smaller files are generated.
* Updated Kitsu integration to use GraphQL API, reducing a lot of internal complexity.
## Version 5
* Updated PHP requirement to 7.4
* Added anime watching history view

2
RoboFile.php

@ -227,7 +227,7 @@ class RoboFile extends Tasks {
{
$this->lint();
$this->_run(['phpunit']);
$this->_run(['vendor/bin/phpunit']);
}
/**

19
app/appConf/routes.php

@ -28,6 +28,17 @@ use const Aviat\AnimeClient\{
// Maps paths to controllers and methods
// -------------------------------------------------------------------------
$routes = [
// ---------------------------------------------------------------------
// AJAX Routes
// ---------------------------------------------------------------------
'cache_purge' => [
'path' => '/cache_purge',
'action' => 'clearCache',
],
'heartbeat' => [
'path' => '/heartbeat',
'action' => 'heartbeat',
],
// ---------------------------------------------------------------------
// Anime List Routes
// ---------------------------------------------------------------------
@ -175,9 +186,9 @@ $routes = [
]
],
'person' => [
'path' => '/people/{id}',
'path' => '/people/{slug}',
'tokens' => [
'id' => SLUG_PATTERN
'slug' => SLUG_PATTERN,
]
],
'default_user_info' => [
@ -215,10 +226,6 @@ $routes = [
'file' => '[a-z0-9\-]+\.[a-z]{3,4}'
]
],
'cache_purge' => [
'path' => '/cache_purge',
'action' => 'clearCache',
],
'settings' => [
'path' => '/settings',
],

101
app/bootstrap.php

@ -10,7 +10,7 @@
* @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
* @version 5.1
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
@ -19,20 +19,24 @@ namespace Aviat\AnimeClient;
use Aura\Html\HelperLocatorFactory;
use Aura\Router\RouterContainer;
use Aura\Session\SessionFactory;
use Aviat\AnimeClient\API\{
Anilist,
Kitsu,
Kitsu\KitsuRequestBuilder
};
use Aviat\AnimeClient\API\{Anilist, Kitsu};
use Aviat\AnimeClient\Component;
use Aviat\AnimeClient\Model;
use Aviat\Banker\Teller;
use Aviat\Ion\Config;
use Aviat\Ion\Di\Container;
use Aviat\Ion\Di\ContainerInterface;
use Psr\SimpleCache\CacheInterface;
use Laminas\Diactoros\{Response, ServerRequestFactory};
use Laminas\Diactoros\ServerRequestFactory;
use Monolog\Formatter\JsonFormatter;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
use Psr\SimpleCache\CacheInterface;
if ( ! defined('APP_DIR'))
{
define('APP_DIR', __DIR__);
define('TEMPLATE_DIR', APP_DIR . '/templates');
}
// -----------------------------------------------------------------------------
// Setup DI container
@ -45,17 +49,18 @@ return static function (array $configArray = []): Container {
// -------------------------------------------------------------------------
$appLogger = new Logger('animeclient');
$appLogger->pushHandler(new RotatingFileHandler(__DIR__ . '/logs/app.log', Logger::NOTICE));
$anilistRequestLogger = new Logger('anilist-request');
$anilistRequestLogger->pushHandler(new RotatingFileHandler(__DIR__ . '/logs/anilist_request.log', Logger::NOTICE));
$appLogger->pushHandler(new RotatingFileHandler(__DIR__ . '/logs/app.log', 2, Logger::WARNING));
$container->setLogger($appLogger);
$kitsuRequestLogger = new Logger('kitsu-request');
$kitsuRequestLogger->pushHandler(new RotatingFileHandler(__DIR__ . '/logs/kitsu_request.log', Logger::NOTICE));
foreach (['anilist-request', 'kitsu-request', 'kitsu-graphql'] as $channel)
{
$logger = new Logger($channel);
$handler = new RotatingFileHandler(__DIR__ . "/logs/{$channel}.log", 2, Logger::WARNING);
$handler->setFormatter(new JsonFormatter());
$logger->pushHandler($handler);
$container->setLogger($appLogger);
$container->setLogger($anilistRequestLogger, 'anilist-request');
$container->setLogger($kitsuRequestLogger, 'kitsu-request');
$container->setLogger($logger, $channel);
}
// -------------------------------------------------------------------------
// Injected Objects
@ -74,29 +79,52 @@ 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 Request/Response Objects
// Create Component helpers
$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,
'verticalTabs' => Component\VerticalTabs::class,
];
foreach ($components as $name => $componentClass)
{
$helper->set($name, static function () use ($container, $componentClass) {
$helper = new $componentClass;
$helper->setContainer($container);
return $helper;
});
}
return $helper;
});
// Create Request Object
$container->set('request', fn () => ServerRequestFactory::fromGlobals(
$_SERVER,
$_GET,
@ -104,7 +132,6 @@ return static function (array $configArray = []): Container {
$_COOKIE,
$_FILES
));
$container->set('response', fn () => new Response);
// Create session Object
$container->set('session', fn () => (new SessionFactory())->newInstance($_COOKIE));
@ -114,7 +141,7 @@ return static function (array $configArray = []): Container {
// Models
$container->set('kitsu-model', static function(ContainerInterface $container): Kitsu\Model {
$requestBuilder = new KitsuRequestBuilder($container);
$requestBuilder = new Kitsu\RequestBuilder($container);
$requestBuilder->setLogger($container->getLogger('kitsu-request'));
$listItem = new Kitsu\ListItem();
@ -130,7 +157,7 @@ return static function (array $configArray = []): Container {
return $model;
});
$container->set('anilist-model', static function(ContainerInterface $container): Anilist\Model {
$requestBuilder = new Anilist\AnilistRequestBuilder();
$requestBuilder = new Anilist\RequestBuilder($container);
$requestBuilder->setLogger($container->getLogger('anilist-request'));
$listItem = new Anilist\ListItem();

12
app/views/anime/cover-item.php → app/templates/anime-cover.php

@ -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 ?>

6
app/templates/character.php

@ -0,0 +1,6 @@
<article class="<?= $className ?>">
<div class="name">
<a href="<?= $link ?>"><?= $name ?></a>
</div>
<a href="<?= $link ?>"><?= $picture ?></a>
</article>

74
app/templates/manga-cover.php

@ -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>

12
app/templates/media.php

@ -0,0 +1,12 @@
<article class="<?= $className ?>">
<a href="<?= $link ?>"><?= $picture ?></a>
<div class="name">
<a href="<?= $link ?>">
<?= array_shift($titles) ?>
<?php foreach ($titles as $title): ?>
<br />
<small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
</article>

5
app/templates/single-tab.php

@ -0,0 +1,5 @@
<section class="<?= $className ?>">
<?php foreach ($data as $tabName => $tabData): ?>
<?= $callback($tabData, $tabName) ?>
<?php endforeach ?>
</section>

32
app/templates/tabs.php

@ -0,0 +1,32 @@
<div class="tabs">
<?php $i = 0; foreach ($data as $tabName => $tabData): ?>
<?php if ( ! empty($tabData)): ?>
<?php $id = "{$name}-{$i}"; ?>
<input
role='tab'
aria-controls="_<?= $id ?>"
type="radio"
name="<?= $name ?>"
id="<?= $id ?>"
<?= ($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"
class="<?= $className ?>"
>
<?= $callback($tabData, $tabName) ?>
</section>
<?php if ($hasSectionWrapper): ?>
</div>
<?php endif ?>
<?php endif ?>
<?php $i++; endforeach ?>
</div>

25
app/templates/vertical-tabs.php

@ -0,0 +1,25 @@
<div class="vertical-tabs">
<?php $i = 0; ?>
<?php foreach ($data as $tabName => $tabData): ?>
<?php $id = "{$name}-{$i}" ?>
<div class="tab">
<input
type="radio"
role='tab'
aria-controls="_<?= $id ?>"
name="<?= $name ?>"
id="<?= $id ?>"
<?= $i === 0 ? 'checked="checked"' : '' ?>
/>
<label for="<?= $id ?>"><?= $tabName ?></label>
<section
id='_<?= $id ?>'
role="tabpanel"
class="<?= $className ?>"
>
<?= $callback($tabData, $tabName) ?>
</section>
</div>
<?php $i++; ?>
<?php endforeach ?>
</div>

2
app/views/anime/cover.php

@ -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>

174
app/views/anime/details.php

@ -1,6 +1,11 @@
<?php use function Aviat\AnimeClient\getLocalImg; ?>
<?php
use Aviat\AnimeClient\Kitsu;
use function Aviat\AnimeClient\getLocalImg;
?>
<main class="details fixed">
<section class="flex">
<section class="flex" unselectable>
<aside class="info">
<?= $helper->picture("images/anime/{$data['id']}-original.webp") ?>
@ -11,20 +16,33 @@
<td class="align-right">Airing Status</td>
<td><?= $data['status'] ?></td>
</tr>
<tr>
<td>Show Type</td>
<td><?= $data['show_type'] ?></td>
</tr>
<tr>
<td>Episode Count</td>
<td><?= $data['episode_count'] ?? '-' ?></td>
<td><?= (strlen($data['show_type']) > 3) ? ucfirst(strtolower($data['show_type'])) : $data['show_type'] ?></td>
</tr>
<?php if ( ! empty($data['episode_length'])): ?>
<?php if ($data['episode_count'] !== 1): ?>
<tr>
<td>Episode Count</td>
<td><?= $data['episode_count'] ?? '-' ?></td>
</tr>
<?php endif ?>
<?php if (( ! empty($data['episode_length'])) && $data['episode_count'] !== 1): ?>
<tr>
<td>Episode Length</td>
<td><?= $data['episode_length'] ?> minutes</td>
<td><?= Kitsu::friendlyTime($data['episode_length']) ?></td>
</tr>
<?php endif ?>
<?php if (isset($data['total_length'], $data['episode_count']) && $data['total_length'] > 0): ?>
<tr>
<td>Total Length</td>
<td><?= Kitsu::friendlyTime($data['total_length']) ?></td>
</tr>
<?php endif ?>
<?php if ( ! empty($data['age_rating'])): ?>
<tr>
<td>Age Rating</td>
@ -32,6 +50,18 @@
</td>
</tr>
<?php endif ?>
<?php if (count($data['links']) > 0): ?>
<tr>
<td>External Links</td>
<td>
<?php foreach ($data['links'] as $urlName => $externalUrl): ?>
<a rel='external' href="<?= $externalUrl ?>"><?= $urlName ?></a><br />
<?php endforeach ?>
</td>
</tr>
<?php endif ?>
<tr>
<td>Genres</td>
<td>
@ -40,11 +70,13 @@
</tr>
</table>
<br />
</aside>
<article class="text">
<h2 class="toph"><a rel="external" href="<?= $data['url'] ?>"><?= $data['title'] ?></a></h2>
<h2 class="toph"><?= $data['title'] ?></h2>
<?php foreach ($data['titles_more'] as $title): ?>
<h3><?= $title ?></h3>
<?php endforeach ?>
@ -99,81 +131,69 @@
<?php endif ?>
<?php if ( ! empty($data['trailer_id'])): ?>
<div class="responsive-iframe">
<h4>Trailer</h4>
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/<?= $data['trailer_id'] ?>"
frameborder="0"
allow="autoplay; encrypted-media"
allowfullscreen
></iframe>
<h4>Trailer</h4>
<iframe
width="560"
height="315"
role='img'
src="https://www.youtube.com/embed/<?= $data['trailer_id'] ?>"
allow="autoplay; encrypted-media"
allowfullscreen
tabindex='0'
title="<?= $data['title'] ?> trailer video"
></iframe>
</div>
<?php endif ?>
</article>
</section>
<?php if (count($data['characters']) > 0): ?>
<section>
<h2>Characters</h2>
<div class="tabs">
<?php $i = 0 ?>
<?php foreach ($data['characters'] as $role => $list): ?>
<input
type="radio" name="character-types"
id="character-types-<?= $i ?>" <?= ($i === 0) ? 'checked' : '' ?> />
<label for="character-types-<?= $i ?>"><?= ucfirst($role) ?></label>
<section class="content media-wrap flex flex-wrap flex-justify-start">
<?php foreach ($list as $id => $char): ?>
<?php if ( ! empty($char['image']['original'])): ?>
<article class="<?= $role === 'supporting' ? 'small-' : '' ?>character">
<?php $link = $url->generate('character', ['slug' => $char['slug']]) ?>
<div class="name">
<?= $helper->a($link, $char['name']); ?>
</div>
<a href="<?= $link ?>">
<?= $helper->picture("images/characters/{$id}.webp") ?>
</a>
</article>
<?php endif ?>
<?php endforeach ?>
</section>
<?php $i++; ?>
<?php endforeach ?>
</div>
</section>
<section>
<h2>Characters</h2>
<?= $component->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));
}) ?>
</section>
<?php endif ?>
<?php if (count($data['staff']) > 0): ?>
<section>
<h2>Staff</h2>
<div class="vertical-tabs">
<?php $i = 0; ?>
<?php foreach ($data['staff'] as $role => $people): ?>
<div class="tab">
<input type="radio" name="staff-roles" id="staff-role<?= $i ?>" <?= $i === 0 ? 'checked' : '' ?> />
<label for="staff-role<?= $i ?>"><?= $role ?></label>
<section class='content media-wrap flex flex-wrap flex-justify-start'>
<?php foreach ($people as $pid => $person): ?>
<article class='character small-person'>
<?php $link = $url->generate('person', ['id' => $person['id']]) ?>
<div class="name">
<a href="<?= $link ?>">
<?= $person['name'] ?>
</a>
</div>
<a href="<?= $link ?>">
<?= $helper->picture(getLocalImg($person['image']['original'] ?? NULL)) ?>
</a>
</article>
<?php endforeach ?>
</section>
</div>
<?php $i++; ?>
<?php endforeach ?>
</div>
</section>
<section>
<h2>Staff</h2>
<?= $component->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', ['slug' => $person['slug']]),
$helper->picture(getLocalImg($person['image']['original'] ?? NULL)),
'character small-person',
);
endforeach;
return implode('', array_map('mb_trim', $rendered));
}) ?>
</section>
<?php endif ?>
</main>

2
app/views/anime/edit.php

@ -32,7 +32,7 @@
<td>
<select name="watching_status" id="watching_status">
<?php foreach($statuses as $status_key => $status_title): ?>
<option <?php if($item['watching_status'] === $status_key): ?>selected="selected"<?php endif ?>
<option <?php if(strtolower($item['watching_status']) === $status_key): ?>selected="selected"<?php endif ?>
value="<?= $status_key ?>"><?= $status_title ?></option>
<?php endforeach ?>
</select>

9
app/views/anime/list.php

@ -1,4 +1,4 @@
<?php use function Aviat\AnimeClient\col_not_empty; ?>
<?php use function Aviat\AnimeClient\colNotEmpty; ?>
<main class="media-list">
<?php if ($auth->isAuthenticated()): ?>
<a class="bracketed" href="<?= $url->generate('anime.add.get') ?>">Add Item</a>
@ -15,7 +15,7 @@
<h3>There's nothing here!</h3>
<?php else: ?>
<?php
$hasNotes = col_not_empty($items, 'notes');
$hasNotes = colNotEmpty($items, 'notes');
?>
<table class='media-wrap'>
<thead>
@ -31,7 +31,6 @@
<th>Rated</th>
<th>Attributes</th>
<?php if($hasNotes): ?><th>Notes</th><?php endif ?>
<th>Genres</th>
</tr>
</thead>
<tbody>
@ -103,10 +102,6 @@
</ul>
</td>
<?php if ($hasNotes): ?><td><p><?= $escape->html($item['notes']) ?></p></td><?php endif ?>
<td class="align-left">
<?php sort($item['anime']->genres) ?>
<?= implode(', ', $item['anime']->genres) ?>
</td>
</tr>
<?php endforeach ?>
</tbody>

168
app/views/character/details.php

@ -1,7 +1,7 @@
<?php
use function Aviat\AnimeClient\getLocalImg;
use Aviat\AnimeClient\API\Kitsu;
use Aviat\AnimeClient\Kitsu;
?>
<main class="character-page details fixed">
@ -33,63 +33,20 @@ use Aviat\AnimeClient\API\Kitsu;
<?php if ( ! (empty($data['media']['anime']) || empty($data['media']['manga']))): ?>
<h3>Media</h3>
<div class="tabs">
<?php if ( ! empty($data['media']['anime'])): ?>
<input checked="checked" type="radio" id="media-anime" name="media-tabs" />
<label for="media-anime">Anime</label>
<section class="media-wrap content">
<?php foreach ($data['media']['anime'] as $id => $anime): ?>
<article class="media">
<?php
$link = $url->generate('anime.details', ['id' => $anime['attributes']['slug']]);
$titles = Kitsu::filterTitles($anime['attributes']);
?>
<a href="<?= $link ?>">
<?= $helper->picture("images/anime/{$id}.webp") ?>
</a>
<div class="name">
<a href="<?= $link ?>">
<?= array_shift($titles) ?>
<?php foreach ($titles as $title): ?>
<br />
<small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
</article>
<?php endforeach ?>
</section>
<?php endif ?>
<?php if ( ! empty($data['media']['manga'])): ?>
<input type="radio" id="media-manga" name="media-tabs" />
<label for="media-manga">Manga</label>
<?= $component->tabs('character-media', $data['media'], static function ($media, $mediaType) use ($url, $component, $helper) {
$rendered = [];
foreach ($media as $id => $item)
{
$rendered[] = $component->media(
array_merge([$item['title']], $item['titles']),
$url->generate("{$mediaType}.details", ['id' => $item['slug']]),
$helper->picture("images/{$mediaType}/{$item['id']}.webp")
);
}
<section class="media-wrap content">
<?php foreach ($data['media']['manga'] as $id => $manga): ?>
<article class="media">
<?php
$link = $url->generate('manga.details', ['id' => $manga['attributes']['slug']]);
$titles = Kitsu::filterTitles($manga['attributes']);
?>
<a href="<?= $link ?>">
<?= $helper->picture("images/manga/{$id}.webp") ?>
</a>
<div class="name">
<a href="<?= $link ?>">
<?= array_shift($titles) ?>
<?php foreach ($titles as $title): ?>
<br />
<small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
</article>
<?php endforeach ?>
</section>
<?php endif ?>
</div>
return implode('', array_map('mb_trim', $rendered));
}, 'media-wrap content') ?>
<?php endif ?>
<section>
@ -158,66 +115,47 @@ use Aviat\AnimeClient\API\Kitsu;
<?php if ( ! empty($vas)): ?>
<h4>Voice Actors</h4>
<div class="tabs">
<?php $i = 0; ?>
<?= $component->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', ['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));
<?php foreach ($vas as $language => $casting): ?>
<input <?= $i === 0 ? 'checked="checked"' : '' ?> type="radio" id="character-va<?= $i ?>"
name="character-vas"
/>
<label for="character-va<?= $i ?>"><?= $language ?></label>
<section class="content">
<table class="borderless max-table">
<tr>
<th>Cast Member</th>
<th>Series</th>
</tr>
<?php foreach ($casting as $cid => $c): ?>
<tr>
<td>
<article class="character">
<?php
$link = $url->generate('person', ['id' => $c['person']['id']]);
?>
<a href="<?= $link ?>">
<?= $helper->picture(getLocalImg($c['person']['image'])) ?>
<div class="name">
<?= $c['person']['name'] ?>
</div>
</a>
</article>
</td>
<td width="75%">
<section class="align-left media-wrap-flex">
<?php foreach ($c['series'] as $series): ?>
<article class="media">
<?php
$link = $url->generate('anime.details', ['id' => $series['attributes']['slug']]);
$titles = Kitsu::filterTitles($series['attributes']);
?>
<a href="<?= $link ?>">
<?= $helper->picture(getLocalImg($series['attributes']['posterImage']['small'], TRUE)) ?>
</a>
<div class="name">
<a href="<?= $link ?>">
<?= array_shift($titles) ?>
<?php foreach ($titles as $title): ?>
<br />
<small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
</article>
<?php endforeach ?>
</section>
</td>
</tr>
<?php endforeach ?>
</table>
</section>
<?php $i++ ?>
<?php endforeach ?>
</div>
$castings[] = <<<HTML
<tr>
<td>{$person}</td>
<td width="75%">
<section class="align-left media-wrap-flex">
{$media}
</section>
</td>
</tr>
HTML;
endforeach;
$languages = implode('', array_map('mb_trim', $castings));
return <<<HTML
<table class="borderless max-table">
<thead>
<tr>
<th>Cast Member</th>
<th>Series</th>
</tr>
</thead>
<tbody>{$languages}</tbody>
</table>
HTML;
}, 'content') ?>
<?php endif ?>
<?php endif ?>
</section>

2
app/views/collection/add.php

@ -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>

41
app/views/collection/cover.php

@ -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>

3
app/views/collection/edit.php

@ -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>

44
app/views/collection/list-all.php

@ -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>

3
app/views/collection/list-item.php

@ -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>

76
app/views/collection/list.php

@ -1,4 +1,4 @@
<?php use function Aviat\AnimeClient\col_not_empty; ?>
<?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 = col_not_empty($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>

0
app/views/collection/_media-select-list.php → app/views/collection/media-select-list.php

1
app/views/header.php

@ -39,5 +39,4 @@
}
}
?>
</header>

75
app/views/manga/cover.php

@ -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>

109
app/views/manga/details.php

@ -6,18 +6,46 @@
<br />
<table class="media-details">
<tr>
<td class="align-right">Publishing Status</td>
<td><?= $data['status'] ?></td>
</tr>
<tr>
<td>Manga Type</td>
<td><?= ucfirst($data['manga_type']) ?></td>
<td><?= ucfirst(strtolower($data['manga_type'])) ?></td>
</tr>
<?php if ( ! empty($data['volume_count'])): ?>
<tr>
<td>Volume Count</td>
<td><?= $data['volume_count'] ?></td>
</tr>
<?php endif ?>
<?php if ( ! empty($data['chapter_count'])): ?>
<tr>
<td>Chapter Count</td>
<td><?= $data['chapter_count'] ?></td>
</tr>
<?php endif ?>
<?php if ( ! empty($data['age_rating'])): ?>
<tr>
<td>Age Rating</td>
<td><abbr title="<?= $data['age_rating_guide'] ?>"><?= $data['age_rating'] ?></abbr>
</td>
</tr>
<?php endif ?>
<?php if (count($data['links']) > 0): ?>
<tr>
<td>External Links</td>
<td>
<?php foreach ($data['links'] as $urlName => $externalUrl): ?>
<a rel='external' href="<?= $externalUrl ?>"><?= $urlName ?></a><br />
<?php endforeach ?>
</td>
</tr>
<?php endif ?>
<tr>
<td>Genres</td>
<td>
@ -29,8 +57,8 @@
<br />
</aside>
<article class="text">
<h2 class="toph"><a rel="external" href="<?= $data['url'] ?>"><?= $data['title'] ?></a></h2>
<?php foreach ($data['titles'] as $title): ?>
<h2 class="toph"><?= $data['title'] ?></h2>
<?php foreach ($data['titles_more'] as $title): ?>
<h3><?= $title ?></h3>
<?php endforeach ?>
@ -44,61 +72,34 @@
<?php if (count($data['characters']) > 0): ?>
<h2>Characters</h2>
<div class="tabs">
<?php $i = 0 ?>
<?php foreach ($data['characters'] as $role => $list): ?>
<input
type="radio" name="character-role-tabs"
id="character-tabs<?= $i ?>" <?= $i === 0 ? 'checked' : '' ?> />
<label for="character-tabs<?= $i ?>"><?= ucfirst($role) ?></label>
<section class="content media-wrap flex flex-wrap flex-justify-start">
<?php foreach ($list as $id => $char): ?>
<?php if ( ! empty($char['image']['original'])): ?>
<article class="<?= $role === 'supporting' ? 'small-' : '' ?>character">
<?php $link = $url->generate('character', ['slug' => $char['slug']]) ?>
<div class="name">
<?= $helper->a($link, $char['name']); ?>
</div>
<a href="<?= $link ?>">
<?= $helper->picture("images/characters/{$id}.webp") ?>
</a>
</article>
<?php endif ?>