mirror of
https://github.com/NoCrypt/migu.git
synced 2026-04-21 00:22:08 +00:00
feat: cycling home screen banner #404
This commit is contained in:
parent
01e3bb7810
commit
d760761bba
3 changed files with 92 additions and 27 deletions
|
|
@ -2,13 +2,32 @@
|
||||||
import FullBanner from './FullBanner.svelte'
|
import FullBanner from './FullBanner.svelte'
|
||||||
import SkeletonBanner from './SkeletonBanner.svelte'
|
import SkeletonBanner from './SkeletonBanner.svelte'
|
||||||
export let data
|
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>
|
</script>
|
||||||
|
|
||||||
<div class='w-full h-450 position-relative gradient'>
|
<div class='w-full h-450 position-relative gradient'>
|
||||||
{#await data}
|
{#await data}
|
||||||
<SkeletonBanner />
|
<SkeletonBanner />
|
||||||
{:then { data }}
|
{:then { data }}
|
||||||
<FullBanner media={data.Page.media[0]} />
|
<FullBanner mediaList={shuffle(data.Page.media).slice(0, 5)} />
|
||||||
{/await}
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,60 +2,83 @@
|
||||||
import { formatMap, setStatus, playMedia } from '@/modules/anime.js'
|
import { formatMap, setStatus, playMedia } from '@/modules/anime.js'
|
||||||
import { anilistClient } from '@/modules/anilist.js'
|
import { anilistClient } from '@/modules/anilist.js'
|
||||||
import { click } from '@/modules/click.js'
|
import { click } from '@/modules/click.js'
|
||||||
export let media
|
export let mediaList
|
||||||
|
|
||||||
|
let current = mediaList[0]
|
||||||
|
|
||||||
async function toggleStatus () {
|
async function toggleStatus () {
|
||||||
if (!media.mediaListEntry) {
|
if (!current.mediaListEntry) {
|
||||||
// add
|
// add
|
||||||
const res = await setStatus('PLANNING', {}, media)
|
const res = await setStatus('PLANNING', {}, current)
|
||||||
media.mediaListEntry = res.data.SaveMediaListEntry
|
current.mediaListEntry = res.data.SaveMediaListEntry
|
||||||
} else {
|
} else {
|
||||||
// delete
|
// delete
|
||||||
anilistClient.delete({ id: media.mediaListEntry.id })
|
anilistClient.delete({ id: current.mediaListEntry.id })
|
||||||
media.mediaListEntry = undefined
|
current.mediaListEntry = undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function toggleFavourite () {
|
function toggleFavourite () {
|
||||||
anilistClient.favourite({ id: media.id })
|
anilistClient.favourite({ id: current.id })
|
||||||
media.isFavourite = !media.isFavourite
|
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>
|
</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='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'>
|
<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>
|
||||||
<div class='details text-white text-capitalize pt-15 pb-10 d-flex w-600 mw-full'>
|
<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'>
|
<span class='text-nowrap d-flex align-items-center'>
|
||||||
{#if media.format}
|
{#if current.format}
|
||||||
{formatMap[media.format]}
|
{formatMap[current.format]}
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
{#if media.episodes && media.episodes !== 1}
|
{#if current.episodes && current.episodes !== 1}
|
||||||
<span class='text-nowrap d-flex align-items-center'>
|
<span class='text-nowrap d-flex align-items-center'>
|
||||||
{#if media.mediaListEntry?.status === 'CURRENT' && media.mediaListEntry?.progress }
|
{#if current.mediaListEntry?.status === 'CURRENT' && current.mediaListEntry?.progress }
|
||||||
{media.mediaListEntry.progress} / {media.episodes} Episodes
|
{current.mediaListEntry.progress} / {current.episodes} Episodes
|
||||||
{:else}
|
{:else}
|
||||||
{media.episodes} Episodes
|
{current.episodes} Episodes
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
{:else if media.duration}
|
{:else if current.duration}
|
||||||
<span class='text-nowrap d-flex align-items-center'>
|
<span class='text-nowrap d-flex align-items-center'>
|
||||||
{media.duration + ' Minutes'}
|
{current.duration + ' Minutes'}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if media.season || media.seasonYear}
|
{#if current.season || current.seasonYear}
|
||||||
<span class='text-nowrap d-flex align-items-center'>
|
<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>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class='text-muted description overflow-hidden w-600 mw-full'>
|
<div class='text-muted description overflow-hidden w-600 mw-full'>
|
||||||
{media.description?.replace(/<[^>]*>/g, '')}
|
{current.description?.replace(/<[^>]*>/g, '')}
|
||||||
</div>
|
</div>
|
||||||
<div class='details text-white text-capitalize pt-15 pb-10 d-flex w-600 mw-full'>
|
<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'>
|
<span class='text-nowrap d-flex align-items-center'>
|
||||||
{genre}
|
{genre}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -63,19 +86,42 @@
|
||||||
</div>
|
</div>
|
||||||
<div class='d-flex flex-row pb-20 w-600 mw-full'>
|
<div class='d-flex flex-row pb-20 w-600 mw-full'>
|
||||||
<button class='btn bg-dark-light px-20 shadow-none border-0'
|
<button class='btn bg-dark-light px-20 shadow-none border-0'
|
||||||
use:click={() => playMedia(media)}>
|
use:click={() => playMedia(current)}>
|
||||||
Watch Now
|
Watch Now
|
||||||
</button>
|
</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
|
favorite
|
||||||
</button>
|
</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
|
bookmark
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
<style>
|
<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 {
|
.w-700 {
|
||||||
width: 70rem
|
width: 70rem
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { alToken, settings } from '@/modules/settings.js'
|
import { alToken, settings } from '@/modules/settings.js'
|
||||||
import { anilistClient, currentSeason, currentYear } from '@/modules/anilist.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()
|
const manager = new SectionsManager()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue