Version 5.1 - All the GraphQL #32

Closed
timw4mail wants to merge 1160 commits from develop into master
19 changed files with 267 additions and 101 deletions
Showing only changes of commit 30ee7b5601 - Show all commits

View File

@ -1,3 +1,4 @@
<?php use function Aviat\AnimeClient\col_not_empty; ?>
<main class="media-list"> <main class="media-list">
<?php if ($auth->isAuthenticated()): ?> <?php if ($auth->isAuthenticated()): ?>
<a class="bracketed" href="<?= $url->generate('anime.add.get') ?>">Add Item</a> <a class="bracketed" href="<?= $url->generate('anime.add.get') ?>">Add Item</a>
@ -13,6 +14,9 @@
<?php if (empty($items)): ?> <?php if (empty($items)): ?>
<h3>There's nothing here!</h3> <h3>There's nothing here!</h3>
<?php else: ?> <?php else: ?>
<?php
$hasNotes = col_not_empty($items, 'notes');
?>
<table class='media-wrap'> <table class='media-wrap'>
<thead> <thead>
<tr> <tr>
@ -25,8 +29,8 @@
<th>Type</th> <th>Type</th>
<th>Progress</th> <th>Progress</th>
<th>Rated</th> <th>Rated</th>
<th colspan="2">Attributes</th> <th>Attributes</th>
<th>Notes</th> <?php if($hasNotes): ?><th>Notes</th><?php endif ?>
<th>Genres</th> <th>Genres</th>
</tr> </tr>
</thead> </thead>
@ -47,9 +51,8 @@
<a href="<?= $url->generate('anime.details', ['id' => $item['anime']['slug']]) ?>"> <a href="<?= $url->generate('anime.details', ['id' => $item['anime']['slug']]) ?>">
<?= $item['anime']['title'] ?> <?= $item['anime']['title'] ?>
</a> </a>
<?php foreach ($item['anime']['titles'] as $title): ?> <br />
<br/><?= $title ?> <?= implode('<br />', $item['anime']['titles']) ?>
<?php endforeach ?>
</td> </td>
<td><?= $item['airing']['status'] ?></td> <td><?= $item['airing']['status'] ?></td>
<td><?= $item['user_rating'] ?> / 10 </td> <td><?= $item['user_rating'] ?> / 10 </td>
@ -60,41 +63,36 @@
</td> </td>
<td><?= $item['anime']['age_rating'] ?></td> <td><?= $item['anime']['age_rating'] ?></td>
<td> <td>
<ul>
<?php if ($item['rewatched'] > 0): ?>
<li>Rewatched <?= $item['rewatched'] ?> time(s)</li>
<?php endif ?>
<?php foreach(['private','rewatching'] as $attr): ?>
<?php if($item[$attr]): ?>
<li><?= ucfirst($attr); ?></li>
<?php endif ?>
<?php endforeach ?>
</ul>
</td>
<td>
<?php foreach($item['anime']['streaming_links'] as $link): ?> <?php foreach($item['anime']['streaming_links'] as $link): ?>
<?php if ($link['meta']['link'] !== FALSE): ?> <?php if ($link['meta']['link'] !== FALSE): ?>
<a href="<?= $link['link'] ?>" title="Stream '<?= $item['anime']['title'] ?>' on <?= $link['meta']['name'] ?>"> <a href="<?= $link['link'] ?>" title="Stream '<?= $item['anime']['title'] ?>' on <?= $link['meta']['name'] ?>">
<?= $helper->img("/public/images/{$link['meta']['image']}", [ <?= $helper->img("/public/images/{$link['meta']['image']}", [
'class' => 'streaming-logo', 'class' => 'small-streaming-logo',
'width' => 50, 'width' => 25,
'height' => 50, 'height' => 25,
'alt' => "{$link['meta']['name']} logo", 'alt' => "{$link['meta']['name']} logo",
]) ?> ]) ?>
</a> </a>
<?php else: ?> <?php else: ?>
<?= $helper->img("/public/images/{$link['meta']['image']}", [ <?= $helper->img("/public/images/{$link['meta']['image']}", [
'class' => 'streaming-logo', 'class' => 'small-streaming-logo',
'width' => 50, 'width' => 25,
'height' => 50, 'height' => 25,
'alt' => "{$link['meta']['name']} logo", 'alt' => "{$link['meta']['name']} logo",
]) ?> ]) ?>
<?php endif ?> <?php endif ?>
<?php endforeach ?> <?php endforeach ?>
<br />
<ul>
<?php if ($item['rewatched'] > 0): ?>li>Rewatched <?= $item['rewatched'] ?> time(s)</li><?php endif ?>
<?php foreach(['private','rewatching'] as $attr): ?>
<?php if($item[$attr]): ?><li><?= ucfirst($attr); ?></li><?php endif ?>
<?php endforeach ?>
</ul>
</td> </td>
<td> <?php if ($hasNotes): ?><td><p><?= $escape->html($item['notes']) ?></p></td><?php endif ?>
<p><?= $escape->html($item['notes']) ?></p>
</td>
<td class="align-left"> <td class="align-left">
<?php sort($item['anime']->genres) ?> <?php sort($item['anime']->genres) ?>
<?= implode(', ', $item['anime']->genres) ?> <?= implode(', ', $item['anime']->genres) ?>

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-list.php' ?> <?php include '_media-select-list.php' ?>
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@ -18,7 +18,9 @@
</div> </div>
<?php endif ?> <?php endif ?>
<div class="row"> <div class="row">
<?php if ($item['episode_count'] > 1): ?>
<div class="completion">Episodes: <?= $item['episode_count'] ?></div> <div class="completion">Episodes: <?= $item['episode_count'] ?></div>
<?php endif ?>
<div class="media_type"><?= $item['show_type'] ?></div> <div class="media_type"><?= $item['show_type'] ?></div>
<div class="age-rating"><?= $item['age_rating'] ?></div> <div class="age-rating"><?= $item['age_rating'] ?></div>
</div> </div>

