feat: previously known as miru on first setup page

fix: initial subtitle override state
wip: animethemes
This commit is contained in:
ThaUnknown 2025-05-29 22:59:43 +02:00
parent d632ad5b49
commit bc35557ebd
No known key found for this signature in database
15 changed files with 173 additions and 22 deletions

View file

@ -3,6 +3,7 @@
"usernamehw.errorlens",
"dbaeumer.vscode-eslint",
"GraphQL.vscode-graphql-syntax",
"bierner.comment-tagged-templates",
"YoavBls.pretty-ts-errors",
"svelte.svelte-vscode", // 109.5.2 or older NOT NEWER
"ardenivanov.svelte-intellisense",

View file

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

View file

@ -132,12 +132,7 @@ a {
}
}
.donate {
filter: drop-shadow(0 0 1rem #fa68b6);
animation: glow 1s ease-in-out infinite alternate;
}
@keyframes glow {
@keyframes hearbeat {
from {
transform: translate3d(var(--tw-translate-x), var(--tw-translate-y), 0) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(1) scaleY(1);
}

View file

@ -252,7 +252,7 @@ export default class Subtitles {
lastSubtitleStyle: typeof defaults.subtitleStyle | undefined = undefined
_applyStyleOverride (subtitleStyle: typeof defaults.subtitleStyle) {
if (this.lastSubtitleStyle === subtitleStyle) return
this.lastSubtitleStyle = subtitleStyle
if (this.renderer) this.lastSubtitleStyle = subtitleStyle
if (subtitleStyle !== 'none') {
const font = OVERRIDE_FONTS[subtitleStyle]
if (font && !this.fonts.includes(font)) {

View file

@ -54,7 +54,7 @@
<Download size={18} />
</SidebarButton>
<Button variant='ghost' id='sidebar-donate' data-up='#sidebar-client' on:click={() => native.openURL('https://github.com/sponsors/ThaUnknown/')} class='px-2 w-full relative mt-auto select:!bg-transparent text-[#fa68b6] select:text-[#fa68b6]'>
<Heart size={18} fill='currentColor' class={cn(active && 'donate')} />
<Heart size={18} fill='currentColor' class={cn('drop-shadow-[0_0_1rem_#fa68b6]', active && 'animate-[hearbeat_1s_ease-in-out_infinite_alternate]')} />
</Button>
<SidebarButton href='/app/settings/'>
<Settings size={18} />

View file

@ -0,0 +1,49 @@
<script lang='ts'>
import type { Media } from '$lib/modules/anilist'
import { themes } from '$lib/modules/animethemes'
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
</script>
{#await themesRes}
Loading
{:then themes}
{#if themes?.anime?.[0]?.animethemes?.length}
{#each themes.anime[0].animethemes as theme (theme.id)}
{theme.type}
{/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>
</div>

View file

@ -0,0 +1 @@
export { default as Themes } from './Themes.svelte'

View file

@ -0,0 +1,7 @@
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`)
}

84
src/lib/modules/animethemes/types.d.ts vendored Normal file
View file

@ -0,0 +1,84 @@
export interface AnimeThemesResponse {
anime?: Anime[]
links?: Links
meta?: Meta
}
export interface Anime {
id?: number
name?: string
media_format?: string
season?: string
slug?: string
synopsis?: string
year?: number
animethemes?: AnimeTheme[]
}
export interface AnimeTheme {
id?: number
sequence?: null
slug?: string
type?: string
song?: Song
animethemeentries?: AnimeThemeEntry[]
}
export interface AnimeThemeEntry {
id?: number
episodes?: string
notes?: null
nsfw?: boolean
spoiler?: boolean
version?: null
videos?: Video[]
}
export interface Video {
id?: number
basename?: string
tags?: string
link?: string
audio?: Audio
}
export interface Audio {
id?: number
basename?: string
size?: number
link?: string
}
export interface Song {
id?: number
title?: string
artists?: Artist[]
}
export interface Artist {
id?: number
name?: string
slug?: string
information?: null
artistsong?: ArtistSong
}
export interface ArtistSong {
alias?: null
as?: null
}
export interface Links {
first?: string
last?: null
prev?: null
next?: null
}
export interface Meta {
current_page?: number
from?: number
path?: string
per_page?: number
to?: number
}

View file

@ -1,13 +1,6 @@
import type { EpisodesResponse, MappingsResponse } from './types'
const safefetch = async <T> (_fetch: typeof fetch, ...args: Parameters<typeof fetch>): Promise<T | null> => {
try {
const res = await _fetch(...args)
return await res.json()
} catch (e) {
return null
}
}
import { safefetch } from '$lib/utils'
// const episodes = safefetch<EpisodesResponse>(`https://hayase.ani.zip/v1/episodes?anilist_id=${params.id}`)
// const mappings = safefetch<MappingsResponse>(fetch, `https://hayase.ani.zip/v1/mappings?anilist_id=${params.id}`)

View file

@ -9,8 +9,9 @@ export function progress ({ mediaListEntry, id }: Pick<Media, 'mediaListEntry' |
return mediaListEntry.progress
}
export function fav (media: Pick<Media, 'isFavourite' | 'id'>): boolean {
return media.isFavourite || (local.get(media.id)?.isFavourite ?? false)
export function fav (media: Pick<Media, 'mediaListEntry' | 'isFavourite' | 'id'>): boolean {
// TODO: idk how to handle this properly
return media.mediaListEntry ? media.isFavourite : (local.get(media.id)?.isFavourite ?? false)
}
export function list (media: Pick<Media, 'mediaListEntry' | 'id'>): 'CURRENT' | 'PLANNING' | 'COMPLETED' | 'DROPPED' | 'PAUSED' | 'REPEATING' | null | undefined {

View file

@ -88,8 +88,8 @@ export default Object.assign<Native, Partial<Native>>({
setZoom: async () => undefined,
// @ts-expect-error yeah
navigate: async (cb) => { globalThis.___navigate = cb },
downloadProgress: async (percent: number) => undefined,
updateProgress: async (cb: (progress: number) => void) => undefined,
downloadProgress: async () => undefined,
updateProgress: async () => undefined,
torrentStats: async (): Promise<TorrentInfo> => ({ peers: rnd(), seeders: rnd(), leechers: rnd(), progress: Math.random(), down: rnd(100000000), up: rnd(100000000), name: 'Amebku.webm', downloaded: rnd(100000), hash: '1234567890abcdef', size: 1234567890, eta: rnd() }),
torrents: async (): Promise<TorrentInfo[]> => [{ peers: rnd(), seeders: rnd(), leechers: rnd(), progress: Math.random(), down: rnd(100000000), up: rnd(100000000), name: 'Amebku.webm', downloaded: rnd(100000), hash: '1234567890abcdef', size: 1234567890, eta: rnd() }]
// @ts-expect-error idk

View file

@ -266,3 +266,12 @@ export const videoRx = new RegExp(`.(${videoExtensions.join('|')})$`, 'i')
export const fontExtensions = ['ttf', 'ttc', 'woff', 'woff2', 'otf', 'cff', 'otc', 'pfa', 'pfb', 'pcf', 'fnt', 'bdf', 'pfr', 'eot']
export const fontRx = new RegExp(`.(${fontExtensions.join('|')})$`, 'i')
export const safefetch = async <T> (_fetch: typeof fetch, ...args: Parameters<typeof fetch>): Promise<T | null> => {
try {
const res = await _fetch(...args)
return await res.json()
} catch (e) {
return null
}
}

View file

@ -6,6 +6,7 @@
import { Threads } from '$lib/components/ui/forums'
import { Load } from '$lib/components/ui/img'
import * as Tabs from '$lib/components/ui/tabs'
import { Themes } from '$lib/components/ui/themes'
import { format, relation } from '$lib/modules/anilist'
import { authAggregator } from '$lib/modules/auth'
import { dragScroll } from '$lib/modules/navigate'
@ -77,4 +78,11 @@
{/if}
{/key}
</Tabs.Content>
<Tabs.Content value='themes' tabindex={-1}>
{#key mediaId}
{#if value === 'themes'}
<Themes {media} />
{/if}
{/key}
</Tabs.Content>
</Tabs.Root>

View file

@ -12,7 +12,10 @@
<div class='space-y-3 lg:max-w-4xl h-full overflow-y-auto w-full py-8 flex flex-col items-center justify-center' use:dragScroll>
<Logo class='w-52 h-52 object-contain mb-14 shrink-0' />
<div class='font-bold text-5xl'>Welcome to Hayase</div>
<div class='font-bold text-5xl relative'>
Welcome to Hayase
<div class='animate-[hearbeat_1.5s_ease-in-out_infinite_alternate] absolute text-lg text-theme -right-20 -top-2 rotate-12'>Previously known as Miru!</div>
</div>
<div class='text-muted-foreground pt-3'>Let's set up your perfect streaming environment.</div>
<div class='flex items-center space-x-2 pt-12 pb-3'>
<Checkbox id='terms' bind:checked />