mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
add sidebar panel lyrics
This commit is contained in:
@@ -781,6 +781,8 @@
|
|||||||
"playerbarWaveformRadius": "waveform radius",
|
"playerbarWaveformRadius": "waveform radius",
|
||||||
"preferLocalLyrics_description": "prefer local lyrics over remote lyrics when available",
|
"preferLocalLyrics_description": "prefer local lyrics over remote lyrics when available",
|
||||||
"preferLocalLyrics": "prefer local lyrics",
|
"preferLocalLyrics": "prefer local lyrics",
|
||||||
|
"showLyricsInSidebar_description": "a panel will be added to the attached play queue that displays the lyrics",
|
||||||
|
"showLyricsInSidebar": "show lyrics in attached play queue",
|
||||||
"preservePitch_description": "preserves pitch when modifying playback speed",
|
"preservePitch_description": "preserves pitch when modifying playback speed",
|
||||||
"preservePitch": "preserve pitch",
|
"preservePitch": "preserve pitch",
|
||||||
"preventSleepOnPlayback_description": "prevent the display from sleeping while music is playing",
|
"preventSleepOnPlayback_description": "prevent the display from sleeping while music is playing",
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
import styles from './synchronized-lyrics.module.css';
|
import styles from './synchronized-lyrics.module.css';
|
||||||
|
|
||||||
import { LyricLine } from '/@/renderer/features/lyrics/lyric-line';
|
import { LyricLine } from '/@/renderer/features/lyrics/lyric-line';
|
||||||
import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events';
|
import {
|
||||||
import { useLyricsSettings, usePlaybackType, usePlayerActions } from '/@/renderer/store';
|
useLyricsSettings,
|
||||||
|
usePlaybackType,
|
||||||
|
usePlayerActions,
|
||||||
|
usePlayerStatus,
|
||||||
|
} from '/@/renderer/store';
|
||||||
|
import { usePlayerTimestamp } from '/@/renderer/store/timestamp.store';
|
||||||
import { FullLyricsMetadata, SynchronizedLyricsArray } from '/@/shared/types/domain-types';
|
import { FullLyricsMetadata, SynchronizedLyricsArray } from '/@/shared/types/domain-types';
|
||||||
import { PlayerStatus, PlayerType } from '/@/shared/types/types';
|
import { PlayerStatus, PlayerType } from '/@/shared/types/types';
|
||||||
|
|
||||||
@@ -30,10 +35,8 @@ export const SynchronizedLyrics = ({
|
|||||||
const playbackType = usePlaybackType();
|
const playbackType = usePlaybackType();
|
||||||
const settings = useLyricsSettings();
|
const settings = useLyricsSettings();
|
||||||
const { mediaSeekToTimestamp } = usePlayerActions();
|
const { mediaSeekToTimestamp } = usePlayerActions();
|
||||||
|
const status = usePlayerStatus();
|
||||||
// State for player status and timestamp from events
|
const timestamp = usePlayerTimestamp();
|
||||||
const [status, setStatus] = useState<PlayerStatus>(PlayerStatus.PAUSED);
|
|
||||||
const [timestamp, setTimestamp] = useState<number>(0);
|
|
||||||
|
|
||||||
const handleSeek = useCallback(
|
const handleSeek = useCallback(
|
||||||
(time: number) => {
|
(time: number) => {
|
||||||
@@ -154,23 +157,6 @@ export const SynchronizedLyrics = ({
|
|||||||
setCurrentLyricRef.current = setCurrentLyric;
|
setCurrentLyricRef.current = setCurrentLyric;
|
||||||
}, [setCurrentLyric]);
|
}, [setCurrentLyric]);
|
||||||
|
|
||||||
// Subscribe to player events
|
|
||||||
usePlayerEvents(
|
|
||||||
{
|
|
||||||
onPlayerProgress: (properties) => {
|
|
||||||
setTimestamp(properties.timestamp);
|
|
||||||
},
|
|
||||||
onPlayerSeekToTimestamp: (properties) => {
|
|
||||||
// When seeking, update timestamp immediately
|
|
||||||
setTimestamp(properties.timestamp);
|
|
||||||
},
|
|
||||||
onPlayerStatus: (properties) => {
|
|
||||||
setStatus(properties.status);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Copy the follow settings into a ref that can be accessed in the timeout
|
// Copy the follow settings into a ref that can be accessed in the timeout
|
||||||
followRef.current = settings.follow;
|
followRef.current = settings.follow;
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
.play-queue-section {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lyrics-section {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 300px;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 200px;
|
||||||
|
max-height: 400px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--theme-colors-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lyrics-section :global(.synchronized-lyrics) {
|
||||||
|
padding: 2rem 0 4rem !important;
|
||||||
|
transform: translateY(0) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lyrics-section :global(.synchronized-lyrics .lyric-line) {
|
||||||
|
padding: 0.25rem 0;
|
||||||
|
}
|
||||||
@@ -1,14 +1,21 @@
|
|||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import styles from './sidebar-play-queue.module.css';
|
||||||
|
|
||||||
import { ItemListHandle } from '/@/renderer/components/item-list/types';
|
import { ItemListHandle } from '/@/renderer/components/item-list/types';
|
||||||
|
import { Lyrics } from '/@/renderer/features/lyrics/lyrics';
|
||||||
import { PlayQueue } from '/@/renderer/features/now-playing/components/play-queue';
|
import { PlayQueue } from '/@/renderer/features/now-playing/components/play-queue';
|
||||||
import { PlayQueueListControls } from '/@/renderer/features/now-playing/components/play-queue-list-controls';
|
import { PlayQueueListControls } from '/@/renderer/features/now-playing/components/play-queue-list-controls';
|
||||||
|
import { useLyricsSettings } from '/@/renderer/store';
|
||||||
|
import { Divider } from '/@/shared/components/divider/divider';
|
||||||
|
import { Flex } from '/@/shared/components/flex/flex';
|
||||||
import { Stack } from '/@/shared/components/stack/stack';
|
import { Stack } from '/@/shared/components/stack/stack';
|
||||||
import { ItemListKey } from '/@/shared/types/types';
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
|
||||||
export const SidebarPlayQueue = () => {
|
export const SidebarPlayQueue = () => {
|
||||||
const tableRef = useRef<ItemListHandle | null>(null);
|
const tableRef = useRef<ItemListHandle | null>(null);
|
||||||
const [search, setSearch] = useState<string | undefined>(undefined);
|
const [search, setSearch] = useState<string | undefined>(undefined);
|
||||||
|
const { showLyricsInSidebar } = useLyricsSettings();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap={0} h="100%" id="sidebar-play-queue-container" pos="relative" w="100%">
|
<Stack gap={0} h="100%" id="sidebar-play-queue-container" pos="relative" w="100%">
|
||||||
@@ -18,7 +25,23 @@ export const SidebarPlayQueue = () => {
|
|||||||
tableRef={tableRef}
|
tableRef={tableRef}
|
||||||
type={ItemListKey.SIDE_QUEUE}
|
type={ItemListKey.SIDE_QUEUE}
|
||||||
/>
|
/>
|
||||||
<PlayQueue listKey={ItemListKey.SIDE_QUEUE} ref={tableRef} searchTerm={search} />
|
<Flex direction="column" style={{ flex: 1, minHeight: 0 }}>
|
||||||
|
<div className={styles.playQueueSection}>
|
||||||
|
<PlayQueue
|
||||||
|
listKey={ItemListKey.SIDE_QUEUE}
|
||||||
|
ref={tableRef}
|
||||||
|
searchTerm={search}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{showLyricsInSidebar && (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<div className={styles.lyricsSection}>
|
||||||
|
<Lyrics />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -43,6 +43,27 @@ export const LyricSettings = () => {
|
|||||||
}),
|
}),
|
||||||
title: t('setting.followLyric', { postProcess: 'sentenceCase' }),
|
title: t('setting.followLyric', { postProcess: 'sentenceCase' }),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
aria-label="Show lyrics in attached play queue"
|
||||||
|
defaultChecked={settings.showLyricsInSidebar}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSettings({
|
||||||
|
lyrics: {
|
||||||
|
...settings,
|
||||||
|
showLyricsInSidebar: e.currentTarget.checked,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.showLyricsInSidebar', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
title: t('setting.showLyricsInSidebar', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
control: (
|
control: (
|
||||||
<Switch
|
<Switch
|
||||||
|
|||||||
@@ -285,6 +285,7 @@ const LyricsSettingsSchema = z.object({
|
|||||||
gap: z.number(),
|
gap: z.number(),
|
||||||
gapUnsync: z.number(),
|
gapUnsync: z.number(),
|
||||||
preferLocalLyrics: z.boolean(),
|
preferLocalLyrics: z.boolean(),
|
||||||
|
showLyricsInSidebar: z.boolean(),
|
||||||
showMatch: z.boolean(),
|
showMatch: z.boolean(),
|
||||||
showProvider: z.boolean(),
|
showProvider: z.boolean(),
|
||||||
sources: z.array(z.nativeEnum(LyricSource)),
|
sources: z.array(z.nativeEnum(LyricSource)),
|
||||||
@@ -728,7 +729,7 @@ const initialState: SettingsState = {
|
|||||||
}),
|
}),
|
||||||
enableAlternateRowColors: false,
|
enableAlternateRowColors: false,
|
||||||
enableHorizontalBorders: false,
|
enableHorizontalBorders: false,
|
||||||
enableRowHoverHighlight: false,
|
enableRowHoverHighlight: true,
|
||||||
enableVerticalBorders: false,
|
enableVerticalBorders: false,
|
||||||
size: 'compact',
|
size: 'compact',
|
||||||
},
|
},
|
||||||
@@ -753,8 +754,8 @@ const initialState: SettingsState = {
|
|||||||
pinned: column.pinned,
|
pinned: column.pinned,
|
||||||
width: column.width,
|
width: column.width,
|
||||||
})),
|
})),
|
||||||
enableAlternateRowColors: true,
|
enableAlternateRowColors: false,
|
||||||
enableHorizontalBorders: true,
|
enableHorizontalBorders: false,
|
||||||
enableRowHoverHighlight: true,
|
enableRowHoverHighlight: true,
|
||||||
enableVerticalBorders: false,
|
enableVerticalBorders: false,
|
||||||
size: 'default',
|
size: 'default',
|
||||||
@@ -801,8 +802,8 @@ const initialState: SettingsState = {
|
|||||||
pinned: column.pinned,
|
pinned: column.pinned,
|
||||||
width: column.width,
|
width: column.width,
|
||||||
})),
|
})),
|
||||||
enableAlternateRowColors: true,
|
enableAlternateRowColors: false,
|
||||||
enableHorizontalBorders: true,
|
enableHorizontalBorders: false,
|
||||||
enableRowHoverHighlight: true,
|
enableRowHoverHighlight: true,
|
||||||
enableVerticalBorders: false,
|
enableVerticalBorders: false,
|
||||||
size: 'default',
|
size: 'default',
|
||||||
@@ -838,8 +839,8 @@ const initialState: SettingsState = {
|
|||||||
pinned: column.pinned,
|
pinned: column.pinned,
|
||||||
width: column.width,
|
width: column.width,
|
||||||
})),
|
})),
|
||||||
enableAlternateRowColors: true,
|
enableAlternateRowColors: false,
|
||||||
enableHorizontalBorders: true,
|
enableHorizontalBorders: false,
|
||||||
enableRowHoverHighlight: true,
|
enableRowHoverHighlight: true,
|
||||||
enableVerticalBorders: false,
|
enableVerticalBorders: false,
|
||||||
size: 'default',
|
size: 'default',
|
||||||
@@ -875,8 +876,8 @@ const initialState: SettingsState = {
|
|||||||
pinned: column.pinned,
|
pinned: column.pinned,
|
||||||
width: column.width,
|
width: column.width,
|
||||||
})),
|
})),
|
||||||
enableAlternateRowColors: true,
|
enableAlternateRowColors: false,
|
||||||
enableHorizontalBorders: true,
|
enableHorizontalBorders: false,
|
||||||
enableRowHoverHighlight: true,
|
enableRowHoverHighlight: true,
|
||||||
enableVerticalBorders: false,
|
enableVerticalBorders: false,
|
||||||
size: 'default',
|
size: 'default',
|
||||||
@@ -919,8 +920,8 @@ const initialState: SettingsState = {
|
|||||||
pinned: column.pinned,
|
pinned: column.pinned,
|
||||||
width: column.width,
|
width: column.width,
|
||||||
})),
|
})),
|
||||||
enableAlternateRowColors: true,
|
enableAlternateRowColors: false,
|
||||||
enableHorizontalBorders: true,
|
enableHorizontalBorders: false,
|
||||||
enableRowHoverHighlight: true,
|
enableRowHoverHighlight: true,
|
||||||
enableVerticalBorders: false,
|
enableVerticalBorders: false,
|
||||||
size: 'compact',
|
size: 'compact',
|
||||||
@@ -951,8 +952,8 @@ const initialState: SettingsState = {
|
|||||||
pinned: column.pinned,
|
pinned: column.pinned,
|
||||||
width: column.width,
|
width: column.width,
|
||||||
})),
|
})),
|
||||||
enableAlternateRowColors: true,
|
enableAlternateRowColors: false,
|
||||||
enableHorizontalBorders: true,
|
enableHorizontalBorders: false,
|
||||||
enableRowHoverHighlight: true,
|
enableRowHoverHighlight: true,
|
||||||
enableVerticalBorders: false,
|
enableVerticalBorders: false,
|
||||||
size: 'default',
|
size: 'default',
|
||||||
@@ -978,8 +979,8 @@ const initialState: SettingsState = {
|
|||||||
pinned: column.pinned,
|
pinned: column.pinned,
|
||||||
width: column.width,
|
width: column.width,
|
||||||
})),
|
})),
|
||||||
enableAlternateRowColors: true,
|
enableAlternateRowColors: false,
|
||||||
enableHorizontalBorders: true,
|
enableHorizontalBorders: false,
|
||||||
enableRowHoverHighlight: true,
|
enableRowHoverHighlight: true,
|
||||||
enableVerticalBorders: false,
|
enableVerticalBorders: false,
|
||||||
size: 'default',
|
size: 'default',
|
||||||
@@ -1005,8 +1006,8 @@ const initialState: SettingsState = {
|
|||||||
pinned: column.pinned,
|
pinned: column.pinned,
|
||||||
width: column.width,
|
width: column.width,
|
||||||
})),
|
})),
|
||||||
enableAlternateRowColors: true,
|
enableAlternateRowColors: false,
|
||||||
enableHorizontalBorders: true,
|
enableHorizontalBorders: false,
|
||||||
enableRowHoverHighlight: true,
|
enableRowHoverHighlight: true,
|
||||||
enableVerticalBorders: false,
|
enableVerticalBorders: false,
|
||||||
size: 'default',
|
size: 'default',
|
||||||
@@ -1050,8 +1051,8 @@ const initialState: SettingsState = {
|
|||||||
pinned: column.pinned,
|
pinned: column.pinned,
|
||||||
width: column.width,
|
width: column.width,
|
||||||
})),
|
})),
|
||||||
enableAlternateRowColors: true,
|
enableAlternateRowColors: false,
|
||||||
enableHorizontalBorders: true,
|
enableHorizontalBorders: false,
|
||||||
enableRowHoverHighlight: true,
|
enableRowHoverHighlight: true,
|
||||||
enableVerticalBorders: false,
|
enableVerticalBorders: false,
|
||||||
size: 'default',
|
size: 'default',
|
||||||
@@ -1092,16 +1093,17 @@ const initialState: SettingsState = {
|
|||||||
delayMs: 0,
|
delayMs: 0,
|
||||||
enableAutoTranslation: false,
|
enableAutoTranslation: false,
|
||||||
enableNeteaseTranslation: false,
|
enableNeteaseTranslation: false,
|
||||||
fetch: false,
|
fetch: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontSizeUnsync: 24,
|
fontSizeUnsync: 24,
|
||||||
gap: 24,
|
gap: 24,
|
||||||
gapUnsync: 24,
|
gapUnsync: 24,
|
||||||
preferLocalLyrics: true,
|
preferLocalLyrics: true,
|
||||||
|
showLyricsInSidebar: true,
|
||||||
showMatch: true,
|
showMatch: true,
|
||||||
showProvider: true,
|
showProvider: true,
|
||||||
sources: [LyricSource.NETEASE, LyricSource.LRCLIB],
|
sources: [LyricSource.NETEASE],
|
||||||
translationApiKey: '',
|
translationApiKey: '',
|
||||||
translationApiProvider: '',
|
translationApiProvider: '',
|
||||||
translationTargetLanguage: 'en',
|
translationTargetLanguage: 'en',
|
||||||
|
|||||||
Reference in New Issue
Block a user