[+] Lyrics display

This commit is contained in:
2025-11-19 11:27:11 +08:00
parent 77e47c410b
commit 7faa03893b
2 changed files with 82 additions and 0 deletions
+1
View File
@@ -2,6 +2,7 @@
let { icon, children, ...rest }: {
icon?: string
children?: any
[key: string]: any
} = $props();
</script>
+81
View File
@@ -1,8 +1,46 @@
<script lang="ts">
import AppBar from "../../../components/appbar/AppBar.svelte";
import type { PageProps } from "./$types"
import { LinearProgress } from "m3-svelte";
import { onMount } from "svelte";
import type { LyricSegment } from "../../../shared/types.ts";
let { data }: PageProps = $props()
// Current word and line index
let wi = $state(0)
let li = $state(0)
let hiddenInput: HTMLInputElement
let inp = $state("")
// Process each line into segments with swi (start word index) and kanji/kana
type ProcLrcSeg = { swi: number, kanji?: string, kana: string }
type ProcLrcLine = { parts: ProcLrcSeg[], totalLen: number }
function processLrcLine(line: LyricSegment[]): ProcLrcLine {
let result: any[] = line.map(part => (typeof part === "string" ? { kana: part } : { kanji: part[0], kana: part[1] }))
let swi = 0
for (let item of result) {
item['swi'] = swi
swi += item.kana.length
}
return { parts: result, totalLen: swi }
}
let processedLrc: ProcLrcLine[] = data.lrc.map(line => processLrcLine(line.lyric))
// State tracking for each kana character: UNSEEN, RIGHT, WRONG
let states = $state(processedLrc.map(line => new Array(line.totalLen).fill('unseen')))
const allStates = (l: number, seg: ProcLrcSeg) => states[l].slice(seg.swi, seg.swi + seg.kana.length)
const getKanjiState = (l: number, seg: ProcLrcSeg) => {
let sts = allStates(l, seg)
if (sts.every(s => s === 'right')) return 'right'
if (sts.some(s => s === 'wrong')) return 'wrong'
return 'typing'
}
onMount(() => {
hiddenInput.focus()
})
</script>
<AppBar title={data.brief.name} sub={data.brief.artists.map(a => a.name).join(", ") + " - " + data.brief.album} right={[
@@ -10,3 +48,46 @@
{icon: "i-material-symbols:more-vert", onclick: () => alert('More clicked')}
]} />
<LinearProgress percent={30} />
<input bind:this={hiddenInput} bind:value={inp} style="position: absolute; top: -9999px; left: -9999px;" autofocus/>
<div class="vbox gap-12px py-32px" lang="ja-JP">
{#each processedLrc as line, l}
<div class="lrc p-content text-center m3-font-body-large" class:active={l === li}>
{#each line.parts as seg}
{#if !seg.kanji}
{#each seg.kana as char, c}
<span class="{states[l][seg.swi + c]}">{char}</span>
{/each}
{:else}
<ruby>
<span class="{getKanjiState(l, seg)}">{seg.kanji}</span><rt>
{#each seg.kana as char, c}
<span class="{states[l][seg.swi + c]}">{char}</span>
{/each}
</rt>
</ruby>
{/if}
{/each}
</div>
{/each}
</div>
<style lang="sass">
.lrc
color: #8b8b8b
font-weight: 500
font-size: 20px
&.active
font-size: 24px
color: rgb(var(--m3-scheme-on-surface))
//background-color: rgba(var(--m3-scheme-secondary-container) / 0.5)
.wrong
color: #e55757
background-color: rgba(229, 87, 87, 0.1)
.right
color: #7b78c2
</style>