View File

@ -11,7 +11,7 @@
<div class="tabs"> <div class="tabs">
<?php $i = 0; ?> <?php $i = 0; ?>
<?php foreach ($sections as $name => $items): ?> <?php foreach ($sections as $name => $items): ?>
<input <?= $i === 0 ? 'checked="checked"' : '' ?> type="radio" id="collection-tab-<?= $i ?>" name="collection-tabs" /> <input type="radio" id="collection-tab-<?= $i ?>" name="collection-tabs" />
<label for="collection-tab-<?= $i ?>"><h2><?= $name ?></h2></label> <label for="collection-tab-<?= $i ?>"><h2><?= $name ?></h2></label>
<div class="content full-height"> <div class="content full-height">
<section class="media-wrap"> <section class="media-wrap">
@ -23,17 +23,14 @@
<?php $i++; ?> <?php $i++; ?>
<?php endforeach ?> <?php endforeach ?>
<!-- All Tab --> <!-- All Tab -->
<input type='radio' id='collection-tab-<?= $i ?>' name='collection-tabs' /> <input type='radio' checked='checked' id='collection-tab-<?= $i ?>' name='collection-tabs' />
<label for='collection-tab-<?= $i ?>'><h2>All</h2></label> <label for='collection-tab-<?= $i ?>'><h2>All</h2></label>
<div class='content full-height'> <div class='content full-height'>
<?php foreach ($sections as $name => $items): ?>
<h3><?= $name ?></h3>
<section class="media-wrap"> <section class="media-wrap">
<?php foreach ($items as $item): ?> <?php foreach ($all as $item): ?>
<?php include __DIR__ . '/cover-item.php'; ?> <?php include __DIR__ . '/cover-item.php'; ?>
<?php endforeach ?> <?php endforeach ?>
</section> </section>
<?php endforeach ?>
</div> </div>
</div> </div>
<?php endif ?> <?php endif ?>

View File

