feat: cycling home screen banner #404

This commit is contained in:
ThaUnknown 2024-03-19 22:37:08 +01:00
parent 01e3bb7810
commit d760761bba
3 changed files with 92 additions and 27 deletions

View file

@ -2,13 +2,32 @@
import FullBanner from './FullBanner.svelte'
import SkeletonBanner from './SkeletonBanner.svelte'
export let data
/**
* Shuffles array in place.
* @template T
* @param {T[]} array items An array containing the items.
* @returns {T[]}
*/
function shuffle (array) {
let currentIndex = array.length
let randomIndex
while (currentIndex > 0) {
randomIndex = Math.floor(Math.random() * currentIndex--);
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]]
}
return array
}
</script>
<div class='w-full h-450 position-relative gradient'>
{#await data}
<SkeletonBanner />
{:then { data }}
<FullBanner media={data.Page.media[0]} />
<FullBanner mediaList={shuffle(data.Page.media).slice(0, 5)} />
{/await}
</div>

View file

@ -2,60 +2,83 @@
import { formatMap, setStatus, playMedia } from '@/modules/anime.js'
import { anilistClient } from '@/modules/anilist.js'
import { click } from '@/modules/click.js'
export let media
export let mediaList
let current = mediaList[0]
async function toggleStatus () {
if (!media.mediaListEntry) {
if (!current.mediaListEntry) {
// add
const res = await setStatus('PLANNING', {}, media)
media.mediaListEntry = res.data.SaveMediaListEntry
const res = await setStatus('PLANNING', {}, current)
current.mediaListEntry = res.data.SaveMediaListEntry
} else {
// delete
anilistClient.delete({ id: media.mediaListEntry.id })
media.mediaListEntry = undefined
anilistClient.delete({ id: current.mediaListEntry.id })
current.mediaListEntry = undefined
}
}
function toggleFavourite () {
anilistClient.favourite({ id: media.id })
media.isFavourite = !media.isFavourite
anilistClient.favourite({ id: current.id })
current.isFavourite = !current.isFavourite
}
function currentIndex () {
return mediaList.indexOf(current)
}
function schedule (index) {
return setTimeout(() => {
current = mediaList[index % mediaList.length]
timeout = schedule(index + 1)
}, 15000)
}
let timeout = schedule(currentIndex() + 1)
function setCurrent (media) {
clearTimeout(timeout)
current = media
timeout = schedule(currentIndex() + 1)
}
</script>
<img src={media.bannerImage || ''} alt='banner' class='img-cover w-full h-full position-absolute' />
{#key current}
<img src={current.bannerImage || ''} alt='banner' class='img-cover w-full h-full position-absolute' />
{/key}
<div class='pl-20 pb-20 justify-content-end d-flex flex-column h-full banner mw-full '>
<div class='text-white font-weight-bold font-size-40 title w-700 mw-full overflow-hidden'>
{media.title.userPreferred}
{current.title.userPreferred}
</div>
<div class='details text-white text-capitalize pt-15 pb-10 d-flex w-600 mw-full'>
<span class='text-nowrap d-flex align-items-center'>
{#if media.format}
{formatMap[media.format]}
{#if current.format}
{formatMap[current.format]}
{/if}
</span>
{#if media.episodes && media.episodes !== 1}
{#if current.episodes && current.episodes !== 1}
<span class='text-nowrap d-flex align-items-center'>
{#if media.mediaListEntry?.status === 'CURRENT' && media.mediaListEntry?.progress }
{media.mediaListEntry.progress} / {media.episodes} Episodes
{#if current.mediaListEntry?.status === 'CURRENT' && current.mediaListEntry?.progress }
{current.mediaListEntry.progress} / {current.episodes} Episodes
{:else}
{media.episodes} Episodes
{current.episodes} Episodes
{/if}
</span>
{:else if media.duration}
{:else if current.duration}
<span class='text-nowrap d-flex align-items-center'>
{media.duration + ' Minutes'}
{current.duration + ' Minutes'}
</span>
{/if}
{#if media.season || media.seasonYear}
{#if current.season || current.seasonYear}
<span class='text-nowrap d-flex align-items-center'>
{[media.season?.toLowerCase(), media.seasonYear].filter(s => s).join(' ')}
{[current.season?.toLowerCase(), current.seasonYear].filter(s => s).join(' ')}
</span>
{/if}
</div>
<div class='text-muted description overflow-hidden w-600 mw-full'>
{media.description?.replace(/<[^>]*>/g, '')}
{current.description?.replace(/<[^>]*>/g, '')}
</div>
<div class='details text-white text-capitalize pt-15 pb-10 d-flex w-600 mw-full'>
{#each media.genres as genre}
{#each current.genres as genre}
<span class='text-nowrap d-flex align-items-center'>
{genre}
</span>
@ -63,19 +86,42 @@
</div>
<div class='d-flex flex-row pb-20 w-600 mw-full'>
<button class='btn bg-dark-light px-20 shadow-none border-0'
use:click={() => playMedia(media)}>
use:click={() => playMedia(current)}>
Watch Now
</button>
<button class='btn bg-dark-light btn-square ml-10 material-symbols-outlined font-size-16 shadow-none border-0' class:filled={media.isFavourite} use:click={toggleFavourite}>
<button class='btn bg-dark-light btn-square ml-10 material-symbols-outlined font-size-16 shadow-none border-0' class:filled={current.isFavourite} use:click={toggleFavourite}>
favorite
</button>
<button class='btn bg-dark-light btn-square ml-10 material-symbols-outlined font-size-16 shadow-none border-0' class:filled={media.mediaListEntry} use:click={toggleStatus}>
<button class='btn bg-dark-light btn-square ml-10 material-symbols-outlined font-size-16 shadow-none border-0' class:filled={current.mediaListEntry} use:click={toggleStatus}>
bookmark
</button>
</div>
<div class='d-flex'>
{#each mediaList as media}
{@const active = current === media}
<div class='rounded bg-dark-light mr-10 progress-badge overflow-hidden pointer' class:active style='height: 3px;' style:width={active ? '5rem' : '2.7rem'} use:click={() => setCurrent(media)}>
<div class='progress-content h-full' class:bg-white={active} />
</div>
{/each}
</div>
</div>
<style>
.progress-badge {
transition: width .8s ease;
}
.progress-badge.active .progress-content {
animation: fill 15s linear;
}
@keyframes fill {
from {
width: 0;
}
to {
width: 100%;
}
}
.w-700 {
width: 70rem
}

View file

@ -3,7 +3,7 @@
import { alToken, settings } from '@/modules/settings.js'
import { anilistClient, currentSeason, currentYear } from '@/modules/anilist.js'
const bannerData = anilistClient.search({ method: 'Search', sort: 'POPULARITY_DESC', perPage: 1, onList: false, season: currentSeason, year: currentYear })
const bannerData = anilistClient.search({ method: 'Search', sort: 'POPULARITY_DESC', perPage: 15, onList: false, season: currentSeason, year: currentYear })
const manager = new SectionsManager()