mirror of
https://github.com/NoCrypt/migu.git
synced 2026-03-29 13:59:09 +00:00
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:
parent
ee530a5d9a
commit
27ac5cf0e8
5 changed files with 141 additions and 100 deletions
|
|
@ -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}> × </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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
Loading…
Reference in a new issue