feat: animethemes

This commit is contained in:
ThaUnknown 2025-05-30 02:04:54 +02:00
parent bc35557ebd
commit fd01257bdf
No known key found for this signature in database
6 changed files with 80 additions and 45 deletions

View file

@ -1,6 +1,6 @@
{
"name": "ui",
"version": "6.3.34",
"version": "6.3.35",
"license": "BUSL-1.1",
"private": true,
"packageManager": "pnpm@9.14.4",

View file

@ -177,8 +177,7 @@ select:not([disabled]):active,
textarea:not([disabled]):active,
details:active,
[tabindex]:not([tabindex="-1"]):active,
[contenteditable]:active,
[controls]:active {
[contenteditable]:active {
transition: all 0.1s ease-in-out;
transform: scale(0.98);
}

View file

@ -1,4 +1,8 @@
<script lang='ts'>
import Play from 'lucide-svelte/icons/play'
import { Button, iconSizes } from '../button'
import type { Media } from '$lib/modules/anilist'
import { themes } from '$lib/modules/animethemes'
@ -6,44 +10,76 @@
export let media: Media
const themesRes = themes(media.id)
// https://api.animethemes.moe/anime/?fields[audio]=id,basename,link,size&fields[video]=id,basename,link,tags&filter[external_id]=113717&filter[has]=resources&filter[site]=AniList&include=animethemes.animethemeentries.videos,animethemes.animethemeentries.videos.audio,animethemes.song,animethemes.song.artists
// https://animethemes.moe/anime/momentary_lily
// https://animethemes.moe/anime/ousama_ranking
let src = ''
function playVideo (url?: string) {
if (!url) return
if (src === url) {
src = ''
return
}
src = url
}
function volume (el: HTMLVideoElement) {
el.volume = 0.2
el.muted = false
}
</script>
{#await themesRes}
Loading
{:then themes}
{#if themes?.anime?.[0]?.animethemes?.length}
{#each themes.anime[0].animethemes as theme (theme.id)}
{theme.type}
<div class='flex flex-col gap-2 pt-3'>
{#await themesRes}
{#each Array.from({ length: 2 }) as _, i (i)}
<div class='bg-neutral-950 rounded-md px-7 py-4 gap-4 flex flex-col text-xs'>
<div class='flex h-8 items-center'>
<div class='w-12'>
<div class='bg-primary/5 rounded h-2.5 w-4 animate-pulse' />
</div>
<div class='text-base font-bold flex'>
<div class='bg-primary/5 rounded h-2.5 w-32 animate-pulse mr-2' />
<div class='bg-primary/5 rounded h-2.5 w-20 animate-pulse' />
</div>
</div>
<div class='flex h-8 items-center text-muted-foreground'>
<div class='w-12'>
<div class='bg-primary/5 rounded h-2 w-4 animate-pulse' />
</div>
<div>
<div class='bg-primary/5 rounded h-2 w-20 animate-pulse' />
</div>
</div>
</div>
{/each}
{/if}
{/await}
<div class='bg-neutral-950 rounded-md px-7 py-4 gap-4 flex flex-col text-xs'>
<div class='flex h-8 items-center'>
<div class='w-12'>
OP
</div>
<div class='text-base font-bold'>
Oishi Survivor <span class='text-xs text-muted-foreground font-medium'>by</span> HANABIE
</div>
</div>
<div class='flex h-8 items-center text-muted-foreground'>
<div class='w-12'>
v1
</div>
<div>
Episodes 1-2
</div>
</div>
<div class='flex h-8 items-center text-muted-foreground'>
<div class='w-12'>
v2
</div>
<div>
Episodes 3-13
</div>
</div>
{:then themes}
{#if themes?.anime?.[0]?.animethemes?.length}
{#each themes.anime[0].animethemes as theme (theme.id)}
<div class='bg-neutral-950 rounded-md px-7 py-4 gap-4 flex flex-col text-xs'>
<div class='flex h-8 items-center'>
<div class='w-12'>
{theme.type}
</div>
<div class='text-base font-bold'>
{theme.song?.title} <span class='text-xs text-muted-foreground font-medium'>by</span> {theme.song?.artists?.map(({ name }) => name).join(', ')}
</div>
</div>
{#each theme.animethemeentries ?? [] as entry (entry.id)}
{@const url = entry.videos?.[entry.videos.length - 1]?.link}
<div class='flex h-8 items-center text-muted-foreground'>
<div class='w-12'>
v{entry.version ?? 1}
</div>
<div>
Episodes {entry.episodes}
</div>
{#if entry.videos?.length}
<Button size='icon-sm' class='ml-auto font-bold rounded-full' on:click={() => playVideo(url)}><Play fill='currentColor' size={iconSizes['icon-sm']} /></Button>
{/if}
</div>
{#if src === url}
<video class='h-64 rounded-md self-center' controls autoplay {src} use:volume />
{/if}
{/each}
</div>
{/each}
{/if}
{/await}
</div>

View file

@ -3,5 +3,5 @@ import type { AnimeThemesResponse } from './types'
import { safefetch } from '$lib/utils'
export function themes (id: number, _fetch = fetch) {
return safefetch<AnimeThemesResponse>(_fetch, `https://api.animethemes.moe/anime/?fields[audio]=id,basename,link,size&fields[video]=id,basename,link,tags&filter[external_id]=${id}&filter[has]=resources&filter[site]=AniList&include=animethemes.animethemeentries.videos,animethemes.animethemeentries.videos.audio,animethemes.song,animethemes.song.artists`)
return safefetch<AnimeThemesResponse>(_fetch, `https://api.animethemes.moe/anime/?fields[audio]=id,basename,link,size&fields[video]=id,basename,link,tags&filter[external_id]=${id}&filter[has]=resources&filter[site]=AniList&include=animethemes.animethemeentries.videos,animethemes.song,animethemes.song.artists`)
}

View file

@ -17,7 +17,7 @@ export interface Anime {
export interface AnimeTheme {
id?: number
sequence?: null
sequence?: number
slug?: string
type?: string
song?: Song
@ -27,10 +27,10 @@ export interface AnimeTheme {
export interface AnimeThemeEntry {
id?: number
episodes?: string
notes?: null
notes?: string
nsfw?: boolean
spoiler?: boolean
version?: null
version?: number
videos?: Video[]
}

View file

@ -65,7 +65,7 @@
<Tabs.List class='flex'>
<Tabs.Trigger value='episodes' class='px-8 data-[state=active]:font-bold'>Episodes</Tabs.Trigger>
<Tabs.Trigger value='threads' class='px-8 data-[state=active]:font-bold'>Threads</Tabs.Trigger>
<Tabs.Trigger value='themes' class='px-8 data-[state=active]:font-bold' disabled>Themes</Tabs.Trigger>
<Tabs.Trigger value='themes' class='px-8 data-[state=active]:font-bold'>Themes</Tabs.Trigger>
</Tabs.List>
</div>
<Tabs.Content value='episodes' tabindex={-1}>