Files
youtube-music/src/plugins/synced-lyrics/renderer/components/SyncedLine.tsx
2025-09-06 04:51:31 +09:00

134 lines
3.7 KiB
TypeScript

import { createEffect, createMemo, For, Show, createSignal } from 'solid-js';
import { type VirtualizerHandle } from 'virtua/solid';
import { type LineLyrics } from '@/plugins/synced-lyrics/types';
import { config } from '../renderer';
import { _ytAPI } from '..';
import { canonicalize, romanize, simplifyUnicode } from '../utils';
interface SyncedLineProps {
scroller: VirtualizerHandle;
index: number;
line: LineLyrics;
status: 'upcoming' | 'current' | 'previous';
}
export const SyncedLine = (props: SyncedLineProps) => {
const text = createMemo(() => {
if (!props.line.text.trim()) {
return config()?.defaultTextString ?? '';
}
return props.line.text;
});
const [romanization, setRomanization] = createSignal('');
createEffect(() => {
if (!config()?.romanization) return;
const input = canonicalize(text());
romanize(input).then((result) => {
setRomanization(canonicalize(result));
});
});
return (
<Show
fallback={
<yt-formatted-string
text={{
runs: [{ text: '' }],
}}
/>
}
when={text()}
>
<div
class={`synced-line ${props.status}`}
onClick={() => {
_ytAPI?.seekTo((props.line.timeInMs + 10) / 1000);
}}
>
<div class="description ytmusic-description-shelf-renderer" dir="auto">
<yt-formatted-string
text={{
runs: [
{
text: config()?.showTimeCodes ? `[${props.line.time}] ` : '',
},
],
}}
/>
<div
class="text-lyrics"
ref={(div: HTMLDivElement) => {
// TODO: Investigate the animation, even though the duration is properly set, all lines have the same animation duration
div.style.setProperty(
'--lyrics-duration',
`${props.line.duration / 1000}s`,
'important',
);
}}
style={{ 'display': 'flex', 'flex-direction': 'column' }}
>
<span>
<For each={text().split(' ')}>
{(word, index) => {
return (
<span
style={{
'transition-delay': `${index() * 0.05}s`,
'animation-delay': `${index() * 0.05}s`,
}}
>
<yt-formatted-string
text={{
runs: [{ text: `${word} ` }],
}}
/>
</span>
);
}}
</For>
</span>
<Show
when={
config()?.romanization &&
simplifyUnicode(text()) !== simplifyUnicode(romanization())
}
>
<span class="romaji">
<For each={romanization().split(' ')}>
{(word, index) => {
return (
<span
style={{
'transition-delay': `${index() * 0.05}s`,
'animation-delay': `${index() * 0.05}s`,
}}
>
<yt-formatted-string
text={{
runs: [{ text: `${word} ` }],
}}
/>
</span>
);
}}
</For>
</span>
</Show>
</div>
</div>
</div>
</Show>
);
};