mirror of
https://github.com/ThaUnknown/miru.git
synced 2026-04-21 07:02:01 +00:00
feat: previously known as miru on first setup page
fix: initial subtitle override state wip: animethemes
This commit is contained in:
parent
d632ad5b49
commit
bc35557ebd
15 changed files with 173 additions and 22 deletions
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
|
|
@ -3,6 +3,7 @@
|
||||||
"usernamehw.errorlens",
|
"usernamehw.errorlens",
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"GraphQL.vscode-graphql-syntax",
|
"GraphQL.vscode-graphql-syntax",
|
||||||
|
"bierner.comment-tagged-templates",
|
||||||
"YoavBls.pretty-ts-errors",
|
"YoavBls.pretty-ts-errors",
|
||||||
"svelte.svelte-vscode", // 109.5.2 or older NOT NEWER
|
"svelte.svelte-vscode", // 109.5.2 or older NOT NEWER
|
||||||
"ardenivanov.svelte-intellisense",
|
"ardenivanov.svelte-intellisense",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "ui",
|
"name": "ui",
|
||||||
"version": "6.3.33",
|
"version": "6.3.34",
|
||||||
"license": "BUSL-1.1",
|
"license": "BUSL-1.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@9.14.4",
|
"packageManager": "pnpm@9.14.4",
|
||||||
|
|
|
||||||
|
|
@ -132,12 +132,7 @@ a {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.donate {
|
@keyframes hearbeat {
|
||||||
filter: drop-shadow(0 0 1rem #fa68b6);
|
|
||||||
animation: glow 1s ease-in-out infinite alternate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes glow {
|
|
||||||
from {
|
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);
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,7 @@ export default class Subtitles {
|
||||||
lastSubtitleStyle: typeof defaults.subtitleStyle | undefined = undefined
|
lastSubtitleStyle: typeof defaults.subtitleStyle | undefined = undefined
|
||||||
_applyStyleOverride (subtitleStyle: typeof defaults.subtitleStyle) {
|
_applyStyleOverride (subtitleStyle: typeof defaults.subtitleStyle) {
|
||||||
if (this.lastSubtitleStyle === subtitleStyle) return
|
if (this.lastSubtitleStyle === subtitleStyle) return
|
||||||
this.lastSubtitleStyle = subtitleStyle
|
if (this.renderer) this.lastSubtitleStyle = subtitleStyle
|
||||||
if (subtitleStyle !== 'none') {
|
if (subtitleStyle !== 'none') {
|
||||||
const font = OVERRIDE_FONTS[subtitleStyle]
|
const font = OVERRIDE_FONTS[subtitleStyle]
|
||||||
if (font && !this.fonts.includes(font)) {
|
if (font && !this.fonts.includes(font)) {
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
<Download size={18} />
|
<Download size={18} />
|
||||||
</SidebarButton>
|
</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]'>
|
<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>
|
</Button>
|
||||||
<SidebarButton href='/app/settings/'>
|
<SidebarButton href='/app/settings/'>
|
||||||
<Settings size={18} />
|
<Settings size={18} />
|
||||||
|
|
|
||||||
49
src/lib/components/ui/themes/Themes.svelte
Normal file
49
src/lib/components/ui/themes/Themes.svelte
Normal 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>
|
||||||
1
src/lib/components/ui/themes/index.ts
Normal file
1
src/lib/components/ui/themes/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as Themes } from './Themes.svelte'
|
||||||
7
src/lib/modules/animethemes/index.ts
Normal file
7
src/lib/modules/animethemes/index.ts
Normal 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
84
src/lib/modules/animethemes/types.d.ts
vendored
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,6 @@
|
||||||
import type { EpisodesResponse, MappingsResponse } from './types'
|
import type { EpisodesResponse, MappingsResponse } from './types'
|
||||||
|
|
||||||
const safefetch = async <T> (_fetch: typeof fetch, ...args: Parameters<typeof fetch>): Promise<T | null> => {
|
import { safefetch } from '$lib/utils'
|
||||||
try {
|
|
||||||
const res = await _fetch(...args)
|
|
||||||
return await res.json()
|
|
||||||
} catch (e) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// const episodes = safefetch<EpisodesResponse>(`https://hayase.ani.zip/v1/episodes?anilist_id=${params.id}`)
|
// 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}`)
|
// const mappings = safefetch<MappingsResponse>(fetch, `https://hayase.ani.zip/v1/mappings?anilist_id=${params.id}`)
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,9 @@ export function progress ({ mediaListEntry, id }: Pick<Media, 'mediaListEntry' |
|
||||||
return mediaListEntry.progress
|
return mediaListEntry.progress
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fav (media: Pick<Media, 'isFavourite' | 'id'>): boolean {
|
export function fav (media: Pick<Media, 'mediaListEntry' | 'isFavourite' | 'id'>): boolean {
|
||||||
return media.isFavourite || (local.get(media.id)?.isFavourite ?? false)
|
// 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 {
|
export function list (media: Pick<Media, 'mediaListEntry' | 'id'>): 'CURRENT' | 'PLANNING' | 'COMPLETED' | 'DROPPED' | 'PAUSED' | 'REPEATING' | null | undefined {
|
||||||
|
|
|
||||||
|
|
@ -88,8 +88,8 @@ export default Object.assign<Native, Partial<Native>>({
|
||||||
setZoom: async () => undefined,
|
setZoom: async () => undefined,
|
||||||
// @ts-expect-error yeah
|
// @ts-expect-error yeah
|
||||||
navigate: async (cb) => { globalThis.___navigate = cb },
|
navigate: async (cb) => { globalThis.___navigate = cb },
|
||||||
downloadProgress: async (percent: number) => undefined,
|
downloadProgress: async () => undefined,
|
||||||
updateProgress: async (cb: (progress: number) => void) => 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() }),
|
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() }]
|
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
|
// @ts-expect-error idk
|
||||||
|
|
|
||||||
|
|
@ -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 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 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
import { Threads } from '$lib/components/ui/forums'
|
import { Threads } from '$lib/components/ui/forums'
|
||||||
import { Load } from '$lib/components/ui/img'
|
import { Load } from '$lib/components/ui/img'
|
||||||
import * as Tabs from '$lib/components/ui/tabs'
|
import * as Tabs from '$lib/components/ui/tabs'
|
||||||
|
import { Themes } from '$lib/components/ui/themes'
|
||||||
import { format, relation } from '$lib/modules/anilist'
|
import { format, relation } from '$lib/modules/anilist'
|
||||||
import { authAggregator } from '$lib/modules/auth'
|
import { authAggregator } from '$lib/modules/auth'
|
||||||
import { dragScroll } from '$lib/modules/navigate'
|
import { dragScroll } from '$lib/modules/navigate'
|
||||||
|
|
@ -77,4 +78,11 @@
|
||||||
{/if}
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
|
<Tabs.Content value='themes' tabindex={-1}>
|
||||||
|
{#key mediaId}
|
||||||
|
{#if value === 'themes'}
|
||||||
|
<Themes {media} />
|
||||||
|
{/if}
|
||||||
|
{/key}
|
||||||
|
</Tabs.Content>
|
||||||
</Tabs.Root>
|
</Tabs.Root>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
<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' />
|
<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='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'>
|
<div class='flex items-center space-x-2 pt-12 pb-3'>
|
||||||
<Checkbox id='terms' bind:checked />
|
<Checkbox id='terms' bind:checked />
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue