mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
split lyrics settings by key (#1389)
This commit is contained in:
@@ -569,6 +569,7 @@
|
|||||||
"scrobble": "scrobble",
|
"scrobble": "scrobble",
|
||||||
"audio": "audio",
|
"audio": "audio",
|
||||||
"lyrics": "lyrics",
|
"lyrics": "lyrics",
|
||||||
|
"lyricsDisplay": "lyrics display",
|
||||||
"transcoding": "transcoding",
|
"transcoding": "transcoding",
|
||||||
"discord": "discord",
|
"discord": "discord",
|
||||||
"logger": "logger",
|
"logger": "logger",
|
||||||
|
|||||||
@@ -0,0 +1,388 @@
|
|||||||
|
import isElectron from 'is-electron';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { languages } from '/@/i18n/i18n';
|
||||||
|
import {
|
||||||
|
SettingOption,
|
||||||
|
SettingsSection,
|
||||||
|
} from '/@/renderer/features/settings/components/settings-section';
|
||||||
|
import {
|
||||||
|
useLyricsDisplaySettings,
|
||||||
|
useLyricsSettings,
|
||||||
|
useSettingsStore,
|
||||||
|
useSettingsStoreActions,
|
||||||
|
} from '/@/renderer/store';
|
||||||
|
import { Fieldset } from '/@/shared/components/fieldset/fieldset';
|
||||||
|
import { MultiSelect } from '/@/shared/components/multi-select/multi-select';
|
||||||
|
import { NumberInput } from '/@/shared/components/number-input/number-input';
|
||||||
|
import { SegmentedControl } from '/@/shared/components/segmented-control/segmented-control';
|
||||||
|
import { Select } from '/@/shared/components/select/select';
|
||||||
|
import { Stack } from '/@/shared/components/stack/stack';
|
||||||
|
import { Switch } from '/@/shared/components/switch/switch';
|
||||||
|
import { TextInput } from '/@/shared/components/text-input/text-input';
|
||||||
|
import { Text } from '/@/shared/components/text/text';
|
||||||
|
import { LyricSource } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
const localSettings = isElectron() ? window.api.localSettings : null;
|
||||||
|
|
||||||
|
interface LyricsSettingsFormProps {
|
||||||
|
settingsKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LyricsSettingsForm = ({ settingsKey }: LyricsSettingsFormProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const lyricsSettings = useLyricsSettings();
|
||||||
|
const displaySettings = useLyricsDisplaySettings(settingsKey);
|
||||||
|
const allLyricsDisplay = useSettingsStore((state) => state.lyricsDisplay);
|
||||||
|
const { setSettings } = useSettingsStoreActions();
|
||||||
|
|
||||||
|
const updateLyricsSetting = (updates: Partial<typeof lyricsSettings>) => {
|
||||||
|
setSettings({
|
||||||
|
lyrics: {
|
||||||
|
...lyricsSettings,
|
||||||
|
...updates,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateDisplaySetting = (updates: Partial<typeof displaySettings>) => {
|
||||||
|
setSettings({
|
||||||
|
lyricsDisplay: {
|
||||||
|
...allLyricsDisplay,
|
||||||
|
[settingsKey]: {
|
||||||
|
...displaySettings,
|
||||||
|
...updates,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const displayOptions: SettingOption[] = [
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<NumberInput
|
||||||
|
onBlur={(e) => {
|
||||||
|
const value = Number(e.currentTarget.value);
|
||||||
|
updateDisplaySetting({ fontSize: value });
|
||||||
|
}}
|
||||||
|
rightSection={
|
||||||
|
<Text pr="md" size="sm">
|
||||||
|
px
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
step={1}
|
||||||
|
value={displaySettings.fontSize}
|
||||||
|
width={100}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: '',
|
||||||
|
title: t(
|
||||||
|
`${t('page.fullscreenPlayer.config.lyricSize')} (${t('page.fullscreenPlayer.config.synchronized')})`,
|
||||||
|
{ postProcess: 'sentenceCase' },
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<NumberInput
|
||||||
|
onBlur={(e) => {
|
||||||
|
const value = Number(e.currentTarget.value);
|
||||||
|
updateDisplaySetting({ fontSizeUnsync: value });
|
||||||
|
}}
|
||||||
|
rightSection={
|
||||||
|
<Text pr="md" size="sm">
|
||||||
|
px
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
step={1}
|
||||||
|
value={displaySettings.fontSizeUnsync}
|
||||||
|
width={100}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: '',
|
||||||
|
title: t(
|
||||||
|
`${t('page.fullscreenPlayer.config.lyricSize')} (${t('page.fullscreenPlayer.config.unsynchronized')})`,
|
||||||
|
{ postProcess: 'sentenceCase' },
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<NumberInput
|
||||||
|
onBlur={(e) => {
|
||||||
|
const value = Number(e.currentTarget.value);
|
||||||
|
updateDisplaySetting({ gap: value });
|
||||||
|
}}
|
||||||
|
rightSection={
|
||||||
|
<Text pr="md" size="sm">
|
||||||
|
px
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
step={1}
|
||||||
|
value={displaySettings.gap}
|
||||||
|
width={100}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: '',
|
||||||
|
title: t(
|
||||||
|
`${t('page.fullscreenPlayer.config.lyricGap')} (${t('page.fullscreenPlayer.config.synchronized')})`,
|
||||||
|
{ postProcess: 'sentenceCase' },
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<NumberInput
|
||||||
|
onBlur={(e) => {
|
||||||
|
const value = Number(e.currentTarget.value);
|
||||||
|
updateDisplaySetting({ gapUnsync: value });
|
||||||
|
}}
|
||||||
|
rightSection={
|
||||||
|
<Text pr="md" size="sm">
|
||||||
|
px
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
step={1}
|
||||||
|
value={displaySettings.gapUnsync}
|
||||||
|
width={100}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: '',
|
||||||
|
title: t(
|
||||||
|
`${t('page.fullscreenPlayer.config.lyricGap')} (${t('page.fullscreenPlayer.config.unsynchronized')})`,
|
||||||
|
{ postProcess: 'sentenceCase' },
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<SegmentedControl
|
||||||
|
data={[
|
||||||
|
{ label: t('common.left', { postProcess: 'titleCase' }), value: 'left' },
|
||||||
|
{
|
||||||
|
label: t('common.center', { postProcess: 'titleCase' }),
|
||||||
|
value: 'center',
|
||||||
|
},
|
||||||
|
{ label: t('common.right', { postProcess: 'titleCase' }), value: 'right' },
|
||||||
|
]}
|
||||||
|
onChange={(value) =>
|
||||||
|
updateLyricsSetting({ alignment: value as 'center' | 'left' | 'right' })
|
||||||
|
}
|
||||||
|
value={lyricsSettings.alignment}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: '',
|
||||||
|
title: t('page.fullscreenPlayer.config.lyricAlignment', {
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
aria-label="Follow lyrics"
|
||||||
|
defaultChecked={lyricsSettings.follow}
|
||||||
|
onChange={(e) => updateLyricsSetting({ follow: e.currentTarget.checked })}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: '',
|
||||||
|
title: t('page.fullscreenPlayer.config.followCurrentLyric', {
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
aria-label="Show match"
|
||||||
|
defaultChecked={lyricsSettings.showMatch}
|
||||||
|
onChange={(e) => updateLyricsSetting({ showMatch: e.currentTarget.checked })}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: '',
|
||||||
|
title: t('page.fullscreenPlayer.config.showLyricMatch', {
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
aria-label="Show provider"
|
||||||
|
defaultChecked={lyricsSettings.showProvider}
|
||||||
|
onChange={(e) => updateLyricsSetting({ showProvider: e.currentTarget.checked })}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: '',
|
||||||
|
title: t('page.fullscreenPlayer.config.showLyricProvider', {
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const lyricOptions: SettingOption[] = [
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
aria-label="Prefer local lyrics"
|
||||||
|
defaultChecked={lyricsSettings.preferLocalLyrics}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateLyricsSetting({ preferLocalLyrics: e.currentTarget.checked })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.preferLocalLyrics', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: !isElectron(),
|
||||||
|
title: t('setting.preferLocalLyrics', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
aria-label="Enable fetching lyrics"
|
||||||
|
defaultChecked={lyricsSettings.fetch}
|
||||||
|
onChange={(e) => updateLyricsSetting({ fetch: e.currentTarget.checked })}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.lyricFetch', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: !isElectron(),
|
||||||
|
title: t('setting.lyricFetch', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<MultiSelect
|
||||||
|
aria-label="Lyric providers"
|
||||||
|
clearable
|
||||||
|
data={Object.values(LyricSource)}
|
||||||
|
defaultValue={lyricsSettings.sources}
|
||||||
|
onChange={(e: string[]) => {
|
||||||
|
localSettings?.set('lyrics', e);
|
||||||
|
updateLyricsSetting({ sources: e.map((source) => source as LyricSource) });
|
||||||
|
}}
|
||||||
|
width={300}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.lyricFetchProvider', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: !isElectron(),
|
||||||
|
title: t('setting.lyricFetchProvider', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
aria-label="Enable NetEase translations"
|
||||||
|
defaultChecked={lyricsSettings.enableNeteaseTranslation}
|
||||||
|
onChange={(e) => {
|
||||||
|
const isChecked = e.currentTarget.checked;
|
||||||
|
updateLyricsSetting({ enableNeteaseTranslation: isChecked });
|
||||||
|
localSettings?.set('enableNeteaseTranslation', isChecked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.neteaseTranslation', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: !isElectron(),
|
||||||
|
title: t('setting.neteaseTranslation', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<NumberInput
|
||||||
|
defaultValue={lyricsSettings.delayMs}
|
||||||
|
onBlur={(e) => {
|
||||||
|
const value = Number(e.currentTarget.value);
|
||||||
|
updateLyricsSetting({ delayMs: value });
|
||||||
|
}}
|
||||||
|
step={10}
|
||||||
|
width={100}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.lyricOffset', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: !isElectron(),
|
||||||
|
title: t('setting.lyricOffset', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Select
|
||||||
|
data={languages}
|
||||||
|
onChange={(value) => {
|
||||||
|
updateLyricsSetting({ translationTargetLanguage: value });
|
||||||
|
}}
|
||||||
|
value={lyricsSettings.translationTargetLanguage}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.translationTargetLanguage', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: !isElectron(),
|
||||||
|
title: t('setting.translationTargetLanguage', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Select
|
||||||
|
clearable
|
||||||
|
data={['Microsoft Azure', 'Google Cloud']}
|
||||||
|
onChange={(value) => {
|
||||||
|
updateLyricsSetting({ translationApiProvider: value });
|
||||||
|
}}
|
||||||
|
value={lyricsSettings.translationApiProvider}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.translationApiProvider', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: !isElectron(),
|
||||||
|
title: t('setting.translationApiProvider', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<TextInput
|
||||||
|
onChange={(e) => {
|
||||||
|
updateLyricsSetting({ translationApiKey: e.currentTarget.value });
|
||||||
|
}}
|
||||||
|
value={lyricsSettings.translationApiKey}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.translationApiKey', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: !isElectron(),
|
||||||
|
title: t('setting.translationApiKey', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
aria-label="Enable auto translation"
|
||||||
|
defaultChecked={lyricsSettings.enableAutoTranslation}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateLyricsSetting({ enableAutoTranslation: e.currentTarget.checked })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.enableAutoTranslation', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: !isElectron(),
|
||||||
|
title: t('setting.enableAutoTranslation', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="md" p="md">
|
||||||
|
<Fieldset legend={t('page.setting.lyricsDisplay', { postProcess: 'sentenceCase' })}>
|
||||||
|
<SettingsSection options={displayOptions} />
|
||||||
|
</Fieldset>
|
||||||
|
<Fieldset legend={t('page.setting.lyrics', { postProcess: 'sentenceCase' })}>
|
||||||
|
<SettingsSection options={lyricOptions} />
|
||||||
|
</Fieldset>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { ContextModalProps } from '@mantine/modals';
|
||||||
|
|
||||||
|
import { LyricsSettingsForm } from './lyrics-settings-form';
|
||||||
|
|
||||||
|
export const LyricsSettingsContextModal = ({
|
||||||
|
innerProps,
|
||||||
|
}: ContextModalProps<{ settingsKey: string }>) => {
|
||||||
|
return <LyricsSettingsForm settingsKey={innerProps.settingsKey} />;
|
||||||
|
};
|
||||||
@@ -22,6 +22,7 @@ interface LyricsActionsProps {
|
|||||||
onTranslateLyric?: () => void;
|
onTranslateLyric?: () => void;
|
||||||
onUpdateOffset: (offsetMs: number) => void;
|
onUpdateOffset: (offsetMs: number) => void;
|
||||||
setIndex: (idx: number) => void;
|
setIndex: (idx: number) => void;
|
||||||
|
settingsKey?: string;
|
||||||
synced?: boolean;
|
synced?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,6 +36,7 @@ export const LyricsActions = ({
|
|||||||
onTranslateLyric,
|
onTranslateLyric,
|
||||||
onUpdateOffset,
|
onUpdateOffset,
|
||||||
setIndex,
|
setIndex,
|
||||||
|
settingsKey = 'default',
|
||||||
}: LyricsActionsProps) => {
|
}: LyricsActionsProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const currentSong = usePlayerSong();
|
const currentSong = usePlayerSong();
|
||||||
|
|||||||
@@ -64,3 +64,13 @@
|
|||||||
transparent 95%
|
transparent 95%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings-icon {
|
||||||
|
z-index: 100;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lyrics-container:hover .settings-icon {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,10 +18,12 @@ import {
|
|||||||
UnsynchronizedLyrics,
|
UnsynchronizedLyrics,
|
||||||
UnsynchronizedLyricsProps,
|
UnsynchronizedLyricsProps,
|
||||||
} from '/@/renderer/features/lyrics/unsynchronized-lyrics';
|
} from '/@/renderer/features/lyrics/unsynchronized-lyrics';
|
||||||
|
import { openLyricsSettingsModal } from '/@/renderer/features/lyrics/utils/open-lyrics-settings-modal';
|
||||||
import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events';
|
import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events';
|
||||||
import { ComponentErrorBoundary } from '/@/renderer/features/shared/components/component-error-boundary';
|
import { ComponentErrorBoundary } from '/@/renderer/features/shared/components/component-error-boundary';
|
||||||
import { queryClient } from '/@/renderer/lib/react-query';
|
import { queryClient } from '/@/renderer/lib/react-query';
|
||||||
import { useLyricsSettings, usePlayerSong } from '/@/renderer/store';
|
import { useLyricsSettings, usePlayerSong } from '/@/renderer/store';
|
||||||
|
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||||
import { Center } from '/@/shared/components/center/center';
|
import { Center } from '/@/shared/components/center/center';
|
||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
import { Spinner } from '/@/shared/components/spinner/spinner';
|
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||||
@@ -35,9 +37,10 @@ import {
|
|||||||
|
|
||||||
type LyricsProps = {
|
type LyricsProps = {
|
||||||
fadeOutNoLyricsMessage?: boolean;
|
fadeOutNoLyricsMessage?: boolean;
|
||||||
|
settingsKey?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Lyrics = ({ fadeOutNoLyricsMessage = true }: LyricsProps) => {
|
export const Lyrics = ({ fadeOutNoLyricsMessage = true, settingsKey = 'default' }: LyricsProps) => {
|
||||||
const currentSong = usePlayerSong();
|
const currentSong = usePlayerSong();
|
||||||
const {
|
const {
|
||||||
enableAutoTranslation,
|
enableAutoTranslation,
|
||||||
@@ -301,9 +304,23 @@ export const Lyrics = ({ fadeOutNoLyricsMessage = true }: LyricsProps) => {
|
|||||||
}
|
}
|
||||||
}, [currentOffsetMs, lyrics, synced]);
|
}, [currentOffsetMs, lyrics, synced]);
|
||||||
|
|
||||||
|
const handleOpenSettings = () => {
|
||||||
|
openLyricsSettingsModal(settingsKey);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ComponentErrorBoundary>
|
<ComponentErrorBoundary>
|
||||||
<div className={styles.lyricsContainer}>
|
<div className={styles.lyricsContainer}>
|
||||||
|
<ActionIcon
|
||||||
|
className={styles.settingsIcon}
|
||||||
|
icon="settings2"
|
||||||
|
iconProps={{ size: 'lg' }}
|
||||||
|
onClick={handleOpenSettings}
|
||||||
|
pos="absolute"
|
||||||
|
right={0}
|
||||||
|
top={0}
|
||||||
|
variant="transparent"
|
||||||
|
/>
|
||||||
{isLoadingLyrics ? (
|
{isLoadingLyrics ? (
|
||||||
<Spinner container />
|
<Spinner container />
|
||||||
) : (
|
) : (
|
||||||
@@ -335,11 +352,13 @@ export const Lyrics = ({ fadeOutNoLyricsMessage = true }: LyricsProps) => {
|
|||||||
<SynchronizedLyrics
|
<SynchronizedLyrics
|
||||||
{...(lyrics as SynchronizedLyricsProps)}
|
{...(lyrics as SynchronizedLyricsProps)}
|
||||||
offsetMs={currentOffsetMs}
|
offsetMs={currentOffsetMs}
|
||||||
|
settingsKey={settingsKey}
|
||||||
translatedLyrics={showTranslation ? translatedLyrics : null}
|
translatedLyrics={showTranslation ? translatedLyrics : null}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<UnsynchronizedLyrics
|
<UnsynchronizedLyrics
|
||||||
{...(lyrics as UnsynchronizedLyricsProps)}
|
{...(lyrics as UnsynchronizedLyricsProps)}
|
||||||
|
settingsKey={settingsKey}
|
||||||
translatedLyrics={showTranslation ? translatedLyrics : null}
|
translatedLyrics={showTranslation ? translatedLyrics : null}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -362,6 +381,7 @@ export const Lyrics = ({ fadeOutNoLyricsMessage = true }: LyricsProps) => {
|
|||||||
}
|
}
|
||||||
onUpdateOffset={handleUpdateOffset}
|
onUpdateOffset={handleUpdateOffset}
|
||||||
setIndex={setIndex}
|
setIndex={setIndex}
|
||||||
|
settingsKey={settingsKey}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import styles from './synchronized-lyrics.module.css';
|
|||||||
|
|
||||||
import { LyricLine } from '/@/renderer/features/lyrics/lyric-line';
|
import { LyricLine } from '/@/renderer/features/lyrics/lyric-line';
|
||||||
import {
|
import {
|
||||||
|
useLyricsDisplaySettings,
|
||||||
useLyricsSettings,
|
useLyricsSettings,
|
||||||
usePlaybackType,
|
usePlaybackType,
|
||||||
usePlayerActions,
|
usePlayerActions,
|
||||||
@@ -22,6 +23,7 @@ const mpris = isElectron() && utils?.isLinux() ? window.api.mpris : null;
|
|||||||
export interface SynchronizedLyricsProps extends Omit<FullLyricsMetadata, 'lyrics'> {
|
export interface SynchronizedLyricsProps extends Omit<FullLyricsMetadata, 'lyrics'> {
|
||||||
lyrics: SynchronizedLyricsArray;
|
lyrics: SynchronizedLyricsArray;
|
||||||
offsetMs?: number;
|
offsetMs?: number;
|
||||||
|
settingsKey?: string;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
translatedLyrics?: null | string;
|
translatedLyrics?: null | string;
|
||||||
}
|
}
|
||||||
@@ -32,12 +34,22 @@ export const SynchronizedLyrics = ({
|
|||||||
name,
|
name,
|
||||||
offsetMs,
|
offsetMs,
|
||||||
remote,
|
remote,
|
||||||
|
settingsKey = 'default',
|
||||||
source,
|
source,
|
||||||
style,
|
style,
|
||||||
translatedLyrics,
|
translatedLyrics,
|
||||||
}: SynchronizedLyricsProps) => {
|
}: SynchronizedLyricsProps) => {
|
||||||
const playbackType = usePlaybackType();
|
const playbackType = usePlaybackType();
|
||||||
const settings = useLyricsSettings();
|
const lyricsSettings = useLyricsSettings();
|
||||||
|
const displaySettings = useLyricsDisplaySettings(settingsKey);
|
||||||
|
const settings = {
|
||||||
|
...lyricsSettings,
|
||||||
|
fontSize:
|
||||||
|
displaySettings.fontSize && displaySettings.fontSize !== 0
|
||||||
|
? displaySettings.fontSize
|
||||||
|
: 24,
|
||||||
|
gap: displaySettings.gap && displaySettings.gap !== 0 ? displaySettings.gap : 24,
|
||||||
|
};
|
||||||
const { mediaSeekToTimestamp } = usePlayerActions();
|
const { mediaSeekToTimestamp } = usePlayerActions();
|
||||||
const status = usePlayerStatus();
|
const status = usePlayerStatus();
|
||||||
const timestamp = usePlayerTimestamp();
|
const timestamp = usePlayerTimestamp();
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ import { useMemo } from 'react';
|
|||||||
import styles from './unsynchronized-lyrics.module.css';
|
import styles from './unsynchronized-lyrics.module.css';
|
||||||
|
|
||||||
import { LyricLine } from '/@/renderer/features/lyrics/lyric-line';
|
import { LyricLine } from '/@/renderer/features/lyrics/lyric-line';
|
||||||
import { useLyricsSettings } from '/@/renderer/store';
|
import { useLyricsDisplaySettings, useLyricsSettings } from '/@/renderer/store';
|
||||||
import { FullLyricsMetadata } from '/@/shared/types/domain-types';
|
import { FullLyricsMetadata } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
export interface UnsynchronizedLyricsProps extends Omit<FullLyricsMetadata, 'lyrics'> {
|
export interface UnsynchronizedLyricsProps extends Omit<FullLyricsMetadata, 'lyrics'> {
|
||||||
lyrics: string;
|
lyrics: string;
|
||||||
|
settingsKey?: string;
|
||||||
translatedLyrics?: null | string;
|
translatedLyrics?: null | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,10 +17,23 @@ export const UnsynchronizedLyrics = ({
|
|||||||
lyrics,
|
lyrics,
|
||||||
name,
|
name,
|
||||||
remote,
|
remote,
|
||||||
|
settingsKey = 'default',
|
||||||
source,
|
source,
|
||||||
translatedLyrics,
|
translatedLyrics,
|
||||||
}: UnsynchronizedLyricsProps) => {
|
}: UnsynchronizedLyricsProps) => {
|
||||||
const settings = useLyricsSettings();
|
const lyricsSettings = useLyricsSettings();
|
||||||
|
const displaySettings = useLyricsDisplaySettings(settingsKey);
|
||||||
|
const settings = {
|
||||||
|
...lyricsSettings,
|
||||||
|
fontSizeUnsync:
|
||||||
|
displaySettings.fontSizeUnsync && displaySettings.fontSizeUnsync !== 0
|
||||||
|
? displaySettings.fontSizeUnsync
|
||||||
|
: 24,
|
||||||
|
gapUnsync:
|
||||||
|
displaySettings.gapUnsync && displaySettings.gapUnsync !== 0
|
||||||
|
? displaySettings.gapUnsync
|
||||||
|
: 24,
|
||||||
|
};
|
||||||
const lines = useMemo(() => {
|
const lines = useMemo(() => {
|
||||||
return lyrics.split('\n');
|
return lyrics.split('\n');
|
||||||
}, [lyrics]);
|
}, [lyrics]);
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { openContextModal } from '@mantine/modals';
|
||||||
|
|
||||||
|
import i18n from '/@/i18n/i18n';
|
||||||
|
|
||||||
|
export const openLyricsSettingsModal = (settingsKey: string = 'default') => {
|
||||||
|
openContextModal({
|
||||||
|
innerProps: { settingsKey },
|
||||||
|
modalKey: 'lyricsSettings',
|
||||||
|
overlayProps: {
|
||||||
|
blur: 0,
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
size: 'xl',
|
||||||
|
styles: {
|
||||||
|
content: {
|
||||||
|
height: '90%',
|
||||||
|
maxWidth: '1400px',
|
||||||
|
minHeight: '600px',
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: i18n.t('common.setting_other', { postProcess: 'titleCase' }),
|
||||||
|
transitionProps: {
|
||||||
|
transition: 'pop',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -270,7 +270,7 @@ const LyricsPanel = () => {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.lyricsSection}>
|
<div className={styles.lyricsSection}>
|
||||||
<PanelReorderControls panelType="lyrics" />
|
<PanelReorderControls panelType="lyrics" />
|
||||||
<Lyrics fadeOutNoLyricsMessage={false} />
|
<Lyrics fadeOutNoLyricsMessage={false} settingsKey="sidebar" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -330,7 +330,7 @@ const CombinedLyricsAndVisualizerPanel = () => {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.lyricsSection}>
|
<div className={styles.lyricsSection}>
|
||||||
<PanelReorderControls panelType="lyrics" />
|
<PanelReorderControls panelType="lyrics" />
|
||||||
<Lyrics fadeOutNoLyricsMessage={true} />
|
<Lyrics fadeOutNoLyricsMessage={true} settingsKey="sidebar" />
|
||||||
<div
|
<div
|
||||||
className={styles.visualizerOverlay}
|
className={styles.visualizerOverlay}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { useFastAverageColor } from '/@/renderer/hooks';
|
|||||||
import {
|
import {
|
||||||
useFullScreenPlayerStore,
|
useFullScreenPlayerStore,
|
||||||
useFullScreenPlayerStoreActions,
|
useFullScreenPlayerStoreActions,
|
||||||
|
useLyricsDisplaySettings,
|
||||||
useLyricsSettings,
|
useLyricsSettings,
|
||||||
usePlayerData,
|
usePlayerData,
|
||||||
usePlayerSong,
|
usePlayerSong,
|
||||||
@@ -235,19 +236,35 @@ const Controls = ({ isPageHovered }: ControlsProps) => {
|
|||||||
} = useFullScreenPlayerStore();
|
} = useFullScreenPlayerStore();
|
||||||
const { setStore } = useFullScreenPlayerStoreActions();
|
const { setStore } = useFullScreenPlayerStoreActions();
|
||||||
const { setSettings } = useSettingsStoreActions();
|
const { setSettings } = useSettingsStoreActions();
|
||||||
const lyricConfig = useLyricsSettings();
|
const lyricsSettings = useLyricsSettings();
|
||||||
|
const displaySettings = useLyricsDisplaySettings('default');
|
||||||
|
const lyricConfig = { ...lyricsSettings, ...displaySettings };
|
||||||
|
|
||||||
const handleToggleFullScreenPlayer = () => {
|
const handleToggleFullScreenPlayer = () => {
|
||||||
setStore({ expanded: !expanded });
|
setStore({ expanded: !expanded });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLyricsSettings = (property: string, value: any) => {
|
const handleLyricsSettings = (property: string, value: any) => {
|
||||||
|
const displayProperties = ['fontSize', 'fontSizeUnsync', 'gap', 'gapUnsync'];
|
||||||
|
if (displayProperties.includes(property)) {
|
||||||
|
const currentDisplay = useSettingsStore.getState().lyricsDisplay;
|
||||||
|
setSettings({
|
||||||
|
lyricsDisplay: {
|
||||||
|
...currentDisplay,
|
||||||
|
default: {
|
||||||
|
...currentDisplay.default,
|
||||||
|
[property]: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
setSettings({
|
setSettings({
|
||||||
lyrics: {
|
lyrics: {
|
||||||
...useSettingsStore.getState().lyrics,
|
...useSettingsStore.getState().lyrics,
|
||||||
[property]: value,
|
[property]: value,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useHotkeys([['Escape', handleToggleFullScreenPlayer]]);
|
useHotkeys([['Escape', handleToggleFullScreenPlayer]]);
|
||||||
|
|||||||
@@ -5,7 +5,12 @@ import styles from './mobile-fullscreen-player.module.css';
|
|||||||
|
|
||||||
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns';
|
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns';
|
||||||
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||||
import { useLyricsSettings, useSettingsStore, useSettingsStoreActions } from '/@/renderer/store';
|
import {
|
||||||
|
useLyricsDisplaySettings,
|
||||||
|
useLyricsSettings,
|
||||||
|
useSettingsStore,
|
||||||
|
useSettingsStoreActions,
|
||||||
|
} from '/@/renderer/store';
|
||||||
import { useFullScreenPlayerStore, useFullScreenPlayerStoreActions } from '/@/renderer/store';
|
import { useFullScreenPlayerStore, useFullScreenPlayerStoreActions } from '/@/renderer/store';
|
||||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||||
import { Divider } from '/@/shared/components/divider/divider';
|
import { Divider } from '/@/shared/components/divider/divider';
|
||||||
@@ -37,15 +42,31 @@ export const MobileFullscreenPlayerHeader = memo(
|
|||||||
} = useFullScreenPlayerStore();
|
} = useFullScreenPlayerStore();
|
||||||
const { setStore } = useFullScreenPlayerStoreActions();
|
const { setStore } = useFullScreenPlayerStoreActions();
|
||||||
const { setSettings } = useSettingsStoreActions();
|
const { setSettings } = useSettingsStoreActions();
|
||||||
const lyricConfig = useLyricsSettings();
|
const lyricsSettings = useLyricsSettings();
|
||||||
|
const displaySettings = useLyricsDisplaySettings('default');
|
||||||
|
const lyricConfig = { ...lyricsSettings, ...displaySettings };
|
||||||
|
|
||||||
const handleLyricsSettings = (property: string, value: any) => {
|
const handleLyricsSettings = (property: string, value: any) => {
|
||||||
|
const displayProperties = ['fontSize', 'fontSizeUnsync', 'gap', 'gapUnsync'];
|
||||||
|
if (displayProperties.includes(property)) {
|
||||||
|
const currentDisplay = useSettingsStore.getState().lyricsDisplay;
|
||||||
|
setSettings({
|
||||||
|
lyricsDisplay: {
|
||||||
|
...currentDisplay,
|
||||||
|
default: {
|
||||||
|
...currentDisplay.default,
|
||||||
|
[property]: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
setSettings({
|
setSettings({
|
||||||
lyrics: {
|
lyrics: {
|
||||||
...useSettingsStore.getState().lyrics,
|
...useSettingsStore.getState().lyrics,
|
||||||
[property]: value,
|
[property]: value,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { openContextModal } from '@mantine/modals';
|
import { openContextModal } from '@mantine/modals';
|
||||||
|
|
||||||
|
import i18n from '/@/i18n/i18n';
|
||||||
|
|
||||||
export const openVisualizerSettingsModal = () => {
|
export const openVisualizerSettingsModal = () => {
|
||||||
openContextModal({
|
openContextModal({
|
||||||
innerProps: {},
|
innerProps: {},
|
||||||
@@ -17,7 +19,7 @@ export const openVisualizerSettingsModal = () => {
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
title: 'Visualizer Settings',
|
title: i18n.t('common.setting_other', { postProcess: 'titleCase' }),
|
||||||
transitionProps: {
|
transitionProps: {
|
||||||
transition: 'pop',
|
transition: 'pop',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -21,20 +21,22 @@ export const LyricSettings = () => {
|
|||||||
const settings = useLyricsSettings();
|
const settings = useLyricsSettings();
|
||||||
const { setSettings } = useSettingsStoreActions();
|
const { setSettings } = useSettingsStoreActions();
|
||||||
|
|
||||||
|
const updateSetting = (updates: Partial<typeof settings>) => {
|
||||||
|
setSettings({
|
||||||
|
lyrics: {
|
||||||
|
...settings,
|
||||||
|
...updates,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const lyricOptions: SettingOption[] = [
|
const lyricOptions: SettingOption[] = [
|
||||||
{
|
{
|
||||||
control: (
|
control: (
|
||||||
<Switch
|
<Switch
|
||||||
aria-label="Follow lyrics"
|
aria-label="Follow lyrics"
|
||||||
defaultChecked={settings.follow}
|
defaultChecked={settings.follow}
|
||||||
onChange={(e) => {
|
onChange={(e) => updateSetting({ follow: e.currentTarget.checked })}
|
||||||
setSettings({
|
|
||||||
lyrics: {
|
|
||||||
...settings,
|
|
||||||
follow: e.currentTarget.checked,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: t('setting.followLyric', {
|
description: t('setting.followLyric', {
|
||||||
@@ -48,14 +50,7 @@ export const LyricSettings = () => {
|
|||||||
<Switch
|
<Switch
|
||||||
aria-label="Prefer local lyrics"
|
aria-label="Prefer local lyrics"
|
||||||
defaultChecked={settings.preferLocalLyrics}
|
defaultChecked={settings.preferLocalLyrics}
|
||||||
onChange={(e) => {
|
onChange={(e) => updateSetting({ preferLocalLyrics: e.currentTarget.checked })}
|
||||||
setSettings({
|
|
||||||
lyrics: {
|
|
||||||
...settings,
|
|
||||||
preferLocalLyrics: e.currentTarget.checked,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: t('setting.preferLocalLyrics', {
|
description: t('setting.preferLocalLyrics', {
|
||||||
@@ -70,14 +65,7 @@ export const LyricSettings = () => {
|
|||||||
<Switch
|
<Switch
|
||||||
aria-label="Enable fetching lyrics"
|
aria-label="Enable fetching lyrics"
|
||||||
defaultChecked={settings.fetch}
|
defaultChecked={settings.fetch}
|
||||||
onChange={(e) => {
|
onChange={(e) => updateSetting({ fetch: e.currentTarget.checked })}
|
||||||
setSettings({
|
|
||||||
lyrics: {
|
|
||||||
...settings,
|
|
||||||
fetch: e.currentTarget.checked,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: t('setting.lyricFetch', {
|
description: t('setting.lyricFetch', {
|
||||||
@@ -96,12 +84,7 @@ export const LyricSettings = () => {
|
|||||||
defaultValue={settings.sources}
|
defaultValue={settings.sources}
|
||||||
onChange={(e: string[]) => {
|
onChange={(e: string[]) => {
|
||||||
localSettings?.set('lyrics', e);
|
localSettings?.set('lyrics', e);
|
||||||
setSettings({
|
updateSetting({ sources: e.map((source) => source as LyricSource) });
|
||||||
lyrics: {
|
|
||||||
...settings,
|
|
||||||
sources: e.map((source) => source as LyricSource),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
width={300}
|
width={300}
|
||||||
/>
|
/>
|
||||||
@@ -120,12 +103,7 @@ export const LyricSettings = () => {
|
|||||||
defaultChecked={settings.enableNeteaseTranslation}
|
defaultChecked={settings.enableNeteaseTranslation}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const isChecked = e.currentTarget.checked;
|
const isChecked = e.currentTarget.checked;
|
||||||
setSettings({
|
updateSetting({ enableNeteaseTranslation: isChecked });
|
||||||
lyrics: {
|
|
||||||
...settings,
|
|
||||||
enableNeteaseTranslation: e.currentTarget.checked,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
localSettings?.set('enableNeteaseTranslation', isChecked);
|
localSettings?.set('enableNeteaseTranslation', isChecked);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -143,12 +121,7 @@ export const LyricSettings = () => {
|
|||||||
defaultValue={settings.delayMs}
|
defaultValue={settings.delayMs}
|
||||||
onBlur={(e) => {
|
onBlur={(e) => {
|
||||||
const value = Number(e.currentTarget.value);
|
const value = Number(e.currentTarget.value);
|
||||||
setSettings({
|
updateSetting({ delayMs: value });
|
||||||
lyrics: {
|
|
||||||
...settings,
|
|
||||||
delayMs: value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
step={10}
|
step={10}
|
||||||
width={100}
|
width={100}
|
||||||
@@ -166,7 +139,7 @@ export const LyricSettings = () => {
|
|||||||
<Select
|
<Select
|
||||||
data={languages}
|
data={languages}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setSettings({ lyrics: { ...settings, translationTargetLanguage: value } });
|
updateSetting({ translationTargetLanguage: value });
|
||||||
}}
|
}}
|
||||||
value={settings.translationTargetLanguage}
|
value={settings.translationTargetLanguage}
|
||||||
/>
|
/>
|
||||||
@@ -184,7 +157,7 @@ export const LyricSettings = () => {
|
|||||||
clearable
|
clearable
|
||||||
data={['Microsoft Azure', 'Google Cloud']}
|
data={['Microsoft Azure', 'Google Cloud']}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setSettings({ lyrics: { ...settings, translationApiProvider: value } });
|
updateSetting({ translationApiProvider: value });
|
||||||
}}
|
}}
|
||||||
value={settings.translationApiProvider}
|
value={settings.translationApiProvider}
|
||||||
/>
|
/>
|
||||||
@@ -200,9 +173,7 @@ export const LyricSettings = () => {
|
|||||||
control: (
|
control: (
|
||||||
<TextInput
|
<TextInput
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setSettings({
|
updateSetting({ translationApiKey: e.currentTarget.value });
|
||||||
lyrics: { ...settings, translationApiKey: e.currentTarget.value },
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
value={settings.translationApiKey}
|
value={settings.translationApiKey}
|
||||||
/>
|
/>
|
||||||
@@ -219,14 +190,9 @@ export const LyricSettings = () => {
|
|||||||
<Switch
|
<Switch
|
||||||
aria-label="Enable auto translation"
|
aria-label="Enable auto translation"
|
||||||
defaultChecked={settings.enableAutoTranslation}
|
defaultChecked={settings.enableAutoTranslation}
|
||||||
onChange={(e) => {
|
onChange={(e) =>
|
||||||
setSettings({
|
updateSetting({ enableAutoTranslation: e.currentTarget.checked })
|
||||||
lyrics: {
|
}
|
||||||
...settings,
|
|
||||||
enableAutoTranslation: e.currentTarget.checked,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: t('setting.enableAutoTranslation', {
|
description: t('setting.enableAutoTranslation', {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { UpdatePlaylistContextModal } from '/@/renderer/features/playlists/compo
|
|||||||
import { SettingsContextModal } from '/@/renderer/features/settings/components/settings-modal';
|
import { SettingsContextModal } from '/@/renderer/features/settings/components/settings-modal';
|
||||||
import { RouterErrorBoundary } from '/@/renderer/features/shared/components/router-error-boundary';
|
import { RouterErrorBoundary } from '/@/renderer/features/shared/components/router-error-boundary';
|
||||||
import { ShareItemContextModal } from '/@/renderer/features/sharing/components/share-item-context-modal';
|
import { ShareItemContextModal } from '/@/renderer/features/sharing/components/share-item-context-modal';
|
||||||
|
import { LyricsSettingsContextModal } from '/@/renderer/features/lyrics/components/lyrics-settings-modal';
|
||||||
import { VisualizerSettingsContextModal } from '/@/renderer/features/visualizer/components/audiomotionanalyzer/visualizer-settings-modal';
|
import { VisualizerSettingsContextModal } from '/@/renderer/features/visualizer/components/audiomotionanalyzer/visualizer-settings-modal';
|
||||||
import { AuthenticationOutlet } from '/@/renderer/layouts/authentication-outlet';
|
import { AuthenticationOutlet } from '/@/renderer/layouts/authentication-outlet';
|
||||||
import { ResponsiveLayout } from '/@/renderer/layouts/responsive-layout';
|
import { ResponsiveLayout } from '/@/renderer/layouts/responsive-layout';
|
||||||
@@ -99,6 +100,7 @@ export const AppRouter = () => {
|
|||||||
shuffleAll: ShuffleAllContextModal,
|
shuffleAll: ShuffleAllContextModal,
|
||||||
updatePlaylist: UpdatePlaylistContextModal,
|
updatePlaylist: UpdatePlaylistContextModal,
|
||||||
visualizerSettings: VisualizerSettingsContextModal,
|
visualizerSettings: VisualizerSettingsContextModal,
|
||||||
|
lyricsSettings: LyricsSettingsContextModal,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RouterErrorBoundary>
|
<RouterErrorBoundary>
|
||||||
|
|||||||
@@ -406,6 +406,13 @@ const HotkeysSettingsSchema = z.object({
|
|||||||
globalMediaHotkeys: z.boolean(),
|
globalMediaHotkeys: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const LyricsDisplaySettingsSchema = z.object({
|
||||||
|
fontSize: z.number(),
|
||||||
|
fontSizeUnsync: z.number(),
|
||||||
|
gap: z.number(),
|
||||||
|
gapUnsync: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
const LyricsSettingsSchema = z.object({
|
const LyricsSettingsSchema = z.object({
|
||||||
alignment: z.enum(['center', 'left', 'right']),
|
alignment: z.enum(['center', 'left', 'right']),
|
||||||
delayMs: z.number(),
|
delayMs: z.number(),
|
||||||
@@ -413,10 +420,6 @@ const LyricsSettingsSchema = z.object({
|
|||||||
enableNeteaseTranslation: z.boolean(),
|
enableNeteaseTranslation: z.boolean(),
|
||||||
fetch: z.boolean(),
|
fetch: z.boolean(),
|
||||||
follow: z.boolean(),
|
follow: z.boolean(),
|
||||||
fontSize: z.number(),
|
|
||||||
fontSizeUnsync: z.number(),
|
|
||||||
gap: z.number(),
|
|
||||||
gapUnsync: z.number(),
|
|
||||||
preferLocalLyrics: z.boolean(),
|
preferLocalLyrics: z.boolean(),
|
||||||
showMatch: z.boolean(),
|
showMatch: z.boolean(),
|
||||||
showProvider: z.boolean(),
|
showProvider: z.boolean(),
|
||||||
@@ -548,6 +551,7 @@ export const ValidationSettingsStateSchema = z.object({
|
|||||||
hotkeys: HotkeysSettingsSchema,
|
hotkeys: HotkeysSettingsSchema,
|
||||||
lists: z.record(z.nativeEnum(ItemListKey), ItemListConfigSchema),
|
lists: z.record(z.nativeEnum(ItemListKey), ItemListConfigSchema),
|
||||||
lyrics: LyricsSettingsSchema,
|
lyrics: LyricsSettingsSchema,
|
||||||
|
lyricsDisplay: z.record(z.string(), LyricsDisplaySettingsSchema),
|
||||||
playback: PlaybackSettingsSchema,
|
playback: PlaybackSettingsSchema,
|
||||||
queryBuilder: QueryBuilderSettingsSchema,
|
queryBuilder: QueryBuilderSettingsSchema,
|
||||||
remote: RemoteSettingsSchema,
|
remote: RemoteSettingsSchema,
|
||||||
@@ -1364,10 +1368,6 @@ const initialState: SettingsState = {
|
|||||||
enableNeteaseTranslation: false,
|
enableNeteaseTranslation: false,
|
||||||
fetch: true,
|
fetch: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
fontSize: 24,
|
|
||||||
fontSizeUnsync: 24,
|
|
||||||
gap: 24,
|
|
||||||
gapUnsync: 24,
|
|
||||||
preferLocalLyrics: true,
|
preferLocalLyrics: true,
|
||||||
showMatch: true,
|
showMatch: true,
|
||||||
showProvider: true,
|
showProvider: true,
|
||||||
@@ -1376,6 +1376,14 @@ const initialState: SettingsState = {
|
|||||||
translationApiProvider: '',
|
translationApiProvider: '',
|
||||||
translationTargetLanguage: 'en',
|
translationTargetLanguage: 'en',
|
||||||
},
|
},
|
||||||
|
lyricsDisplay: {
|
||||||
|
default: {
|
||||||
|
fontSize: 48,
|
||||||
|
fontSizeUnsync: 24,
|
||||||
|
gap: 32,
|
||||||
|
gapUnsync: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
playback: {
|
playback: {
|
||||||
audioDeviceId: undefined,
|
audioDeviceId: undefined,
|
||||||
audioFadeOnStatusChange: true,
|
audioFadeOnStatusChange: true,
|
||||||
@@ -1749,10 +1757,40 @@ export const useSettingsStore = createWithEqualityFn<SettingsSlice>()(
|
|||||||
state.window.releaseChannel = 'beta';
|
state.window.releaseChannel = 'beta';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (version <= 17) {
|
||||||
|
// Migrate lyrics settings from record structure to separate lyrics and lyricsDisplay
|
||||||
|
if (
|
||||||
|
state.lyrics &&
|
||||||
|
typeof state.lyrics === 'object' &&
|
||||||
|
'default' in state.lyrics
|
||||||
|
) {
|
||||||
|
const oldLyrics = state.lyrics as any;
|
||||||
|
const defaultSettings = oldLyrics.default || oldLyrics;
|
||||||
|
|
||||||
|
// Extract display settings
|
||||||
|
const displaySettings = {
|
||||||
|
fontSize: defaultSettings.fontSize || 24,
|
||||||
|
fontSizeUnsync: defaultSettings.fontSizeUnsync || 24,
|
||||||
|
gap: defaultSettings.gap || 24,
|
||||||
|
gapUnsync: defaultSettings.gapUnsync || 24,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove display properties from main settings
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const { fontSize, fontSizeUnsync, gap, gapUnsync, ...mainSettings } =
|
||||||
|
defaultSettings;
|
||||||
|
|
||||||
|
state.lyrics = mainSettings;
|
||||||
|
state.lyricsDisplay = {
|
||||||
|
default: displaySettings,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return persistedState;
|
return persistedState;
|
||||||
},
|
},
|
||||||
name: 'store_settings',
|
name: 'store_settings',
|
||||||
version: 17,
|
version: 18,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -1780,6 +1818,9 @@ export const useMpvSettings = () =>
|
|||||||
|
|
||||||
export const useLyricsSettings = () => useSettingsStore((state) => state.lyrics, shallow);
|
export const useLyricsSettings = () => useSettingsStore((state) => state.lyrics, shallow);
|
||||||
|
|
||||||
|
export const useLyricsDisplaySettings = (key: string = 'default') =>
|
||||||
|
useSettingsStore((state) => state.lyricsDisplay[key] || state.lyricsDisplay.default, shallow);
|
||||||
|
|
||||||
export const useRemoteSettings = () => useSettingsStore((state) => state.remote, shallow);
|
export const useRemoteSettings = () => useSettingsStore((state) => state.remote, shallow);
|
||||||
|
|
||||||
export const useFontSettings = () => useSettingsStore((state) => state.font, shallow);
|
export const useFontSettings = () => useSettingsStore((state) => state.font, shallow);
|
||||||
|
|||||||
Reference in New Issue
Block a user