@ -24,7 +24,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-list.php' ?> <?php include '_media-select-list.php' ?>
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@ -0,0 +1,44 @@
<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"><?= $item['notes'] ?></td>
<td class="align-left"><?= implode(', ', $item['genres']) ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
</div>

View File

@ -12,9 +12,9 @@
<?= ! empty($item['alternate_title']) ? ' <br /><small> ' . $item['alternate_title'] . '</small>' : '' ?> <?= ! empty($item['alternate_title']) ? ' <br /><small> ' . $item['alternate_title'] . '</small>' : '' ?>
</td> </td>
<td><?= $item['episode_count'] ?></td> <td><?= $item['episode_count'] ?></td>
<td><?= $item['episode_length'] ?></td> <td><?= ($item['episode_count'] > 1) ? $item['episode_count'] : '-' ?></td>
<td><?= $item['show_type'] ?></td> <td><?= $item['show_type'] ?></td>
<td><?= $item['age_rating'] ?></td> <td><?= $item['age_rating'] ?></td>
<?php if ($hasNotes): ?><td class="align-left"><?= $item['notes'] ?></td><?php endif ?>
<td class="align-left"><?= implode(', ', $item['genres']) ?></td> <td class="align-left"><?= implode(', ', $item['genres']) ?></td>
<td class="align-left"><?= $item['notes'] ?></td>
</tr> </tr>

View File

@ -1,3 +1,4 @@
<?php use function Aviat\AnimeClient\col_not_empty; ?>
<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>
@ -11,28 +12,26 @@
<?php $i = 0; ?> <?php $i = 0; ?>
<div class="tabs"> <div class="tabs">
<?php foreach ($sections as $name => $items): ?> <?php foreach ($sections as $name => $items): ?>
<input <?= $i === 0 ? 'checked="checked"' : '' ?> type="radio" id="collection-tab-<?= $i ?>" <?php $hasNotes = col_not_empty($items, 'notes') ?>
name="collection-tabs"/> <input type="radio" id="collection-tab-<?= $i ?>" name="collection-tabs" />
<label for="collection-tab-<?= $i ?>"><h2><?= $name ?></h2></label> <label for="collection-tab-<?= $i ?>"><h2><?= $name ?></h2></label>
<div class="content full-height"> <div class="content full-height">
<table class="full-width media-wrap"> <table class="full-width media-wrap">
<thead> <thead>
<tr> <tr>
<?php if ($auth->isAuthenticated()): ?> <?php if ($auth->isAuthenticated()): ?><td>&nbsp;</td><?php endif ?>
<td>Actions</td> <th>Title</th>
<?php endif ?> <th>Episode Count</th>
<th>Title</th> <th>Episode Length</th>
<th>Episode Count</th> <th>Show Type</th>
<th>Episode Length</th> <th>Age Rating</th>
<th>Show Type</th> <?php if ($hasNotes): ?><th>Notes</th><?php endif ?>
<th>Age Rating</th> <th>Genres</th>
<th>Genres</th> </tr>
<th>Notes</th>
</tr>
</thead> </thead>
<tbody> <tbody>
<?php foreach ($items as $item): ?> <?php foreach ($items as $item): ?>
<?php include __DIR__ . '/list-item.php' ?> <?php include 'list-item.php' ?>
<?php endforeach ?> <?php endforeach ?>
</tbody> </tbody>
</table> </table>
@ -40,34 +39,7 @@
<?php $i++ ?> <?php $i++ ?>
<?php endforeach ?> <?php endforeach ?>
<!-- All --> <!-- All -->
<input type='radio' id='collection-tab-<?= $i ?>' name='collection-tabs' /> <?php include 'list-all.php' ?>
<label for='collection-tab-<?= $i ?>'><h2>All</h2></label>
<div class="content full-height">
<?php foreach ($sections as $name => $items): ?>
<h3><?= $name ?></h3>
<table class="full-width media-wrap">
<thead>
<tr>
<?php if ($auth->isAuthenticated()): ?>
<td>Actions</td>
<?php endif ?>
<th>Title</th>
<th>Episode Count</th>
<th>Episode Length</th>
<th>Show Type</th>
<th>Age Rating</th>
<th>Genres</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<?php foreach ($items as $item): ?>
<?php include __DIR__ . '/list-item.php' ?>
<?php endforeach ?>
</tbody>
</table>
<?php endforeach; ?>
</div>
</div> </div>
<?php endif ?> <?php endif ?>
</main> </main>

