feat: viewAnime improvements

added recommendations
added friends anime status
added an option to show more/less relations and recommendations
improved reliability of quitting using ESC
improved autofocus on title search on home page
removed some unused CSS
This commit is contained in:
ThaUnknown 2022-07-06 00:29:06 +02:00
parent ee530a5d9a
commit 27ac5cf0e8
5 changed files with 141 additions and 100 deletions

View file

@ -12,10 +12,29 @@
$view = null
}
$: media = $view
let modal
$: media && modal?.focus()
$: !$trailer && modal?.focus()
let following = null
async function updateFollowing (media) {
if (media) {
following = null
following = (await alRequest({ method: 'Following', id: media.id })).data?.Page?.mediaList
}
}
$: updateFollowing(media)
$: maxPlayEp = getMediaMaxEp($view || {}, true)
function checkClose ({ keyCode }) {
if (keyCode === 27) close()
}
const statusMap = {
CURRENT: 'Watching',
PLANNING: 'Planning',
COMPLETED: 'Completed',
DROPPED: 'Dropped',
PAUSED: 'Paused',
REPEATING: 'Re-Watching'
}
const detailsMap = [
{ property: 'episode', label: 'Airing', icon: 'schedule', custom: 'property' },
{ property: 'genres', label: 'Genres', icon: 'theater_comedy' },
@ -93,9 +112,17 @@
function openInBrowser (url) {
window.IPC.emit('open', url)
}
let showMoreRelations = false
function toggleRelations () {
showMoreRelations = !showMoreRelations
}
let showMoreRecommendations = false
function toggleRecommendations () {
showMoreRecommendations = !showMoreRecommendations
}
</script>
<div class="modal modal-full" class:show={media} on:keydown={checkClose} tabindex="-1">
<div class="modal modal-full" class:show={media} on:keydown={checkClose} tabindex="-1" bind:this={modal}>
{#if media}
<div class="h-full modal-content bg-very-dark p-0 overflow-y-auto">
<button class="close pointer z-30 bg-dark shadow-lg top-20 right-0" type="button" on:click={close}> &times; </button>
@ -213,13 +240,17 @@
<div class="container-xl bg-very-dark z-10">
<div class="row p-20 px-xl-0 flex-column-reverse flex-md-row">
<div class="col-md-9 px-20">
<h1 class="title font-weight-bold text-white">Sypnosis</h1>
<h1 class="title font-weight-bold text-white">Synopsis</h1>
<div class="font-size-16 pr-15">
{@html media.description}
</div>
{#if media.relations?.edges?.filter(({ node }) => node.type === 'ANIME').length}
<span class="d-flex align-items-end pointer text-decoration-none mt-20 pt-20" on:click={toggleRelations}>
<h1 class="font-weight-bold text-white">Relations</h1>
<h6 class="ml-auto font-size-12 more text-muted">{showMoreRelations ? 'Show Less' : 'Show More'}</h6>
</span>
<div class="d-flex text-capitalize flex-wrap pt-20 justify-center">
{#each media.relations?.edges.filter(({ node }) => node.type === 'ANIME') as { relationType, node }}
{#each media.relations?.edges.filter(({ node }) => node.type === 'ANIME').slice(0, showMoreRelations ? 100 : 4) as { relationType, node }}
<div class="w-150 mx-15 mb-10 rel pointer" on:click={async () => { $view = null; $view = (await alRequest({ method: 'SearchIDSingle', id: node.id })).data.Media }}>
<img loading="lazy" src={node.coverImage.medium || ''} alt="cover" class="cover-img w-full h-200 rel-img" />
<div class="pt-5">{relationType.replace(/_/g, ' ').toLowerCase()}</div>
@ -251,10 +282,24 @@
</tbody>
</table>
{/if}
{#if media.recommendations?.edges?.length}
<span class="d-flex align-items-end pointer text-decoration-none mt-20 pt-20" on:click={toggleRecommendations}>
<h1 class="font-weight-bold text-white">Recommendations</h1>
<h6 class="ml-auto font-size-12 more text-muted">{showMoreRecommendations ? 'Show Less' : 'Show More'}</h6>
</span>
<div class="d-flex text-capitalize flex-wrap pt-20 justify-center">
{#each media.recommendations.edges.slice(0, showMoreRecommendations ? 100 : 4) as { node }}
<div class="w-150 mx-15 mb-10 rel pointer" on:click={async () => { $view = null; $view = (await alRequest({ method: 'SearchIDSingle', id: node.mediaRecommendation.id })).data.Media }}>
<img loading="lazy" src={node.mediaRecommendation.coverImage.medium || ''} alt="cover" class="cover-img w-full h-200 rel-img" />
<h5 class="font-weight-bold text-white">{node.mediaRecommendation.title.userPreferred}</h5>
</div>
{/each}
</div>
{/if}
</div>
<div class="col-md-3 px-sm-0 px-20">
<h1 class="title font-weight-bold text-white">Details</h1>
<div class="card m-0 px-20 py-10 d-flex flex-md-column flex-row overflow-x-auto text-capitalize" id="viewDetails">
<div class="card m-0 px-20 py-10 d-flex flex-md-column flex-row overflow-x-auto text-capitalize">
{#each detailsMap as detail}
{@const property = getProperty(detail.property, media)}
{#if property}
@ -278,6 +323,19 @@
{/if}
{/each}
</div>
{#if following?.length}
<h2 class="font-weight-bold text-white mt-20">Following</h2>
<div class="card m-0 px-20 pt-15 pb-5 flex-column">
{#each following as friend}
<div class="d-flex align-items-center w-full pb-10 px-10">
<img src={friend.user.avatar.medium} alt="avatar" class="w-30 h-30 img-fluid rounded" />
<span class="my-0 pl-10 mr-auto text-truncate">{friend.user.name}</span>
<span class="my-0 px-10 text-capitalize">{statusMap[friend.status]}</span>
<span class="material-icons pointer text-primary font-size-18" on:click={() => window.IPC.emit('open', 'https://anilist.co/user/' + friend.user.name)}> open_in_new </span>
</div>
{/each}
</div>
{/if}
</div>
</div>
</div>
@ -286,6 +344,9 @@
</div>
<style>
.more:hover {
color: var(--dm-link-text-color-hover) !important;
}
.banner {
background: no-repeat center center;
background-size: cover;
@ -344,4 +405,10 @@
border: none;
margin-right: 0.6rem;
}
.w-30 {
width: 3rem
}
.h-30 {
height: 3rem
}
</style>

View file

@ -2,15 +2,17 @@
import { getContext } from 'svelte'
const url = getContext('trailer')
function close() {
let modal
function close () {
$url = null
}
function checkClose({ keyCode }) {
if (keyCode == 27) close()
function checkClose ({ keyCode }) {
if (keyCode === 27) close()
}
$: $url && modal?.focus()
</script>
<div class="modal" class:show={$url} on:keydown={checkClose} tabindex="-1">
<div class="modal" class:show={$url} on:keydown={checkClose} tabindex="-1" bind:this={modal}>
{#if $url}
<div class="modal-dialog" role="document" on:click|self={close}>
<div class="modal-content w-three-quarter h-full bg-transparent d-flex justify-content-center flex-column">

View file

@ -1428,95 +1428,4 @@
filter: invert(0.84);
padding-top: 2rem;
}
/* Radio debloat for halfmoon */
.custom-radio {
display: flex;
flex-direction: column;
}
.custom-radio label {
position: relative;
}
.custom-radio input[type='radio']:hover + label:before {
background-color: var(--lm-radio-bg-color-hover);
border-color: var(--lm-radio-border-color-hover);
}
.custom-radio input[type='radio']:focus + label:before {
border-color: var(--lm-radio-border-color-focus);
-moz-box-shadow: var(--lm-radio-box-shadow-focus);
-webkit-box-shadow: var(--lm-radio-box-shadow-focus);
box-shadow: var(--lm-radio-box-shadow-focus);
}
.custom-radio input[type='radio']:checked + label:before {
background-color: var(--lm-radio-bg-color-checked);
border-color: var(--lm-radio-border-color-checked);
}
.custom-radio input[type='radio']:checked:focus + label:before {
border-color: var(--lm-radio-border-color-checked-focus);
-moz-box-shadow: var(--lm-radio-box-shadow-checked-focus);
-webkit-box-shadow: var(--lm-radio-box-shadow-checked-focus);
box-shadow: var(--lm-radio-box-shadow-checked-focus);
}
.dark-mode .custom-radio label:before {
background-color: var(--dm-radio-bg-color);
border-color: var(--dm-radio-border-color);
}
.dark-mode .custom-radio input[type='radio']:hover + label:before {
background-color: var(--dm-radio-bg-color-hover);
border-color: var(--dm-radio-border-color-hover);
}
.dark-mode .custom-radio input[type='radio']:focus + label:before {
border-color: var(--dm-radio-border-color-focus);
-moz-box-shadow: var(--dm-radio-box-shadow-focus);
-webkit-box-shadow: var(--dm-radio-box-shadow-focus);
box-shadow: var(--dm-radio-box-shadow-focus);
}
.dark-mode .custom-radio input[type='radio']:checked + label:before {
background-color: var(--dm-radio-bg-color-checked);
border-color: var(--dm-radio-border-color-checked);
}
.dark-mode .custom-radio input[type='radio']:checked:focus + label:before {
border-color: var(--dm-radio-border-color-checked-focus);
-moz-box-shadow: var(--dm-radio-box-shadow-checked-focus);
-webkit-box-shadow: var(--dm-radio-box-shadow-checked-focus);
box-shadow: var(--dm-radio-box-shadow-checked-focus);
}
.custom-radio label:after {
content: '';
position: absolute;
display: none;
top: var(--radio-checkmark-top);
left: var(--radio-checkmark-left);
width: var(--radio-checkmark-width-height);
height: var(--radio-checkmark-width-height);
background-color: var(--lm-radio-checkmark-color);
border-radius: var(--radio-checkmark-border-radius);
}
.custom-radio input[type='radio']:checked + label:after {
display: block;
}
.custom-radio input[type='radio']:disabled + label {
opacity: 0.6;
cursor: not-allowed;
}
.custom-radio input[type='radio']:disabled + label:before,
.custom-radio input[type='radio']:hover:disabled + label:before {
background-color: var(--lm-radio-bg-color);
border-color: var(--lm-radio-border-color);
}
.custom-radio input[type='radio']:disabled:checked + label:before,
.custom-radio input[type='radio']:hover:disabled:checked + label:before {
background-color: var(--lm-radio-bg-color-checked);
border-color: var(--lm-radio-border-color-checked);
}
.dark-mode .custom-radio input[type='radio']:disabled + label:before,
.dark-mode .custom-radio input[type='radio']:hover:disabled + label:before {
background-color: var(--dm-radio-bg-color);
border-color: var(--dm-radio-border-color);
}
.dark-mode .custom-radio input[type='radio']:disabled:checked + label:before,
.dark-mode .custom-radio input[type='radio']:hover:disabled:checked + label:before {
background-color: var(--dm-radio-bg-color-checked);
border-color: var(--dm-radio-border-color-checked);
}
</style>

View file

@ -1,10 +1,16 @@
<script>
import { getContext } from 'svelte'
export let search
export let current
export let media = null
export let loadCurrent
let searchTimeout = null
let searchTextInput
const view = getContext('view')
$: !$view && searchTextInput?.focus()
function searchClear () {
search = {
format: '',
@ -14,6 +20,7 @@
status: ''
}
current = null
searchTextInput?.focus()
}
function input () {
if (!searchTimeout) {
@ -47,6 +54,7 @@
<span class="input-group-text d-flex material-icons bg-dark pr-0 font-size-18">search</span>
</div>
<!-- svelte-ignore missing-declaration -->
<!-- svelte-ignore a11y-autofocus -->
<input
on:input={({ target }) => {
queueMicrotask(() => {
@ -54,6 +62,8 @@
input()
})
}}
bind:this={searchTextInput}
autofocus
type="search"
class="form-control bg-dark border-left-0 shadow-none text-capitalize"
autocomplete="off"
@ -102,7 +112,13 @@
<option value="SUMMER">Summer</option>
<option value="FALL">Fall</option>
</select>
<input type="number" placeholder="Any" min="1940" max="2100" class="form-control bg-dark shadow-none" bind:value={search.year} />
<input type="number" placeholder="Any" min="1940" max="2100" list="search-year" class="form-control bg-dark shadow-none" bind:value={search.year} />
<datalist id="search-year">
{#each Array(new Date().getFullYear() - 1940 + 2) as _, i}
{@const year = new Date().getFullYear() + 2 - i}
<option>{year}</option>
{/each}
</datalist>
</div>
</div>
<div class="col p-10 d-flex flex-column justify-content-end">

View file

@ -221,6 +221,21 @@ relations {
}
}
}
},
recommendations {
edges {
node {
mediaRecommendation {
id,
title {
userPreferred
},
coverImage {
medium
}
}
}
}
}`
if (opts.status) variables.status = opts.status
if (localStorage.getItem('ALtoken')) options.headers.Authorization = alToken
@ -366,6 +381,38 @@ query ($page: Int, $perPage: Int, $sort: [MediaSort], $type: MediaType, $search:
deleted
}
}`
break
} case 'Following': {
variables.id = opts.id
query = /* js */`
query($id: Int) {
Page {
pageInfo {
total,
perPage,
currentPage,
lastPage,
hasNextPage
},
mediaList(mediaId: $id, isFollowing: true, sort: UPDATED_TIME_DESC) {
id,
status,
score,
progress,
user {
id,
name,
avatar {
medium
},
mediaListOptions {
scoreFormat
}
}
}
}
}`
break
}
}
options.body = JSON.stringify({