mirror of
https://github.com/ThaUnknown/miru.git
synced 2026-04-20 05:42:04 +00:00
feat: viewAnime improvements: better performance, scoring, relations, grey watched episodes fix #160
This commit is contained in:
parent
5f044564d2
commit
135a0caba1
4 changed files with 108 additions and 43 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "Miru",
|
||||
"version": "2.5.1",
|
||||
"version": "2.6.0",
|
||||
"author": "ThaUnknown_ <ThaUnknown@users.noreply.github.com>",
|
||||
"main": "src/index.js",
|
||||
"homepage": "https://github.com/ThaUnknown/miru#readme",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@
|
|||
function close () {
|
||||
$view = null
|
||||
}
|
||||
$: media = $view
|
||||
$: maxPlayEp = getMediaMaxEp($view || {}, true)
|
||||
function checkClose ({ keyCode }) {
|
||||
if (keyCode === 27) close()
|
||||
}
|
||||
|
|
@ -31,9 +33,9 @@
|
|||
function getCustomProperty (detail, media) {
|
||||
if (detail.property === 'episodes') {
|
||||
if (media.mediaListEntry?.progress) {
|
||||
return `Watched <b>${media.mediaListEntry.progress}</b> of <b>${media.episodes}</b>`
|
||||
return `Watched <b>${media.mediaListEntry.progress}</b> of <b>${getMediaMaxEp(media)}</b>`
|
||||
}
|
||||
return `${media.episodes} Episodes`
|
||||
return `${getMediaMaxEp(media)} Episodes`
|
||||
} else if (detail.property === 'averageScore') {
|
||||
return media.averageScore + '%'
|
||||
} else if (detail.property === 'duration') {
|
||||
|
|
@ -65,59 +67,68 @@
|
|||
$view = (await alRequest({ method: 'SearchIDSingle', id: media.id })).data.Media
|
||||
}
|
||||
}
|
||||
async function score (media, score) {
|
||||
const variables = {
|
||||
method: 'Entry',
|
||||
id: media.id,
|
||||
score: score * 10
|
||||
}
|
||||
await alRequest(variables)
|
||||
$view = (await alRequest({ method: 'SearchIDSingle', id: media.id })).data.Media
|
||||
}
|
||||
const trailer = getContext('trailer')
|
||||
function viewTrailer (media) {
|
||||
$trailer = media.trailer.id
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="modal modal-full" class:show={$view} on:keydown={checkClose} tabindex="-1">
|
||||
{#if $view}
|
||||
<div class="modal modal-full" class:show={media} on:keydown={checkClose} tabindex="-1">
|
||||
{#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}> × </button>
|
||||
<div class="h-md-half w-full position-relative z-20">
|
||||
<div class="h-full w-full position-absolute bg-dark-light banner" style:--bannerurl={`url('${$view.bannerImage || ''}')`} />
|
||||
<div class="h-full w-full position-absolute bg-dark-light banner" style:--bannerurl={`url('${media.bannerImage || ''}')`} />
|
||||
<div class="d-flex h-full top w-full">
|
||||
<div class="container-xl w-full">
|
||||
<div class="row d-flex justify-content-end flex-row h-full px-20 pt-20 px-xl-0">
|
||||
<div class="col-md-3 col-4 d-flex h-full justify-content-end flex-column pb-15 align-items-center">
|
||||
<img class="contain-img rounded mw-full mh-full shadow" alt="cover" src={$view.coverImage?.extraLarge || $view.coverImage?.medium} />
|
||||
<img class="contain-img rounded mw-full mh-full shadow" alt="cover" src={media.coverImage?.extraLarge || media.coverImage?.medium} />
|
||||
</div>
|
||||
<div class="col-md-9 col-8 row align-content-end pl-20">
|
||||
<div class="col-md-8 col-12 d-flex justify-content-end flex-column">
|
||||
<div class="px-md-20 d-flex flex-column font-size-12">
|
||||
<span class="title font-weight-bold pb-sm-15 text-white">
|
||||
{$view.title.userPreferred}
|
||||
{media.title.userPreferred}
|
||||
</span>
|
||||
<div class="d-flex flex-row font-size-18 pb-sm-15">
|
||||
{#if $view.averageScore}
|
||||
{#if media.averageScore}
|
||||
<span class="material-icons mr-10 font-size-24"> trending_up </span>
|
||||
<span>
|
||||
Rating: {$view.averageScore + '%'}
|
||||
Rating: {media.averageScore + '%'}
|
||||
<span class="font-weight-bold mr-20" />
|
||||
</span>
|
||||
{/if}
|
||||
<span class="material-icons mx-10 font-size-24"> monitor </span>
|
||||
<span>
|
||||
Format: {$view.format === 'TV' ? $view.format : $view.format?.toLowerCase()}
|
||||
Format: {media.format === 'TV' ? media.format : media.format?.toLowerCase()}
|
||||
<span class="font-weight-bold mr-20 text-capitalize" />
|
||||
</span>
|
||||
{#if $view.episodes !== 1 && getMediaMaxEp($view)}
|
||||
{#if media.episodes !== 1 && getMediaMaxEp(media)}
|
||||
<span class="material-icons mx-10 font-size-24"> theaters </span>
|
||||
<span>
|
||||
Episodes: {getMediaMaxEp($view)}
|
||||
Episodes: {getMediaMaxEp(media)}
|
||||
<span class="font-weight-bold mr-20" />
|
||||
</span>
|
||||
{:else if $view.duration}
|
||||
{:else if media.duration}
|
||||
<span class="material-icons mx-10 font-size-24"> timer </span>
|
||||
<span>
|
||||
Length: {$view.duration + ' min'}
|
||||
Length: {media.duration + ' min'}
|
||||
<span class="font-weight-bold mr-20" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="pb-15 pt-5 overflow-x-auto text-nowrap font-weight-bold">
|
||||
{#each $view.genres as genre}
|
||||
{#each media.genres as genre}
|
||||
<div class="badge badge-pill shadow">
|
||||
{genre}
|
||||
</div>
|
||||
|
|
@ -128,24 +139,44 @@
|
|||
<div class="col-md-4 d-flex justify-content-end flex-column">
|
||||
<div class="d-flex flex-column flex-wrap">
|
||||
<button
|
||||
class="btn btn-primary d-flex align-items-center font-weight-bold font-size-24 h-50 mb-5"
|
||||
class="btn btn-primary d-flex align-items-center font-weight-bold font-size-24 h-50 mb-5 shadow-lg"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
playAnime($view, Math.min(getMediaMaxEp($view), $view.mediaListEntry?.progress + 1))
|
||||
playAnime(media, Math.min(maxPlayEp, media.mediaListEntry?.progress + 1))
|
||||
close()
|
||||
}}>
|
||||
<span class="material-icons mr-10 font-size-24 w-30"> play_arrow </span>
|
||||
<span>{$view.mediaListEntry?.progress ? 'Continue ' + Math.min(getMediaMaxEp($view), $view.mediaListEntry?.progress + 1) : 'Play'}</span>
|
||||
<span>{(media.mediaListEntry?.progress && media.mediaListEntry?.status !== 'COMPLETED') ? 'Continue ' + Math.min(maxPlayEp, media.mediaListEntry?.progress + 1) : 'Play'}</span>
|
||||
</button>
|
||||
{#if alToken && $view.mediaListEntry?.status !== 'CURRENT' && $view.mediaListEntry?.status !== 'COMPLETED'}
|
||||
<button class="btn d-flex align-items-center mb-5 font-weight-bold font-size-16 btn-primary" on:click={() => addToList($view)}>
|
||||
<span class="material-icons mr-10 font-size-18 w-30"> {$view.mediaListEntry?.status !== 'PLANNING' ? 'add' : 'remove'} </span>
|
||||
{$view.mediaListEntry?.status !== 'PLANNING' ? 'Add To List' : 'Remove From List'}
|
||||
</button>
|
||||
{#if alToken}
|
||||
{#if media.mediaListEntry?.status !== 'CURRENT' && media.mediaListEntry?.status !== 'COMPLETED'}
|
||||
<button class="btn d-flex align-items-center mb-5 font-weight-bold font-size-16 btn-primary shadow-lg" on:click={() => addToList(media)}>
|
||||
<span class="material-icons mr-10 font-size-18 w-30"> {media.mediaListEntry?.status !== 'PLANNING' ? 'add' : 'remove'} </span>
|
||||
{media.mediaListEntry?.status !== 'PLANNING' ? 'Add To List' : 'Remove From List'}
|
||||
</button>
|
||||
{/if}
|
||||
<div class="input-group shadow-lg mb-5 font-size-16">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text bg-tp pl-15 d-flex material-icons font-size-18">hotel_class</span>
|
||||
</div>
|
||||
<select class="form-control" required value={(media.mediaListEntry?.score || '').toString()} on:change={({ target }) => { score(media, target.value) }}>
|
||||
<option value selected disabled hidden>Score</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
<option value="10">10</option>
|
||||
</select>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $view.trailer}
|
||||
<button class="btn d-flex align-items-center mb-5 font-weight-bold font-size-16" on:click={() => viewTrailer($view)}>
|
||||
<span class="material-icons mr-10 font-size-18 w-30"> live_tv </span>
|
||||
{#if media.trailer}
|
||||
<button class="btn d-flex align-items-center mb-5 font-weight-bold font-size-16 shadow-lg" on:click={() => viewTrailer(media)}>
|
||||
<span class="material-icons mr-15 font-size-18 w-30"> live_tv </span>
|
||||
Trailer
|
||||
</button>
|
||||
{/if}
|
||||
|
|
@ -161,10 +192,20 @@
|
|||
<div class="col-md-9 px-20">
|
||||
<h1 class="title font-weight-bold text-white">Sypnosis</h1>
|
||||
<div class="font-size-16 pr-15">
|
||||
{@html $view.description}
|
||||
{@html media.description}
|
||||
</div>
|
||||
|
||||
{#if getMediaMaxEp($view)}
|
||||
{#if media.relations?.edges?.filter(({ node }) => node.type === 'ANIME').length}
|
||||
<div class="d-flex text-capitalize flex-wrap pt-20 justify-center">
|
||||
{#each media.relations?.edges.filter(({ node }) => node.type === 'ANIME') 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>
|
||||
<h5 class="font-weight-bold text-white">{node.title.userPreferred}</h5>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if maxPlayEp}
|
||||
<table class="table table-hover w-500 table-auto">
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
@ -173,15 +214,14 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each Array(getMediaMaxEp($view, true)) as _, i}
|
||||
{@const index = getMediaMaxEp($view, true) - i - 1}
|
||||
<tr
|
||||
class="font-size-20 py-10 pointer"
|
||||
{#each Array(maxPlayEp) as _, i}
|
||||
{@const ep = maxPlayEp - i}
|
||||
<tr class="font-size-20 py-10 pointer {ep <= media.mediaListEntry?.progress ? 'text-muted' : 'text-white'}"
|
||||
on:click={() => {
|
||||
playAnime($view, index + 1)
|
||||
playAnime(media, ep)
|
||||
close()
|
||||
}}>
|
||||
<td class="w-full">Episode {index + 1}</td>
|
||||
<td class="w-full font-weight-semi-bold">Episode {ep}</td>
|
||||
<td class="material-icons text-right h-full d-table-cell">play_arrow</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
|
@ -193,7 +233,7 @@
|
|||
<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">
|
||||
{#each detailsMap as detail}
|
||||
{@const property = getProperty(detail.property, $view)}
|
||||
{@const property = getProperty(detail.property, media)}
|
||||
{#if property}
|
||||
<div class="d-flex flex-row px-10 py-5">
|
||||
<div class={'mr-10 ' + (detail.custom === 'icon' ? 'd-flex align-items-center text-nowrap font-size-20 font-weight-bold' : 'material-icons font-size-24')}>
|
||||
|
|
@ -202,7 +242,7 @@
|
|||
<div class="d-flex flex-column justify-content-center text-nowrap">
|
||||
<div class="font-weight-bold">
|
||||
{#if detail.custom === 'property'}
|
||||
{@html getCustomProperty(detail, $view)}
|
||||
{@html getCustomProperty(detail, media)}
|
||||
{:else if property.constructor === Array}
|
||||
{property === 'nodes' ? property[0] && property[0].name : property.join(', ').replace(/_/g, ' ').toLowerCase()}
|
||||
{:else}
|
||||
|
|
@ -229,6 +269,25 @@
|
|||
background-image: linear-gradient(0deg, rgba(17, 20, 23, 1) 0%, rgba(17, 20, 23, 0.8) 25%, rgba(17, 20, 23, 0.4) 50%, rgba(37, 40, 44, 0) 100%), var(--bannerurl) !important;
|
||||
}
|
||||
|
||||
select.form-control:invalid {
|
||||
color: var(--dm-input-placeholder-text-color);
|
||||
}
|
||||
|
||||
.rel-img{
|
||||
height: 27rem;
|
||||
width: 17rem
|
||||
}
|
||||
|
||||
.cover-img {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.rel {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
.rel:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
.d-table-cell {
|
||||
display: table-cell !important;
|
||||
}
|
||||
|
|
@ -243,6 +302,10 @@
|
|||
box-shadow: var(--dm-button-box-shadow) !important;
|
||||
}
|
||||
|
||||
.bg-tp {
|
||||
background-color: var(--dm-button-bg-color) !important;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
export let loadCurrent
|
||||
let searchTimeout = null
|
||||
|
||||
function searchClear() {
|
||||
function searchClear () {
|
||||
search = {
|
||||
format: '',
|
||||
genre: '',
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
}
|
||||
current = null
|
||||
}
|
||||
function input() {
|
||||
function input () {
|
||||
if (!searchTimeout) {
|
||||
if (Object.values(search).filter(v => v).length) media = [new Promise(() => {})]
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -182,7 +182,8 @@ mediaListEntry {
|
|||
id,
|
||||
progress,
|
||||
repeat,
|
||||
status
|
||||
status,
|
||||
score(format: POINT_10)
|
||||
},
|
||||
source,
|
||||
studios(isMain: true) {
|
||||
|
|
@ -348,9 +349,10 @@ query ($page: Int, $perPage: Int, $sort: [MediaSort], $type: MediaType, $search:
|
|||
variables.id = opts.id
|
||||
variables.status = opts.status
|
||||
variables.episode = opts.episode
|
||||
variables.score = opts.score
|
||||
query = `
|
||||
mutation ($id: Int, $status: MediaListStatus, $episode: Int, $repeat: Int) {
|
||||
SaveMediaListEntry (mediaId: $id, status: $status, progress: $episode, repeat: $repeat) {
|
||||
mutation ($id: Int, $status: MediaListStatus, $episode: Int, $repeat: Int, $score: Int) {
|
||||
SaveMediaListEntry (mediaId: $id, status: $status, progress: $episode, repeat: $repeat, scoreRaw: $score) {
|
||||
id,
|
||||
status,
|
||||
progress,
|
||||
|
|
|
|||
Loading…
Reference in a new issue