View File

@ -87,10 +87,6 @@ tbody > tr:nth-child(odd) {
background: #ddd; background: #ddd;
} }
select[multiple] {
width: 100%;
}
a:hover, a:active { a:hover, a:active {
color: var(--link-hover-color) color: var(--link-hover-color)
} }
@ -877,6 +873,12 @@ aside picture, aside img {
vertical-align: middle; vertical-align: middle;
} }
.small-streaming-logo {
width: 25px;
height: 25px;
vertical-align: middle;
}
.cover-streaming-link { .cover-streaming-link {
display: none; display: none;
} }

View File

@ -0,0 +1,26 @@
<?php
use Phinx\Migration\AbstractMigration;
class AnimeCollectionCleanup extends AbstractMigration
{
public function up()
{
if ($this->hasTable('genre_anime_set_link'))
{
$this->table('genre_anime_set_link')
->rename('anime_set_genre_link')
->update();
}
}
public function down()
{
if ($this->hasTable('anime_set_genre_link'))
{
$this->table('anime_set_genre_link')
->rename('genre_anime_set_link')
->update();
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -332,4 +332,17 @@ function createPlaceholderImage ($path, ?int $width, ?int $height, $text = 'Imag
imagewebp($pngImage, $path . '/placeholder.webp'); imagewebp($pngImage, $path . '/placeholder.webp');
imagedestroy($pngImage); imagedestroy($pngImage);
}
/**
* Check that there is a value for at least one item in a collection with the specified key
*
* @param array $search
* @param string $key
* @return bool
*/
function col_not_empty(array $search, string $key): bool
{
$items = array_filter(array_column($search, $key), fn ($x) => ( ! empty($x)));
return count($items) > 0;
} }

View File

@ -101,12 +101,10 @@ final class AnimeCollection extends BaseController {
'list' => 'list' 'list' => 'list'
]; ];
$data = $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' => $data, 'sections' => $this->animeCollectionModel->getCollection(),
'genres' => $this->animeCollectionModel->getGenreList() 'all' => $this->animeCollectionModel->getFlatCollection(),
]); ]);
} }
@ -233,6 +231,13 @@ final class AnimeCollection extends BaseController {
$this->redirect('/anime-collection/view', 303); $this->redirect('/anime-collection/view', 303);
} }
/**
* Update a collection item
*
* @param $data
* @throws ContainerException
* @throws NotFoundException
*/
protected function update($data): void protected function update($data): void
{ {
if (array_key_exists('hummingbird_id', $data)) if (array_key_exists('hummingbird_id', $data))

View File

@ -69,6 +69,47 @@ final class AnimeCollection extends Collection {
return $collection; return $collection;
} }
/**
* Get the collection from the database
*
* @return array
*/
public function getFlatCollection(): array
{
if ( ! $this->validDatabase)
{
return [];
}
$query = $this->db->select('a.hummingbird_id, slug, title, alternate_title, show_type,
age_rating, episode_count, episode_length, cover_image, notes')
->from('anime_set a')
->orderBy('title')
->get();
// Add genres associated with each item
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
$genres = $this->getGenreList();
$media = $this->getMediaList();
foreach($rows as &$row)
{
$id = $row['hummingbird_id'];
$row['genres'] = array_key_exists($id, $genres)
? $genres[$id]
: [];
$row['media'] = array_key_exists($id, $media)
? $media[$id]
: [];
sort($row['genres']);
}
return $rows;
}
/** /**
* Get list of media types * Get list of media types
* *
@ -262,7 +303,7 @@ final class AnimeCollection extends Collection {
$this->db->beginTransaction(); $this->db->beginTransaction();
$this->db->where('hummingbird_id', $data['hummingbird_id']) $this->db->where('hummingbird_id', $data['hummingbird_id'])
->delete('genre_anime_set_link'); ->delete('anime_set_genre_link');
$this->db->where('hummingbird_id', $data['hummingbird_id']) $this->db->where('hummingbird_id', $data['hummingbird_id'])
->delete('anime_set_media_link'); ->delete('anime_set_media_link');
@ -361,7 +402,7 @@ final class AnimeCollection extends Collection {
try try
{ {
$this->db->select('hummingbird_id, genre') $this->db->select('hummingbird_id, genre')
->from('genre_anime_set_link gl') ->from('anime_set_genre_link gl')
->join('genres g', 'g.id=gl.genre_id', 'left'); ->join('genres g', 'g.id=gl.genre_id', 'left');
@ -402,6 +443,68 @@ final class AnimeCollection extends Collection {
return $output; return $output;
} }
/**
* Get media for anime collection items
*
* @param array $filter
* @return array
*/
public function getMediaList(array $filter = []): array
{
if ($this->validDatabase === FALSE)
{
return [];
}
$output = [];
// Catch the missing table PDOException
// so that the collection does not show an
// error by default
try
{
$this->db->select('m.type as media, hummingbird_id')
->from('anime_set_media_link ml')
->join('media m', 'm.id=ml.media_id', 'left');
if ( ! empty($filter))
{
$this->db->whereIn('hummingbird_id', $filter);
}
$query = $this->db->orderBy('hummingbird_id')
->orderBy('media')
->get();
foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $row)
{
$id = $row['hummingbird_id'];
$media = $row['media'];
// Empty genre names aren't useful
if (empty($media))
{
continue;
}
if (array_key_exists($id, $output))
{
$output[$id][] = $media;
}
else
{
$output[$id] = [$media];
}
}
}
catch (PDOException $e) {}
$this->db->resetQuery();
return $output;
}
private function updateMediaLink(string $animeId, array $media): void private function updateMediaLink(string $animeId, array $media): void
{ {
$this->db->beginTransaction(); $this->db->beginTransaction();
@ -469,7 +572,11 @@ final class AnimeCollection extends Collection {
if ( ! empty($linksToInsert)) if ( ! empty($linksToInsert))
{ {
$this->db->insertBatch('genre_anime_set_link', $linksToInsert); try
{
$this->db->insertBatch('anime_set_genre_link', $linksToInsert);
}
catch (PDOException $e) {}
} }
} }
@ -554,7 +661,7 @@ final class AnimeCollection extends Collection {
$links = []; $links = [];
$query = $this->db->select('hummingbird_id, genre_id') $query = $this->db->select('hummingbird_id, genre_id')
->from('genre_anime_set_link') ->from('anime_set_genre_link')
->get(); ->get();
foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $link) foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $link)

View File

@ -98,12 +98,12 @@ class Config extends AbstractType {
/** /**
* @var bool * @var bool
*/ */
public bool $show_anime_collection = FALSE; public $show_anime_collection = FALSE;
/** /**
* @var bool * @var bool
*/ */
public bool $show_manga_collection = FALSE; public $show_manga_collection = FALSE;
/** /**
* CSS theme: light, dark, or auto-switching * CSS theme: light, dark, or auto-switching

View File

@ -21,7 +21,7 @@ namespace Aviat\AnimeClient\Types;
*/ */
class FormItem extends AbstractType { class FormItem extends AbstractType {
/** /**
* @var string * @var string|int
*/ */
public $id; public $id;
@ -31,9 +31,9 @@ class FormItem extends AbstractType {
public ?string $anilist_item_id; public ?string $anilist_item_id;
/** /**
* @var string * @var string|int
*/ */
public ?string $mal_id; public $mal_id;
/** /**
* @var FormItemData * @var FormItemData