[+] Lyrics display
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
let { icon, children, ...rest }: {
|
||||
icon?: string
|
||||
children?: any
|
||||
[key: string]: any
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user