Get Person detail pages via GraphQL, resolves #27
timw4mail/HummingBirdAnimeClient/pipeline/pr-master This commit looks good Details

This commit is contained in:
Timothy Warren 2020-08-27 15:01:00 -04:00
parent 1b74df5269
commit e2f29c6731
19 changed files with 349 additions and 198 deletions

View File

@ -186,9 +186,8 @@ $routes = [
]
],
'person' => [
'path' => '/people/{id}{/slug}',
'path' => '/people/{slug}',
'tokens' => [
'id' => SLUG_PATTERN,
'slug' => SLUG_PATTERN,
]
],

View File

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

View File

@ -186,7 +186,7 @@ use function Aviat\AnimeClient\getLocalImg;
}
$rendered[] = $component->character(
$person['name'],
$url->generate('person', ['id' => $person['id'], 'slug' => $person['slug']]),
$url->generate('person', ['slug' => $person['slug']]),
$helper->picture(getLocalImg($person['image']['original'] ?? NULL)),
'character small-person',
);

View File

@ -120,10 +120,7 @@ use Aviat\AnimeClient\Kitsu;
foreach ($casting as $id => $c):
$person = $component->character(
$c['person']['name'],
$url->generate('person', [
'id' => $c['person']['id'],
'slug' => $c['person']['slug']
]),
$url->generate('person', ['slug' => $c['person']['slug']]),
$helper->picture(getLocalImg($c['person']['image']))
);
$medias = array_map(fn ($series) => $component->media(

View File

@ -95,7 +95,7 @@
fn($people) => implode('', array_map(
fn ($person) => $component->character(
$person['name'],
$url->generate('person', ['id' => $person['id'], 'slug' => $person['slug']]),
$url->generate('person', ['slug' => $person['slug']]),
$helper->picture("images/people/{$person['id']}.webp")
),
$people

View File

@ -1,6 +1,5 @@
<?php
use function Aviat\AnimeClient\getLocalImg;
use Aviat\AnimeClient\Kitsu;
?>
<main class="details fixed">
<section class="flex flex-no-wrap">
@ -9,6 +8,14 @@ use Aviat\AnimeClient\Kitsu;
</div>
<div>
<h2 class="toph"><?= $data['name'] ?></h2>
<?php foreach ($data['names'] as $name): ?>
<h3><?= $name ?></h3>
<?php endforeach ?>
<br />
<hr />
<div class="description">
<p><?= str_replace("\n", '</p><p>', $data['description']) ?></p>
</div>
</div>
</section>
@ -24,7 +31,6 @@ use Aviat\AnimeClient\Kitsu;
type="radio" name="staff-roles" id="staff-role<?= $i ?>" <?= $i === 0 ? 'checked' : '' ?> />
<label for="staff-role<?= $i ?>"><?= $role ?></label>
<?php foreach ($entries as $type => $casting): ?>
<?php if ($type === 'characters') continue; ?>
<?php if (isset($entries['manga'], $entries['anime'])): ?>
<h4><?= ucfirst($type) ?></h4>
<?php endif ?>
@ -32,7 +38,7 @@ use Aviat\AnimeClient\Kitsu;
<?php foreach ($casting as $sid => $series): ?>
<?php $mediaType = in_array($type, ['anime', 'manga'], TRUE) ? $type : 'anime'; ?>
<?= $component->media(
Kitsu::filterTitles($series),
$series['titles'],
$url->generate("{$mediaType}.details", ['id' => $series['slug']]),
$helper->picture("images/{$type}/{$sid}.webp")
) ?>
@ -46,7 +52,7 @@ use Aviat\AnimeClient\Kitsu;
</section>
<?php endif ?>
<?php if ( ! (empty($data['characters']['main']) || empty($data['characters']['supporting']))): ?>
<?php if ( ! empty($data['characters'])): ?>
<section>
<h3>Voice Acting Roles</h3>
<?= $component->tabs('voice-acting-roles', $data['characters'], static function ($characterList) use ($component, $helper, $url) {
@ -61,7 +67,7 @@ use Aviat\AnimeClient\Kitsu;
foreach ($item['media'] as $sid => $series)
{
$medias[] = $component->media(
Kitsu::filterTitles($series),
$series['titles'],
$url->generate('anime.details', ['id' => $series['slug']]),
$helper->picture("images/anime/{$sid}.webp")
);

View File

@ -163,7 +163,7 @@ CSS Tabs
/* text-align: center; */
}
.tabs .content {
.tabs .content, .single-tab {
display: none;
max-height: 950px;
border: 1px solid #e5e5e5;
@ -175,7 +175,14 @@ CSS Tabs
overflow: auto;
}
.tabs .content.full-height {
.single-tab {
display: block;
border: 1px solid #e5e5e5;
box-shadow: 0 48px 80px -32px rgba(0, 0, 0, 0.3);
margin-top: 1.5em;
}
.tabs .content.full-height, .single-tab.full-height {
max-height: none;
}

View File

@ -147,7 +147,8 @@ button:active {
.tabs > [type="radio"]:checked + label,
.tabs > [type="radio"]:checked + label + .content,
.vertical-tabs [type="radio"]:checked + label,
.vertical-tabs [type="radio"]:checked ~ .content {
.vertical-tabs [type="radio"]:checked ~ .content,
.single-tab {
/* border-color: #333; */
border: 0;
background: #666;

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

@ -199,27 +199,14 @@ final class Model {
/**
* Get information about a person
*
* @param string $id
* @param string $slug
* @return array
* @throws InvalidArgumentException
*/
public function getPerson(string $id): array
public function getPerson(string $slug): array
{
return $this->getCached("kitsu-person-{$id}", fn () => $this->requestBuilder->getRequest("people/{$id}", [
'query' => [
'filter' => [
'id' => $id,
],
'fields' => [
'characters' => 'canonicalName,slug,image',
'characterVoices' => 'mediaCharacter',
'anime' => 'canonicalTitle,abbreviatedTitles,titles,slug,posterImage',
'manga' => 'canonicalTitle,abbreviatedTitles,titles,slug,posterImage',
'mediaCharacters' => 'role,media,character',
'mediaStaff' => 'role,media,person',
],
'include' => 'voices.mediaCharacter.media,voices.mediaCharacter.character,staff.media',
],
return $this->getCached("kitsu-person-{$slug}", fn () => $this->requestBuilder->runQuery('PersonDetails', [
'slug' => $slug
]));
}

View File

@ -1,5 +1,5 @@
query ($id: ID!) {
findPersonById(id: $id) {
query ($slug: String!) {
findPersonBySlug(slug: $slug) {
id
description
birthday
@ -22,6 +22,36 @@ query ($id: ID!) {
canonical
localized
}
mediaStaff {
nodes {
id
role
media {
id
slug
type
posterImage {
original {
height
name
url
width
}
views {
height
name
url
width
}
}
titles {
alternatives
canonical
localized
}
}
}
}
voices {
nodes {
locale
@ -29,6 +59,7 @@ query ($id: ID!) {
role
character {
id
slug
image {
original {
height
@ -43,6 +74,7 @@ query ($id: ID!) {
}
media {
id
slug
posterImage {
original {
height

View File

@ -50,7 +50,7 @@ final class CharacterTransformer extends AbstractTransformer {
if (isset($data['media']['nodes']))
{
[$media, $castings] = $this->organizeMediaAndVoices($data['media']['nodes']);
[$media, $castings] = $this->organizeMediaAndVoices($data['media']['nodes'] ?? []);
}
return Character::from([

View File

@ -16,7 +16,7 @@
namespace Aviat\AnimeClient\API\Kitsu\Transformer;
use Aviat\AnimeClient\API\JsonAPI;
use Aviat\AnimeClient\Kitsu;
use Aviat\AnimeClient\Types\Person;
use Aviat\Ion\Transformer\AbstractTransformer;
@ -31,14 +31,17 @@ final class PersonTransformer extends AbstractTransformer {
*/
public function transform($personData): Person
{
$data = JsonAPI::organizeData($personData);
$included = JsonAPI::organizeIncludes($personData['included']);
$data = $personData['data']['findPersonBySlug'] ?? [];
$canonicalName = $data['names']['localized'][$data['names']['canonical']]
?? array_shift($data['names']['localized']);
$orgData = $this->organizeData($included);
$orgData = $this->organizeData($data);
return Person::from([
'id' => $data['id'],
'name' => $data['attributes']['name'],
'name' => $canonicalName,
'names' => array_diff($data['names']['localized'], [$canonicalName]),
'description' => $data['description']['en'] ?? '',
'characters' => $orgData['characters'],
'staff' => $orgData['staff'],
]);
@ -47,88 +50,98 @@ final class PersonTransformer extends AbstractTransformer {
protected function organizeData(array $data): array
{
$output = [
'characters' => [
'main' => [],
'supporting' => [],
],
'characters' => [],
'staff' => [],
];
if (array_key_exists('characterVoices', $data))
{
foreach ($data['characterVoices'] as $cv)
{
$mcId = $cv['relationships']['mediaCharacter']['data']['id'];
$characters = [];
$staff = [];
if ( ! array_key_exists($mcId, $data['mediaCharacters']))
if (count($data['mediaStaff']['nodes']) > 0)
{
$roles = array_unique(array_column($data['mediaStaff']['nodes'], 'role'));
foreach ($roles as $role)
{
$staff[$role] = [];
}
ksort($staff);
foreach ($data['mediaStaff']['nodes'] as $staffing)
{
$media = $staffing['media'];
$role = $staffing['role'];
$title = $media['titles']['canonical'];
$type = strtolower($media['type']);
$staff[$role][$type][$media['id']] = [
'id' => $media['id'],
'title' => $title,
'titles' => array_merge([$title], Kitsu::getFilteredTitles($media['titles'])),
'image' => [
'original' => $media['posterImage']['views'][1]['url'],
],
'slug' => $media['slug'],
];
uasort($staff[$role][$type], fn ($a, $b) => $a['title'] <=> $b['title']);
}
$output['staff'] = $staff;
}
if (count($data['voices']['nodes']) > 0)
{
foreach ($data['voices']['nodes'] as $voicing)
{
$character = $voicing['mediaCharacter']['character'];
$charId = $character['id'];
$rawMedia = $voicing['mediaCharacter']['media'];
$role = strtolower($voicing['mediaCharacter']['role']);
$media = [
'id' => $rawMedia['id'],
'slug' => $rawMedia['slug'],
'titles' => array_merge(
[$rawMedia['titles']['canonical']],
Kitsu::getFilteredTitles($rawMedia['titles']),
),
];
if ( ! isset($characters[$role][$charId]))
{
continue;
if ( ! array_key_exists($role, $characters))
{
$characters[$role] = [];
}
$characters[$role][$charId] = [
'character' => [
'id' => $character['id'],
'slug' => $character['slug'],
'image' => [
'original' => $character['image']['original']['url'],
],
'canonicalName' => $character['names']['canonical'],
],
'media' => [
$media['id'] => $media
],
];
}
else
{
$characters[$role][$charId]['media'][$media['id']] = $media;
}
$mc = $data['mediaCharacters'][$mcId];
$role = $mc['role'];
$charId = $mc['relationships']['character']['data']['id'];
$mediaId = $mc['relationships']['media']['data']['id'];
$existingMedia = array_key_exists($charId, $output['characters'][$role])
? $output['characters'][$role][$charId]['media']
: [];
$relatedMedia = [
$mediaId => $data['anime'][$mediaId],
];
$includedMedia = array_replace_recursive($existingMedia, $relatedMedia);
uasort($includedMedia, static function ($a, $b) {
return $a['canonicalTitle'] <=> $b['canonicalTitle'];
});
$character = $data['characters'][$charId];
$output['characters'][$role][$charId] = [
'character' => $character,
'media' => $includedMedia,
];
}
}
if (array_key_exists('mediaStaff', $data))
{
foreach ($data['mediaStaff'] as $rid => $role)
{
$roleName = $role['role'];
$mediaType = $role['relationships']['media']['data']['type'];
$mediaId = $role['relationships']['media']['data']['id'];
$media = $data[$mediaType][$mediaId];
$output['staff'][$roleName][$mediaType][$mediaId] = $media;
}
}
uasort($output['characters']['main'], static function ($a, $b) {
return $a['character']['canonicalName'] <=> $b['character']['canonicalName'];
});
uasort($output['characters']['supporting'], static function ($a, $b) {
return $a['character']['canonicalName'] <=> $b['character']['canonicalName'];
});
ksort($output['staff']);
foreach ($output['staff'] as $role => &$media)
{
if (array_key_exists('anime', $media))
{
uasort($media['anime'], static function ($a, $b) {
return $a['canonicalTitle'] <=> $b['canonicalTitle'];
});
uasort(
$characters[$role][$charId]['media'],
fn ($a, $b) => $a['titles'][0] <=> $b['titles'][0]
);
}
if (array_key_exists('manga', $media))
{
uasort($media['manga'], static function ($a, $b) {
return $a['canonicalTitle'] <=> $b['canonicalTitle'];
});
}
krsort($characters);
$output['characters'] = $characters;
}
return $output;

View File

@ -402,10 +402,9 @@ type Anime implements Episodic & Media & WithTimestamps {
youtubeTrailerVideoId: String
}
type AnimeAmountConsumed implements AmountConsumed & WithTimestamps {
type AnimeAmountConsumed implements AmountConsumed {
"Total media completed atleast once."
completed: Int!
createdAt: ISO8601DateTime!
id: ID!
"Total amount of media."
media: Int!
@ -417,13 +416,11 @@ type AnimeAmountConsumed implements AmountConsumed & WithTimestamps {
time: Int!
"Total progress of library including reconsuming."
units: Int!
updatedAt: ISO8601DateTime!
}
type AnimeCategoryBreakdown implements CategoryBreakdown & WithTimestamps {
type AnimeCategoryBreakdown implements CategoryBreakdown {
"A Map of category_id -> count for all categories present on the library entries"
categories: Map!
createdAt: ISO8601DateTime!
id: ID!
"The profile related to the user for this stat."
profile: Profile!
@ -431,7 +428,6 @@ type AnimeCategoryBreakdown implements CategoryBreakdown & WithTimestamps {
recalculatedAt: ISO8601Date!
"The total amount of library entries."
total: Int!
updatedAt: ISO8601DateTime!
}
"The connection type for Anime."
@ -468,13 +464,12 @@ type AnimeEdge {
node: Anime
}
type AnimeMutation implements WithTimestamps {
type AnimeMutation {
"Create an Anime."
create(
"Create an Anime."
input: AnimeCreateInput!
): AnimeCreatePayload
createdAt: ISO8601DateTime!
"Delete an Anime."
delete(
"Delete an Anime."
@ -485,7 +480,6 @@ type AnimeMutation implements WithTimestamps {
"Update an Anime."
input: AnimeUpdateInput!
): AnimeUpdatePayload
updatedAt: ISO8601DateTime!
}
"Autogenerated return type of AnimeUpdate"
@ -739,6 +733,20 @@ type EpisodeConnection {
totalCount: Int!
}
"Autogenerated return type of EpisodeCreate"
type EpisodeCreatePayload {
episode: Episode
"Graphql Errors"
errors: [Generic!]
}
"Autogenerated return type of EpisodeDelete"
type EpisodeDeletePayload {
episode: GenericDelete
"Graphql Errors"
errors: [Generic!]
}
"An edge in a connection."
type EpisodeEdge {
"A cursor for use in pagination."
@ -747,6 +755,31 @@ type EpisodeEdge {
node: Episode
}
type EpisodeMutation {
"Create an Episode."
create(
"Create an Episode"
input: EpisodeCreateInput!
): EpisodeCreatePayload
"Delete an Episode."
delete(
"Delete an Episode"
input: GenericDeleteInput!
): EpisodeDeletePayload
"Update an Episode."
update(
"Update an Episode"
input: EpisodeUpdateInput!
): EpisodeUpdatePayload
}
"Autogenerated return type of EpisodeUpdate"
type EpisodeUpdatePayload {
episode: Episode
"Graphql Errors"
errors: [Generic!]
}
"Favorite media, characters, and people for a user"
type Favorite implements WithTimestamps {
createdAt: ISO8601DateTime!
@ -778,30 +811,24 @@ type FavoriteEdge {
node: Favorite
}
type Generic implements Base & WithTimestamps {
type Generic implements Base {
"The error code."
code: String
createdAt: ISO8601DateTime!
"A description of the error"
message: String!
"Which input value this error came from"
path: [String!]
updatedAt: ISO8601DateTime!
}
type GenericDelete implements WithTimestamps {
createdAt: ISO8601DateTime!
type GenericDelete {
id: ID!
updatedAt: ISO8601DateTime!
}
type Image implements WithTimestamps {
type Image {
"A blurhash-encoded version of this image"
blurhash: String
createdAt: ISO8601DateTime!
"The original image"
original: ImageView!
updatedAt: ISO8601DateTime!
"The various generated views of this image"
views(names: [String!]): [ImageView!]!
}
@ -820,7 +847,7 @@ type ImageView implements WithTimestamps {
}
"The user library filterable by media_type and status"
type Library implements WithTimestamps {
type Library {
"All Library Entries for a specific Media"
all(
"Returns the elements in the list that come after the specified cursor."
@ -846,7 +873,6 @@ type Library implements WithTimestamps {
last: Int,
mediaType: media_type!
): LibraryEntryConnection!
createdAt: ISO8601DateTime!
"Library Entries for a specific Media filtered by the current status"
current(
"Returns the elements in the list that come after the specified cursor."
@ -895,7 +921,6 @@ type Library implements WithTimestamps {
last: Int,
mediaType: media_type!
): LibraryEntryConnection!
updatedAt: ISO8601DateTime!
}
"Information about a specific media entry for a user"
@ -984,13 +1009,12 @@ type LibraryEntryEdge {
node: LibraryEntry
}
type LibraryEntryMutation implements WithTimestamps {
type LibraryEntryMutation {
"Create a library entry"
create(
"Create a Library Entry"
input: LibraryEntryCreateInput!
): LibraryEntryCreatePayload
createdAt: ISO8601DateTime!
"Delete a library entry"
delete(
"Delete Library Entry"
@ -1001,17 +1025,36 @@ type LibraryEntryMutation implements WithTimestamps {
"Update Library Entry"
input: LibraryEntryUpdateInput!
): LibraryEntryUpdatePayload
"Update a library entry status by id"
"Update library entry progress by id"
updateProgressById(
"Update library entry progress by id"
input: UpdateProgressByIdInput!
): LibraryEntryUpdateProgressByIdPayload
"Update library entry progress by media"
updateProgressByMedia(
"Update library entry progress by media"
input: UpdateProgressByMediaInput!
): LibraryEntryUpdateProgressByMediaPayload
"Update library entry rating by id"
updateRatingById(
"Update library entry rating by id"
input: UpdateRatingByIdInput!
): LibraryEntryUpdateRatingByIdPayload
"Update library entry rating by media"
updateRatingByMedia(
"Update library entry rating by media"
input: UpdateRatingByMediaInput!
): LibraryEntryUpdateRatingByMediaPayload
"Update library entry status by id"
updateStatusById(
"Update a library entry status by id"
"Update library entry status by id"
input: UpdateStatusByIdInput!
): LibraryEntryUpdateStatusByIdPayload
"Update a library entry status by media"
"Update library entry status by media"
updateStatusByMedia(
"Update a library entry status by media"
"Update library entry status by media"
input: UpdateStatusByMediaInput!
): LibraryEntryUpdateStatusByMediaPayload
updatedAt: ISO8601DateTime!
}
"Autogenerated return type of LibraryEntryUpdate"
@ -1021,6 +1064,34 @@ type LibraryEntryUpdatePayload {
libraryEntry: LibraryEntry
}
"Autogenerated return type of LibraryEntryUpdateProgressById"
type LibraryEntryUpdateProgressByIdPayload {
"Graphql Errors"
errors: [Generic!]
libraryEntry: LibraryEntry
}
"Autogenerated return type of LibraryEntryUpdateProgressByMedia"
type LibraryEntryUpdateProgressByMediaPayload {
"Graphql Errors"
errors: [Generic!]
libraryEntry: LibraryEntry
}
"Autogenerated return type of LibraryEntryUpdateRatingById"
type LibraryEntryUpdateRatingByIdPayload {
"Graphql Errors"
errors: [Generic!]
libraryEntry: LibraryEntry
}
"Autogenerated return type of LibraryEntryUpdateRatingByMedia"
type LibraryEntryUpdateRatingByMediaPayload {
"Graphql Errors"
errors: [Generic!]
libraryEntry: LibraryEntry
}
"Autogenerated return type of LibraryEntryUpdateStatusById"
type LibraryEntryUpdateStatusByIdPayload {
"Graphql Errors"
@ -1210,10 +1281,9 @@ type Manga implements Media & WithTimestamps {
volumeCount: Int
}
type MangaAmountConsumed implements AmountConsumed & WithTimestamps {
type MangaAmountConsumed implements AmountConsumed {
"Total media completed atleast once."
completed: Int!
createdAt: ISO8601DateTime!
id: ID!
"Total amount of media."
media: Int!
@ -1223,13 +1293,11 @@ type MangaAmountConsumed implements AmountConsumed & WithTimestamps {
recalculatedAt: ISO8601Date!
"Total progress of library including reconsuming."
units: Int!
updatedAt: ISO8601DateTime!
}
type MangaCategoryBreakdown implements CategoryBreakdown & WithTimestamps {
type MangaCategoryBreakdown implements CategoryBreakdown {
"A Map of category_id -> count for all categories present on the library entries"
categories: Map!
createdAt: ISO8601DateTime!
id: ID!
"The profile related to the user for this stat."
profile: Profile!
@ -1237,7 +1305,6 @@ type MangaCategoryBreakdown implements CategoryBreakdown & WithTimestamps {
recalculatedAt: ISO8601Date!
"The total amount of library entries."
total: Int!
updatedAt: ISO8601DateTime!
}
"The connection type for Manga."
@ -1470,12 +1537,11 @@ type MediaStaffEdge {
node: MediaStaff
}
type Mutation implements WithTimestamps {
type Mutation {
anime: AnimeMutation
createdAt: ISO8601DateTime!
episode: EpisodeMutation
libraryEntry: LibraryEntryMutation
pro: ProMutation!
updatedAt: ISO8601DateTime!
}
"Information about pagination in a connection."
@ -1490,11 +1556,7 @@ type PageInfo {
startCursor: String
}
"""
A Voice Actor, Director, Animator, or other person who works in the creation and\
localization of media
"""
"A Voice Actor, Director, Animator, or other person who works in the creation and localization of media"
type Person implements WithTimestamps {
"The day when this person was born"
birthday: Date
@ -1504,6 +1566,17 @@ type Person implements WithTimestamps {
id: ID!
"An image of the person"
image: Image
"Information about the person working on specific media"
mediaStaff(
"Returns the elements in the list that come after the specified cursor."
after: String,
"Returns the elements in the list that come before the specified cursor."
before: String,
"Returns the first _n_ elements from the list."
first: Int,
"Returns the last _n_ elements from the list."
last: Int
): MediaStaffConnection
"The primary name of this person."
name: String!
"The name of this person in various languages"
@ -1596,8 +1669,7 @@ type PostEdge {
node: Post
}
type ProMutation implements WithTimestamps {
createdAt: ISO8601DateTime!
type ProMutation {
"Set the user's discord tag"
setDiscord(
"Your discord tag (Name#1234)"
@ -1610,7 +1682,6 @@ type ProMutation implements WithTimestamps {
): SetMessagePayload
"End the user's pro subscription"
unsubscribe: UnsubscribePayload
updatedAt: ISO8601DateTime!
}
"A subscription to Kitsu PRO"
@ -1719,11 +1790,7 @@ type Profile implements WithTimestamps {
"Returns the last _n_ elements from the list."
last: Int
): MediaReactionConnection!
"""
A non-unique publicly visible name for the profile.
Minimum of 3 characters and any valid Unicode character
"""
"A non-unique publicly visible name for the profile. Minimum of 3 characters and any valid Unicode character"
name: String!
"Post pinned to the user profile"
pinnedPost: Post
@ -1787,17 +1854,15 @@ type ProfileEdge {
}
"The different types of user stats that we calculate."
type ProfileStats implements WithTimestamps {
type ProfileStats {
"The total amount of anime you have watched over your whole life."
animeAmountConsumed: AnimeAmountConsumed!
"The breakdown of the different categories related to the anime you have completed"
animeCategoryBreakdown: AnimeCategoryBreakdown!
createdAt: ISO8601DateTime!
"The total amount of manga you ahve read over your whole life."
mangaAmountConsumed: MangaAmountConsumed!
"The breakdown of the different categories related to the manga you have completed"
mangaCategoryBreakdown: MangaCategoryBreakdown!
updatedAt: ISO8601DateTime!
}
type Query {
@ -2017,13 +2082,11 @@ type QuoteLineEdge {
}
"Information about a user session"
type Session implements WithTimestamps {
type Session {
"The account associated with this session"
account: Account
createdAt: ISO8601DateTime!
"The profile associated with this session"
profile: Profile
updatedAt: ISO8601DateTime!
}
"Autogenerated return type of SetDiscord"
@ -2141,17 +2204,15 @@ type StreamingLinkEdge {
node: StreamingLink
}
type TitlesList implements WithTimestamps {
type TitlesList {
"A list of additional, alternative, abbreviated, or unofficial titles"
alternatives: [String!]
"The official or de facto international title"
canonical: String
"The locale code that identifies which title is used as the canonical title"
canonicalLocale: String
createdAt: ISO8601DateTime!
"The list of localized titles keyed by locale"
localized(locales: [String!]): Map!
updatedAt: ISO8601DateTime!
}
"Autogenerated return type of Unsubscribe"
@ -2424,6 +2485,27 @@ input AnimeUpdateInput {
youtubeTrailerVideoId: String
}
input EpisodeCreateInput {
description: Map
length: Int
mediaId: ID!
mediaType: media_type!
number: Int!
releasedAt: Date
thumbnailImage: Upload
titles: TitlesListInput!
}
input EpisodeUpdateInput {
description: Map
id: ID!
length: Int
number: Int
releasedAt: Date
thumbnailImage: Upload
titles: TitlesListInput
}
input GenericDeleteInput {
id: ID!
}
@ -2464,6 +2546,30 @@ input TitlesListInput {
localized: Map
}
input UpdateProgressByIdInput {
id: ID!
progress: Int!
}
input UpdateProgressByMediaInput {
mediaId: ID!
mediaType: media_type!
progress: Int!
}
input UpdateRatingByIdInput {
id: ID!
"A number between 2 - 20"
rating: Int!
}
input UpdateRatingByMediaInput {
mediaId: ID!
mediaType: media_type!
"A number between 2 - 20"
rating: Int!
}
input UpdateStatusByIdInput {
id: ID!
status: LibraryEntryStatus!

View File

@ -38,6 +38,17 @@ final class Tabs {
bool $hasSectionWrapper = false
): string
{
if (count($tabData) < 2)
{
return $this->render('single-tab.php', [
'name' => $name,
'data' => $tabData,
'callback' => $cb,
'className' => $className . ' single-tab',
'hasSectionWrapper' => $hasSectionWrapper,
]);
}
return $this->render('tabs.php', [
'name' => $name,
'data' => $tabData,

View File

@ -50,15 +50,14 @@ final class People extends BaseController {
/**
* Show information about a person
*
* @param string $id
* @param string|null $slug
* @param string $slug
* @return void
* @throws ContainerException
* @throws NotFoundException
*/
public function index(string $id, ?string $slug = NULL): void
public function index(string $slug): void
{
$rawData = $this->model->getPerson($id, $slug);
$rawData = $this->model->getPerson($slug);
$data = (new PersonTransformer())->transform($rawData)->toArray();
if (( ! array_key_exists('data', $rawData)) || empty($rawData['data']))

View File

@ -20,28 +20,16 @@ namespace Aviat\AnimeClient\Types;
* Type representing a person for display
*/
final class Person extends AbstractType {
/**
* @var string
*/
public $id;
/**
* @var string
*/
public ?string $name;
/**
* @var Characters
*/
public ?Characters $characters;
public array $names = [];
public ?string $description;
public array $characters = [];
/**
* @var array
*/
public array $staff = [];
public function setCharacters($characters): void
{
$this->characters = Characters::from($characters);
}
}