feat: Better relations cards

This commit is contained in:
RockinChaos 2024-08-01 16:16:13 -07:00
parent 81f4d03c47
commit ea1c3e29ed
7 changed files with 159 additions and 58 deletions

View file

@ -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;

View file

@ -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} />

View file

@ -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 = {

View file

@ -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({

View file

@ -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 () {

View file

@ -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;
}

View file

@ -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;