mirror of
https://github.com/NoCrypt/migu.git
synced 2026-04-05 00:59:49 +00:00
feat: Better relations cards
This commit is contained in:
parent
81f4d03c47
commit
ea1c3e29ed
7 changed files with 159 additions and 58 deletions
|
|
@ -4,8 +4,10 @@
|
|||
import { click } from '@/modules/click.js'
|
||||
import Scoring from '@/views/ViewAnime/Scoring.svelte'
|
||||
import AudioLabel from '@/views/ViewAnime/AudioLabel.svelte'
|
||||
import Helper from "@/modules/helper.js"
|
||||
/** @type {import('@/modules/al.d.ts').Media} */
|
||||
export let media
|
||||
export let type = null
|
||||
|
||||
let hide = true
|
||||
|
||||
|
|
@ -91,7 +93,21 @@
|
|||
{/if}
|
||||
<Scoring {media} previewAnime={true}/>
|
||||
</div>
|
||||
<div class='details text-white text-capitalize pt-15 pb-10 d-flex'>
|
||||
<div class='details text-white text-capitalize pt-15 d-flex'>
|
||||
{#if type || type === 0}
|
||||
<span class='context-type text-nowrap d-flex align-items-center'>
|
||||
{#if Number.isInteger(type) && type >= 0}
|
||||
<span class='material-symbols-outlined filled font-size-18 pr-5 {type === 0 ? "text-muted" : "text-success"}'>
|
||||
thumb_up
|
||||
</span>
|
||||
{:else if Number.isInteger(type) && type < 0}
|
||||
<span class='material-symbols-outlined text-danger filled font-size-18 pr-5'>
|
||||
thumb_down
|
||||
</span>
|
||||
{/if}
|
||||
{(Number.isInteger(type) ? Math.abs(type).toLocaleString() + (type >= 0 ? ' likes' : ' dislikes') : type)}
|
||||
</span>
|
||||
{/if}
|
||||
<span class='text-nowrap d-flex align-items-center'>
|
||||
{#if media.format}
|
||||
{formatMap[media.format]}
|
||||
|
|
@ -118,15 +134,19 @@
|
|||
Rated 18+
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class='details text-white text-capitalize pb-10 d-flex'>
|
||||
{#if media.season || media.seasonYear}
|
||||
<span class='text-nowrap d-flex align-items-center'>
|
||||
{[media.season?.toLowerCase(), media.seasonYear].filter(s => s).join(' ')}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class='w-full h-full text-muted description overflow-hidden'>
|
||||
{media.description?.replace(/<[^>]*>/g, '')}
|
||||
</div>
|
||||
{#if media.description}
|
||||
<div class='w-full h-full text-muted description overflow-hidden'>
|
||||
{media.description?.replace(/<[^>]*>/g, '')}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -136,9 +156,9 @@
|
|||
-webkit-line-clamp: 4;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
.details span + span::before {
|
||||
.details > span:not(:last-child)::after {
|
||||
content: '•';
|
||||
padding: 0 .5rem;
|
||||
padding: .5rem;
|
||||
font-size: .6rem;
|
||||
align-self: center;
|
||||
white-space: normal;
|
||||
|
|
|
|||
|
|
@ -7,8 +7,11 @@
|
|||
import AudioLabel from '@/views/ViewAnime/AudioLabel.svelte'
|
||||
|
||||
import { page } from '@/App.svelte'
|
||||
import { anilistClient } from "@/modules/anilist"
|
||||
import Helper from "@/modules/helper.js"
|
||||
/** @type {import('@/modules/al.d.ts').Media} */
|
||||
export let media
|
||||
export let type = null
|
||||
export let variables = null
|
||||
let preview = false
|
||||
|
||||
|
|
@ -23,7 +26,7 @@
|
|||
|
||||
<div class='d-flex p-md-20 p-15 position-relative first-check' use:hoverClick={[viewMedia, setHoverState]}>
|
||||
{#if preview}
|
||||
<PreviewCard {media} />
|
||||
<PreviewCard {media} {type} />
|
||||
{/if}
|
||||
<div class='item small-card d-flex flex-column h-full pointer content-visibility-auto' class:opacity-half={variables?.continueWatching && Helper.isMalAuth() && media?.status !== 'FINISHED' && media?.mediaListEntry?.progress >= media?.nextAiringEpisode?.episode - 1}>
|
||||
{#if $page === 'schedule'}
|
||||
|
|
@ -42,6 +45,20 @@
|
|||
<img loading='lazy' src={media.coverImage.extraLarge || ''} alt='cover' class='cover-img w-full rounded' style:--color={media.coverImage.color || '#1890ff'} />
|
||||
<AudioLabel {media} />
|
||||
</div>
|
||||
{#if type || type === 0}
|
||||
<div class='context-type d-flex align-items-center'>
|
||||
{#if Number.isInteger(type) && type >= 0}
|
||||
<span class='material-symbols-outlined filled font-size-18 pr-5 {type === 0 ? "text-muted" : "text-success"}'>
|
||||
thumb_up
|
||||
</span>
|
||||
{:else if Number.isInteger(type) && type < 0}
|
||||
<span class='material-symbols-outlined text-danger filled font-size-18 pr-5'>
|
||||
thumb_down
|
||||
</span>
|
||||
{/if}
|
||||
{(Number.isInteger(type) ? Math.abs(type).toLocaleString() + (type >= 0 ? ' likes' : ' dislikes') : type)}
|
||||
</div>
|
||||
{/if}
|
||||
<div class='text-white font-weight-very-bold font-size-16 pt-15 title overflow-hidden'>
|
||||
{#if media.mediaListEntry?.status}
|
||||
<div style:--statusColor={statusColorMap[media.mediaListEntry.status]} class='list-status-circle d-inline-flex overflow-hidden mr-5' title={media.mediaListEntry.status} />
|
||||
|
|
|
|||
45
common/modules/al.d.ts
vendored
45
common/modules/al.d.ts
vendored
|
|
@ -31,6 +31,12 @@ export type Media = {
|
|||
isAdult?: boolean
|
||||
bannerImage?: string
|
||||
synonyms?: string[]
|
||||
stats: {
|
||||
scoreDistribution: {
|
||||
score: number
|
||||
amount: number
|
||||
}[]
|
||||
}
|
||||
nextAiringEpisode?: {
|
||||
episode: number
|
||||
airingAt: number
|
||||
|
|
@ -55,6 +61,16 @@ export type Media = {
|
|||
status?: string
|
||||
customLists?: string[]
|
||||
score?: number
|
||||
startedAt?: {
|
||||
year: number
|
||||
month: number
|
||||
day: number
|
||||
}
|
||||
completedAt?: {
|
||||
year: number
|
||||
month: number
|
||||
day: number
|
||||
}
|
||||
}
|
||||
studios?: {
|
||||
edges: {
|
||||
|
|
@ -74,7 +90,11 @@ export type Media = {
|
|||
relationType: string
|
||||
node: {
|
||||
id: number
|
||||
idMal: number
|
||||
title: {
|
||||
romaji?: string
|
||||
english?: string
|
||||
native?: string
|
||||
userPreferred: string
|
||||
}
|
||||
type: string
|
||||
|
|
@ -97,21 +117,16 @@ export type Media = {
|
|||
}
|
||||
}[]
|
||||
}
|
||||
// recommendations?: {
|
||||
// edges?: {
|
||||
// node: {
|
||||
// media: {
|
||||
// id: number
|
||||
// title: {
|
||||
// userPreferred: string
|
||||
// }
|
||||
// coverImage?: {
|
||||
// medium: string
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }[]
|
||||
// }
|
||||
recommendations?: {
|
||||
edges?: {
|
||||
node: {
|
||||
rating: number
|
||||
mediaRecommendation: {
|
||||
id: number
|
||||
}
|
||||
}
|
||||
}[]
|
||||
}
|
||||
}
|
||||
|
||||
export type Following = {
|
||||
|
|
|
|||
|
|
@ -94,6 +94,12 @@ countryOfOrigin,
|
|||
isAdult,
|
||||
bannerImage,
|
||||
synonyms,
|
||||
stats {
|
||||
scoreDistribution {
|
||||
score,
|
||||
amount
|
||||
}
|
||||
},
|
||||
nextAiringEpisode {
|
||||
timeUntilAiring,
|
||||
episode
|
||||
|
|
@ -117,7 +123,17 @@ mediaListEntry {
|
|||
repeat,
|
||||
status,
|
||||
customLists(asArray: true),
|
||||
score(format: POINT_10)
|
||||
score(format: POINT_10),
|
||||
startedAt {
|
||||
year,
|
||||
month,
|
||||
day
|
||||
},
|
||||
completedAt {
|
||||
year,
|
||||
month,
|
||||
day
|
||||
}
|
||||
},
|
||||
studios(isMain: true) {
|
||||
nodes {
|
||||
|
|
@ -135,9 +151,17 @@ relations {
|
|||
relationType(version:2),
|
||||
node {
|
||||
id,
|
||||
title {userPreferred},
|
||||
coverImage {medium},
|
||||
idMal,
|
||||
title {
|
||||
romaji,
|
||||
english,
|
||||
native,
|
||||
userPreferred
|
||||
},
|
||||
coverImage {
|
||||
medium,
|
||||
extraLarge
|
||||
},
|
||||
type,
|
||||
status,
|
||||
format,
|
||||
|
|
@ -157,23 +181,17 @@ relations {
|
|||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// recommendations {
|
||||
// edges {
|
||||
// node {
|
||||
// mediaRecommendation {
|
||||
// id,
|
||||
// title {
|
||||
// userPreferred
|
||||
// },
|
||||
// coverImage {
|
||||
// medium
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
},
|
||||
recommendations {
|
||||
edges {
|
||||
node {
|
||||
rating,
|
||||
mediaRecommendation {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
class AnilistClient {
|
||||
limiter = new Bottleneck({
|
||||
|
|
|
|||
|
|
@ -28,6 +28,15 @@ export default function (t, { speed = 120, smooth = 10 } = {}) {
|
|||
return deltaTime / 14
|
||||
}
|
||||
|
||||
t.addEventListener('scrolltop', () => {
|
||||
pos = 0
|
||||
t.scrollTop = scrollTop
|
||||
if (!moving) {
|
||||
lastTime = null
|
||||
update()
|
||||
}
|
||||
})
|
||||
|
||||
t.addEventListener('pointerup', () => { pos = scrollTop = t.scrollTop })
|
||||
|
||||
function update () {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
showMore = !showMore
|
||||
}
|
||||
export let title = 'Relations'
|
||||
export let promise = null
|
||||
</script>
|
||||
{#if list?.length}
|
||||
<span class='d-flex align-items-end pointer' use:click={toggleList}>
|
||||
|
|
@ -18,14 +19,17 @@
|
|||
{/if}
|
||||
</div>
|
||||
</span>
|
||||
<div class='d-flex text-capitalize flex-wrap pt-10 justify-content-center'>
|
||||
<div class='d-flex text-capitalize flex-wrap pt-10 justify-content-center gallery'>
|
||||
{#each list.slice(0, showMore ? 100 : 4) as item}
|
||||
<slot {item} />
|
||||
<slot {item} {promise} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.gallery :global(.first-check:first-child) :global(.absolute-container) {
|
||||
left: -48% !important;
|
||||
}
|
||||
.more:hover {
|
||||
color: var(--dm-link-text-color-hover) !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@
|
|||
import Following from './Following.svelte'
|
||||
import smoothScroll from '@/modules/scroll.js'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
import SmallCard from "@/components/cards/SmallCard.svelte"
|
||||
import SkeletonCard from "@/components/cards/SkeletonCard.svelte"
|
||||
import Helper from "@/modules/helper.js"
|
||||
|
||||
export let overlay
|
||||
const view = getContext('view')
|
||||
|
|
@ -161,20 +164,32 @@
|
|||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class='w-full d-flex flex-row align-items-center pt-20 mt-10'>
|
||||
<hr class='w-full' />
|
||||
<div class='font-size-18 font-weight-semi-bold px-20 text-white'>Synopsis</div>
|
||||
<hr class='w-full' />
|
||||
</div>
|
||||
<div class='font-size-16 pre-wrap pt-20 select-all'>
|
||||
{media.description?.replace(/<[^>]*>/g, '') || ''}
|
||||
</div>
|
||||
<ToggleList list={media.relations?.edges?.filter(({ node }) => node.type === 'ANIME')} let:item title='Relations'>
|
||||
<div class='w-150 mx-15 my-10 rel pointer'
|
||||
use:click={async () => { $view = null; $view = (await anilistClient.searchIDSingle({ id: item.node.id })).data.Media }}>
|
||||
<img loading='lazy' src={item.node.coverImage.medium || ''} alt='cover' class='cover-img w-full h-200 rel-img rounded' />
|
||||
<div class='pt-5'>{item.relationType.replace(/_/g, ' ').toLowerCase()}</div>
|
||||
<h5 class='font-weight-bold text-white mb-5'>{item.node.title.userPreferred}</h5>
|
||||
{#if media.description}
|
||||
<div class='w-full d-flex flex-row align-items-center pt-20 mt-10'>
|
||||
<hr class='w-full' />
|
||||
<div class='font-size-18 font-weight-semi-bold px-20 text-white'>Synopsis</div>
|
||||
<hr class='w-full' />
|
||||
</div>
|
||||
<div class='font-size-16 pre-wrap pt-20 select-all'>
|
||||
{media.description?.replace(/<[^>]*>/g, '') || ''}
|
||||
</div>
|
||||
{/if}
|
||||
<ToggleList list={
|
||||
media.relations?.edges?.filter(({ node, relationType }) => node.type === 'ANIME' && relationType !== 'CHARACTER').sort((a, b) => {
|
||||
const relationTypeComparison = a.relationType.localeCompare(b.relationType)
|
||||
if (relationTypeComparison !== 0) {
|
||||
return relationTypeComparison
|
||||
}
|
||||
return (a.node.seasonYear || 0) - (b.node.seasonYear || 0)
|
||||
})} promise={ anilistClient.searchIDS({ page: 1, perPage: 50, id: media.relations?.edges?.filter(({ node, relationType }) => node.type === 'ANIME' && relationType !== 'CHARACTER').map(({ node }) => node.id) }) } let:item let:promise title='Relations'>
|
||||
<div class='small-card'>
|
||||
{#await promise}
|
||||
<SkeletonCard />
|
||||
{:then res }
|
||||
{#if res}
|
||||
<SmallCard media={anilistClient.mediaCache[item.node.id]} type={item.relationType.replace(/_/g, ' ').toLowerCase()} />
|
||||
{/if}
|
||||
{/await}
|
||||
</div>
|
||||
</ToggleList>
|
||||
<Following {media} />
|
||||
|
|
@ -229,6 +244,9 @@
|
|||
.cover {
|
||||
aspect-ratio: 7/10;
|
||||
}
|
||||
.small-card {
|
||||
width: 23rem !important;
|
||||
}
|
||||
|
||||
button.bg-dark:hover {
|
||||
background: #292d33 !important;
|
||||
|
|
|
|||
Loading…
Reference in a new issue