mirror of
https://github.com/jeffvli/feishin.git
synced 2026-08-08 13:23:16 +02:00
redesign lyrics settings form
This commit is contained in:
@@ -3,9 +3,9 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { languages } from '/@/i18n/i18n';
|
||||
import {
|
||||
SettingOption,
|
||||
SettingsSection,
|
||||
} from '/@/renderer/features/settings/components/settings-section';
|
||||
ListConfigBooleanControl,
|
||||
ListConfigTable,
|
||||
} from '/@/renderer/features/shared/components/list-config-menu';
|
||||
import {
|
||||
useLyricsDisplaySettings,
|
||||
useLyricsSettings,
|
||||
@@ -19,9 +19,7 @@ import { SegmentedControl } from '/@/shared/components/segmented-control/segment
|
||||
import { Select } from '/@/shared/components/select/select';
|
||||
import { Slider } from '/@/shared/components/slider/slider';
|
||||
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;
|
||||
@@ -58,131 +56,169 @@ export const LyricsSettingsForm = ({ settingsKey }: LyricsSettingsFormProps) =>
|
||||
});
|
||||
};
|
||||
|
||||
const displayOptions: SettingOption[] = [
|
||||
const displayOptions = [
|
||||
{
|
||||
control: (
|
||||
<NumberInput
|
||||
onBlur={(e) => {
|
||||
const value = Number(e.currentTarget.value);
|
||||
component: (
|
||||
<Slider
|
||||
defaultValue={displaySettings.fontSize}
|
||||
label={(value) => `${value}px`}
|
||||
marks={[
|
||||
{ label: '8', value: 8 },
|
||||
{ label: '24', value: 24 },
|
||||
{ label: '48', value: 48 },
|
||||
{ label: '72', value: 72 },
|
||||
]}
|
||||
max={72}
|
||||
min={8}
|
||||
onChangeEnd={(value) => {
|
||||
updateDisplaySetting({ fontSize: value });
|
||||
}}
|
||||
rightSection={
|
||||
<Text pr="md" size="sm">
|
||||
px
|
||||
</Text>
|
||||
}
|
||||
step={1}
|
||||
value={displaySettings.fontSize}
|
||||
width={100}
|
||||
w="75%"
|
||||
/>
|
||||
),
|
||||
description: '',
|
||||
title: t(
|
||||
description: t('page.fullscreenPlayer.config.lyricSizeSynchronized', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'fontSize',
|
||||
label: t(
|
||||
`${t('page.fullscreenPlayer.config.lyricSize')} (${t('page.fullscreenPlayer.config.synchronized')})`,
|
||||
),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<NumberInput
|
||||
onBlur={(e) => {
|
||||
const value = Number(e.currentTarget.value);
|
||||
component: (
|
||||
<Slider
|
||||
defaultValue={displaySettings.fontSizeUnsync}
|
||||
label={(value) => `${value}px`}
|
||||
marks={[
|
||||
{ label: '8', value: 8 },
|
||||
{ label: '24', value: 24 },
|
||||
{ label: '48', value: 48 },
|
||||
{ label: '72', value: 72 },
|
||||
]}
|
||||
max={72}
|
||||
min={8}
|
||||
onChangeEnd={(value) => {
|
||||
updateDisplaySetting({ fontSizeUnsync: value });
|
||||
}}
|
||||
rightSection={
|
||||
<Text pr="md" size="sm">
|
||||
px
|
||||
</Text>
|
||||
}
|
||||
step={1}
|
||||
value={displaySettings.fontSizeUnsync}
|
||||
width={100}
|
||||
w="75%"
|
||||
/>
|
||||
),
|
||||
description: '',
|
||||
title: t(
|
||||
description: t('page.fullscreenPlayer.config.lyricSizeUnsynchronized', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'fontSizeUnsync',
|
||||
label: t(
|
||||
`${t('page.fullscreenPlayer.config.lyricSize')} (${t('page.fullscreenPlayer.config.unsynchronized')})`,
|
||||
),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<NumberInput
|
||||
onBlur={(e) => {
|
||||
const value = Number(e.currentTarget.value);
|
||||
component: (
|
||||
<Slider
|
||||
defaultValue={displaySettings.gap}
|
||||
label={(value) => `${value}px`}
|
||||
marks={[
|
||||
{ label: '0', value: 0 },
|
||||
{ label: '25', value: 25 },
|
||||
{ label: '50', value: 50 },
|
||||
]}
|
||||
max={50}
|
||||
min={0}
|
||||
onChangeEnd={(value) => {
|
||||
updateDisplaySetting({ gap: value });
|
||||
}}
|
||||
rightSection={
|
||||
<Text pr="md" size="sm">
|
||||
px
|
||||
</Text>
|
||||
}
|
||||
step={1}
|
||||
value={displaySettings.gap}
|
||||
width={100}
|
||||
w="75%"
|
||||
/>
|
||||
),
|
||||
description: '',
|
||||
title: t(
|
||||
description: t('page.fullscreenPlayer.config.lyricGapSynchronized', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'gap',
|
||||
label: t(
|
||||
`${t('page.fullscreenPlayer.config.lyricGap')} (${t('page.fullscreenPlayer.config.synchronized')})`,
|
||||
),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<NumberInput
|
||||
onBlur={(e) => {
|
||||
const value = Number(e.currentTarget.value);
|
||||
component: (
|
||||
<Slider
|
||||
defaultValue={displaySettings.gapUnsync}
|
||||
label={(value) => `${value}px`}
|
||||
marks={[
|
||||
{ label: '0', value: 0 },
|
||||
{ label: '25', value: 25 },
|
||||
{ label: '50', value: 50 },
|
||||
]}
|
||||
max={50}
|
||||
min={0}
|
||||
onChangeEnd={(value) => {
|
||||
updateDisplaySetting({ gapUnsync: value });
|
||||
}}
|
||||
rightSection={
|
||||
<Text pr="md" size="sm">
|
||||
px
|
||||
</Text>
|
||||
}
|
||||
step={1}
|
||||
value={displaySettings.gapUnsync}
|
||||
width={100}
|
||||
w="75%"
|
||||
/>
|
||||
),
|
||||
description: '',
|
||||
title: t(
|
||||
description: t('page.fullscreenPlayer.config.lyricGapUnsynchronized', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'gapUnsync',
|
||||
label: t(
|
||||
`${t('page.fullscreenPlayer.config.lyricGap')} (${t('page.fullscreenPlayer.config.unsynchronized')})`,
|
||||
),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
component: (
|
||||
<Slider
|
||||
defaultValue={displaySettings.paddingLeft ?? 0}
|
||||
label={(value) => `${value}%`}
|
||||
marks={[
|
||||
{ label: '0', value: 0 },
|
||||
{ label: '25', value: 25 },
|
||||
{ label: '50', value: 50 },
|
||||
]}
|
||||
max={50}
|
||||
min={0}
|
||||
onChangeEnd={(value) => {
|
||||
updateDisplaySetting({ paddingLeft: value });
|
||||
}}
|
||||
step={1}
|
||||
w={100}
|
||||
w="100%"
|
||||
/>
|
||||
),
|
||||
description: '',
|
||||
title: t('page.fullscreenPlayer.config.lyricPaddingLeft'),
|
||||
description: t('page.fullscreenPlayer.config.lyricPaddingLeft', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'paddingLeft',
|
||||
label: t('page.fullscreenPlayer.config.lyricPaddingLeft'),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
component: (
|
||||
<Slider
|
||||
defaultValue={displaySettings.paddingRight ?? 0}
|
||||
label={(value) => `${value}%`}
|
||||
marks={[
|
||||
{ label: '0', value: 0 },
|
||||
{ label: '25', value: 25 },
|
||||
{ label: '50', value: 50 },
|
||||
]}
|
||||
max={50}
|
||||
min={0}
|
||||
onChangeEnd={(value) => {
|
||||
updateDisplaySetting({ paddingRight: value });
|
||||
}}
|
||||
step={1}
|
||||
w={100}
|
||||
w="100%"
|
||||
/>
|
||||
),
|
||||
description: '',
|
||||
title: t('page.fullscreenPlayer.config.lyricPaddingRight'),
|
||||
description: t('page.fullscreenPlayer.config.lyricPaddingRight', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'paddingRight',
|
||||
label: t('page.fullscreenPlayer.config.lyricPaddingRight'),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
component: (
|
||||
<SegmentedControl
|
||||
data={[
|
||||
{ label: t('common.left'), value: 'left' },
|
||||
@@ -196,41 +232,57 @@ export const LyricsSettingsForm = ({ settingsKey }: LyricsSettingsFormProps) =>
|
||||
updateLyricsSetting({ alignment: value as 'center' | 'left' | 'right' })
|
||||
}
|
||||
value={lyricsSettings.alignment}
|
||||
w="100%"
|
||||
/>
|
||||
),
|
||||
description: '',
|
||||
title: t('page.fullscreenPlayer.config.lyricAlignment'),
|
||||
description: t('page.fullscreenPlayer.config.lyricAlignment', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'alignment',
|
||||
label: t('page.fullscreenPlayer.config.lyricAlignment'),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<Switch
|
||||
aria-label="Follow lyrics"
|
||||
defaultChecked={lyricsSettings.follow}
|
||||
onChange={(e) => updateLyricsSetting({ follow: e.currentTarget.checked })}
|
||||
component: (
|
||||
<ListConfigBooleanControl
|
||||
onChange={(value) => updateLyricsSetting({ follow: value })}
|
||||
value={lyricsSettings.follow}
|
||||
/>
|
||||
),
|
||||
description: '',
|
||||
title: t('page.fullscreenPlayer.config.followCurrentLyric'),
|
||||
description: t('page.fullscreenPlayer.config.followCurrentLyric', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'follow',
|
||||
label: t('page.fullscreenPlayer.config.followCurrentLyric'),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
component: (
|
||||
<Slider
|
||||
defaultValue={lyricsSettings.followScrollAlignment ?? 0}
|
||||
label={(value) => value.toString()}
|
||||
marks={[
|
||||
{ label: '-50', value: -50 },
|
||||
{ label: '-25', value: -25 },
|
||||
{ label: '0', value: 0 },
|
||||
{ label: '25', value: 25 },
|
||||
{ label: '50', value: 50 },
|
||||
]}
|
||||
max={50}
|
||||
min={-50}
|
||||
onChangeEnd={(value) => {
|
||||
updateLyricsSetting({ followScrollAlignment: value });
|
||||
}}
|
||||
step={1}
|
||||
w={100}
|
||||
w="100%"
|
||||
/>
|
||||
),
|
||||
description: '',
|
||||
title: t('page.fullscreenPlayer.config.lyricFollowScrollAlignment'),
|
||||
description: t('page.fullscreenPlayer.config.lyricFollowScrollAlignment', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'followScrollAlignment',
|
||||
label: t('page.fullscreenPlayer.config.lyricFollowScrollAlignment'),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
component: (
|
||||
<NumberInput
|
||||
defaultValue={lyricsSettings.lineLeadTimeMs}
|
||||
max={3000}
|
||||
@@ -243,14 +295,24 @@ export const LyricsSettingsForm = ({ settingsKey }: LyricsSettingsFormProps) =>
|
||||
width={100}
|
||||
/>
|
||||
),
|
||||
description: '',
|
||||
title: t('page.fullscreenPlayer.config.lyricLineLeadTime'),
|
||||
description: t('page.fullscreenPlayer.config.lyricLineLeadTime', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'lineLeadTimeMs',
|
||||
label: t('page.fullscreenPlayer.config.lyricLineLeadTime'),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
component: (
|
||||
<Slider
|
||||
defaultValue={displaySettings.opacityNonActive}
|
||||
label={(e) => (e * 100).toFixed(0) + '%'}
|
||||
marks={[
|
||||
{ label: '0%', value: 0 },
|
||||
{ label: '25%', value: 0.25 },
|
||||
{ label: '50%', value: 0.5 },
|
||||
{ label: '75%', value: 0.75 },
|
||||
{ label: '100%', value: 1 },
|
||||
]}
|
||||
max={1.0}
|
||||
min={0.0}
|
||||
onChangeEnd={(e) => {
|
||||
@@ -259,17 +321,25 @@ export const LyricsSettingsForm = ({ settingsKey }: LyricsSettingsFormProps) =>
|
||||
});
|
||||
}}
|
||||
step={0.01}
|
||||
w={100}
|
||||
w="100%"
|
||||
/>
|
||||
),
|
||||
description: '',
|
||||
title: t(`${t('page.fullscreenPlayer.config.lyricOpacityNonActive')}`, {}),
|
||||
description: t('page.fullscreenPlayer.config.lyricOpacityNonActive', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'opacityNonActive',
|
||||
label: t('page.fullscreenPlayer.config.lyricOpacityNonActive'),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
component: (
|
||||
<Slider
|
||||
defaultValue={displaySettings.scaleNonActive}
|
||||
label={(e) => (e * 100).toFixed(0) + '%'}
|
||||
marks={[
|
||||
{ label: '50%', value: 0.5 },
|
||||
{ label: '75%', value: 0.75 },
|
||||
{ label: '100%', value: 1 },
|
||||
]}
|
||||
max={1.0}
|
||||
min={0.5}
|
||||
onChangeEnd={(e) => {
|
||||
@@ -278,69 +348,74 @@ export const LyricsSettingsForm = ({ settingsKey }: LyricsSettingsFormProps) =>
|
||||
});
|
||||
}}
|
||||
step={0.01}
|
||||
w={100}
|
||||
w="100%"
|
||||
/>
|
||||
),
|
||||
description: '',
|
||||
title: t(`${t('page.fullscreenPlayer.config.lyricScaleNonActive')}`, {}),
|
||||
description: t('page.fullscreenPlayer.config.lyricScaleNonActive', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'scaleNonActive',
|
||||
label: t('page.fullscreenPlayer.config.lyricScaleNonActive'),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<Switch
|
||||
aria-label="Show match"
|
||||
defaultChecked={lyricsSettings.showMatch}
|
||||
onChange={(e) => updateLyricsSetting({ showMatch: e.currentTarget.checked })}
|
||||
component: (
|
||||
<ListConfigBooleanControl
|
||||
onChange={(value) => updateLyricsSetting({ showMatch: value })}
|
||||
value={lyricsSettings.showMatch}
|
||||
/>
|
||||
),
|
||||
description: '',
|
||||
title: t('page.fullscreenPlayer.config.showLyricMatch'),
|
||||
description: t('page.fullscreenPlayer.config.showLyricMatch', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'showMatch',
|
||||
label: t('page.fullscreenPlayer.config.showLyricMatch'),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<Switch
|
||||
aria-label="Show provider"
|
||||
defaultChecked={lyricsSettings.showProvider}
|
||||
onChange={(e) => updateLyricsSetting({ showProvider: e.currentTarget.checked })}
|
||||
component: (
|
||||
<ListConfigBooleanControl
|
||||
onChange={(value) => updateLyricsSetting({ showProvider: value })}
|
||||
value={lyricsSettings.showProvider}
|
||||
/>
|
||||
),
|
||||
description: '',
|
||||
title: t('page.fullscreenPlayer.config.showLyricProvider'),
|
||||
description: t('page.fullscreenPlayer.config.showLyricProvider', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'showProvider',
|
||||
label: t('page.fullscreenPlayer.config.showLyricProvider'),
|
||||
},
|
||||
];
|
||||
|
||||
const lyricOptions: SettingOption[] = [
|
||||
const lyricOptions = [
|
||||
{
|
||||
control: (
|
||||
<Switch
|
||||
aria-label="Prefer local lyrics"
|
||||
defaultChecked={lyricsSettings.preferLocalLyrics}
|
||||
onChange={(e) =>
|
||||
updateLyricsSetting({ preferLocalLyrics: e.currentTarget.checked })
|
||||
}
|
||||
component: (
|
||||
<ListConfigBooleanControl
|
||||
onChange={(value) => updateLyricsSetting({ preferLocalLyrics: value })}
|
||||
value={lyricsSettings.preferLocalLyrics}
|
||||
/>
|
||||
),
|
||||
description: t('setting.preferLocalLyrics', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'preferLocalLyrics',
|
||||
isHidden: !isElectron(),
|
||||
title: t('setting.preferLocalLyrics'),
|
||||
label: t('setting.preferLocalLyrics'),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<Switch
|
||||
aria-label="Enable fetching lyrics"
|
||||
defaultChecked={lyricsSettings.fetch}
|
||||
onChange={(e) => updateLyricsSetting({ fetch: e.currentTarget.checked })}
|
||||
component: (
|
||||
<ListConfigBooleanControl
|
||||
onChange={(value) => updateLyricsSetting({ fetch: value })}
|
||||
value={lyricsSettings.fetch}
|
||||
/>
|
||||
),
|
||||
description: t('setting.lyricFetch', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'fetch',
|
||||
isHidden: !isElectron(),
|
||||
title: t('setting.lyricFetch'),
|
||||
label: t('setting.lyricFetch'),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
component: (
|
||||
<MultiSelect
|
||||
aria-label="Lyric providers"
|
||||
clearable
|
||||
@@ -350,97 +425,96 @@ export const LyricsSettingsForm = ({ settingsKey }: LyricsSettingsFormProps) =>
|
||||
localSettings?.set('lyrics', e);
|
||||
updateLyricsSetting({ sources: e.map((source) => source as LyricSource) });
|
||||
}}
|
||||
width={300}
|
||||
width="100%"
|
||||
/>
|
||||
),
|
||||
description: t('setting.lyricFetchProvider', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'sources',
|
||||
isHidden: !isElectron(),
|
||||
title: t('setting.lyricFetchProvider'),
|
||||
label: t('setting.lyricFetchProvider'),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<Switch
|
||||
aria-label="Enable furigana"
|
||||
defaultChecked={lyricsSettings.enableFurigana}
|
||||
onChange={(e) =>
|
||||
updateLyricsSetting({ enableFurigana: e.currentTarget.checked })
|
||||
}
|
||||
component: (
|
||||
<ListConfigBooleanControl
|
||||
onChange={(value) => updateLyricsSetting({ enableFurigana: value })}
|
||||
value={lyricsSettings.enableFurigana ?? false}
|
||||
/>
|
||||
),
|
||||
description: t('setting.enableFurigana', {
|
||||
context: 'description',
|
||||
}),
|
||||
title: t('setting.enableFurigana'),
|
||||
id: 'enableFurigana',
|
||||
label: t('setting.enableFurigana'),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<Switch
|
||||
aria-label="Enable romaji"
|
||||
defaultChecked={lyricsSettings.enableRomaji}
|
||||
onChange={(e) => updateLyricsSetting({ enableRomaji: e.currentTarget.checked })}
|
||||
component: (
|
||||
<ListConfigBooleanControl
|
||||
onChange={(value) => updateLyricsSetting({ enableRomaji: value })}
|
||||
value={lyricsSettings.enableRomaji ?? false}
|
||||
/>
|
||||
),
|
||||
description: t('setting.enableRomaji', {
|
||||
context: 'description',
|
||||
}),
|
||||
title: t('setting.enableRomaji'),
|
||||
id: 'enableRomaji',
|
||||
label: t('setting.enableRomaji'),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<Switch
|
||||
aria-label="Enable NetEase translations"
|
||||
defaultChecked={lyricsSettings.enableNeteaseTranslation}
|
||||
onChange={(e) => {
|
||||
const isChecked = e.currentTarget.checked;
|
||||
updateLyricsSetting({ enableNeteaseTranslation: isChecked });
|
||||
localSettings?.set('enableNeteaseTranslation', isChecked);
|
||||
component: (
|
||||
<ListConfigBooleanControl
|
||||
onChange={(value) => {
|
||||
updateLyricsSetting({ enableNeteaseTranslation: value });
|
||||
localSettings?.set('enableNeteaseTranslation', value);
|
||||
}}
|
||||
value={lyricsSettings.enableNeteaseTranslation}
|
||||
/>
|
||||
),
|
||||
description: t('setting.neteaseTranslation', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'enableNeteaseTranslation',
|
||||
isHidden: !isElectron(),
|
||||
title: t('setting.neteaseTranslation'),
|
||||
label: t('setting.neteaseTranslation'),
|
||||
},
|
||||
];
|
||||
|
||||
const translationSettings = [
|
||||
{
|
||||
control: (
|
||||
<NumberInput
|
||||
defaultValue={lyricsSettings.delayMs}
|
||||
onBlur={(e) => {
|
||||
const value = Number(e.currentTarget.value);
|
||||
updateLyricsSetting({ delayMs: value });
|
||||
}}
|
||||
step={10}
|
||||
width={100}
|
||||
component: (
|
||||
<ListConfigBooleanControl
|
||||
onChange={(value) => updateLyricsSetting({ enableAutoTranslation: value })}
|
||||
value={lyricsSettings.enableAutoTranslation}
|
||||
/>
|
||||
),
|
||||
description: t('setting.lyricOffset', {
|
||||
description: t('setting.enableAutoTranslation', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'enableAutoTranslation',
|
||||
isHidden: !isElectron(),
|
||||
title: t('setting.lyricOffset'),
|
||||
label: t('setting.enableAutoTranslation'),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
component: (
|
||||
<Select
|
||||
data={languages}
|
||||
onChange={(value) => {
|
||||
updateLyricsSetting({ translationTargetLanguage: value });
|
||||
}}
|
||||
value={lyricsSettings.translationTargetLanguage}
|
||||
width="100%"
|
||||
/>
|
||||
),
|
||||
description: t('setting.translationTargetLanguage', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'translationTargetLanguage',
|
||||
isHidden: !isElectron(),
|
||||
title: t('setting.translationTargetLanguage'),
|
||||
label: t('setting.translationTargetLanguage'),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
component: (
|
||||
<Select
|
||||
clearable
|
||||
data={['Microsoft Azure', 'Google Cloud']}
|
||||
@@ -448,54 +522,45 @@ export const LyricsSettingsForm = ({ settingsKey }: LyricsSettingsFormProps) =>
|
||||
updateLyricsSetting({ translationApiProvider: value });
|
||||
}}
|
||||
value={lyricsSettings.translationApiProvider}
|
||||
width="100%"
|
||||
/>
|
||||
),
|
||||
description: t('setting.translationApiProvider', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'translationApiProvider',
|
||||
isHidden: !isElectron(),
|
||||
title: t('setting.translationApiProvider'),
|
||||
label: t('setting.translationApiProvider'),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
component: (
|
||||
<TextInput
|
||||
onChange={(e) => {
|
||||
updateLyricsSetting({ translationApiKey: e.currentTarget.value });
|
||||
}}
|
||||
value={lyricsSettings.translationApiKey}
|
||||
width="100%"
|
||||
/>
|
||||
),
|
||||
description: t('setting.translationApiKey', {
|
||||
context: 'description',
|
||||
}),
|
||||
id: 'translationApiKey',
|
||||
isHidden: !isElectron(),
|
||||
title: t('setting.translationApiKey'),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<Switch
|
||||
aria-label="Enable auto translation"
|
||||
defaultChecked={lyricsSettings.enableAutoTranslation}
|
||||
onChange={(e) =>
|
||||
updateLyricsSetting({ enableAutoTranslation: e.currentTarget.checked })
|
||||
}
|
||||
/>
|
||||
),
|
||||
description: t('setting.enableAutoTranslation', {
|
||||
context: 'description',
|
||||
}),
|
||||
isHidden: !isElectron(),
|
||||
title: t('setting.enableAutoTranslation'),
|
||||
label: t('setting.translationApiKey'),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack gap="md" p="md">
|
||||
<Fieldset legend={t('page.setting.lyricsDisplay')}>
|
||||
<SettingsSection options={displayOptions} />
|
||||
</Fieldset>
|
||||
<Stack gap="sm">
|
||||
<Fieldset legend={t('page.setting.lyrics')}>
|
||||
<SettingsSection options={lyricOptions} />
|
||||
<ListConfigTable options={lyricOptions} />
|
||||
</Fieldset>
|
||||
<Fieldset legend={t('page.setting.lyricsDisplay')}>
|
||||
<ListConfigTable options={displayOptions} />
|
||||
</Fieldset>
|
||||
<Fieldset legend={t('page.setting.lyricsTranslation')}>
|
||||
<ListConfigTable options={translationSettings} />
|
||||
</Fieldset>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -54,7 +54,6 @@ import { SegmentedControl } from '/@/shared/components/segmented-control/segment
|
||||
import { Select } from '/@/shared/components/select/select';
|
||||
import { Slider } from '/@/shared/components/slider/slider';
|
||||
import { Stack } from '/@/shared/components/stack/stack';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { useMediaQuery } from '/@/shared/hooks/use-media-query';
|
||||
import { useThrottledCallback } from '/@/shared/hooks/use-throttled-callback';
|
||||
import { useThrottledValue } from '/@/shared/hooks/use-throttled-value';
|
||||
@@ -201,17 +200,9 @@ const AutoDJButton = () => {
|
||||
w="96px"
|
||||
/>
|
||||
),
|
||||
description: t('setting.autoDJ_itemCount_description'),
|
||||
id: 'itemCount',
|
||||
label: (
|
||||
<Stack gap="xs">
|
||||
<Text isNoSelect size="sm">
|
||||
{t('setting.autoDJ_itemCount')}
|
||||
</Text>
|
||||
<Text isMuted isNoSelect size="xs">
|
||||
{t('setting.autoDJ_itemCount_description')}
|
||||
</Text>
|
||||
</Stack>
|
||||
),
|
||||
label: t('setting.autoDJ_itemCount'),
|
||||
},
|
||||
{
|
||||
component: (
|
||||
@@ -239,17 +230,9 @@ const AutoDJButton = () => {
|
||||
w="144px"
|
||||
/>
|
||||
),
|
||||
description: t('setting.autoDJ_timing_description'),
|
||||
id: 'timing',
|
||||
label: (
|
||||
<Stack gap="xs">
|
||||
<Text isNoSelect size="sm">
|
||||
{t('setting.autoDJ_timing')}
|
||||
</Text>
|
||||
<Text isMuted isNoSelect size="xs">
|
||||
{t('setting.autoDJ_timing_description')}
|
||||
</Text>
|
||||
</Stack>
|
||||
),
|
||||
label: t('setting.autoDJ_timing'),
|
||||
},
|
||||
],
|
||||
[
|
||||
@@ -279,17 +262,9 @@ const AutoDJButton = () => {
|
||||
value={settings.allowDuplicates}
|
||||
/>
|
||||
),
|
||||
description: t('setting.autoDJ_allowDuplicates_description'),
|
||||
id: 'allowDuplicates',
|
||||
label: (
|
||||
<Stack gap="xs">
|
||||
<Text isNoSelect size="sm">
|
||||
{t('setting.autoDJ_allowDuplicates')}
|
||||
</Text>
|
||||
<Text isMuted isNoSelect size="xs">
|
||||
{t('setting.autoDJ_allowDuplicates_description')}
|
||||
</Text>
|
||||
</Stack>
|
||||
),
|
||||
label: t('setting.autoDJ_allowDuplicates'),
|
||||
},
|
||||
{
|
||||
component: (
|
||||
@@ -304,17 +279,9 @@ const AutoDJButton = () => {
|
||||
value={settings.onlySimilar}
|
||||
/>
|
||||
),
|
||||
description: t('setting.autoDJ_onlySimilar_description'),
|
||||
id: 'onlySimilar',
|
||||
label: (
|
||||
<Stack gap="xs">
|
||||
<Text isNoSelect size="sm">
|
||||
{t('setting.autoDJ_onlySimilar')}
|
||||
</Text>
|
||||
<Text isMuted isNoSelect size="xs">
|
||||
{t('setting.autoDJ_onlySimilar_description')}
|
||||
</Text>
|
||||
</Stack>
|
||||
),
|
||||
label: t('setting.autoDJ_onlySimilar'),
|
||||
},
|
||||
],
|
||||
[setSettings, settings.allowDuplicates, settings.onlySimilar, t],
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
width: 50%;
|
||||
padding: var(--theme-spacing-md) var(--theme-spacing-md) var(--theme-spacing-md) 0;
|
||||
font-weight: 500;
|
||||
vertical-align: middle;
|
||||
vertical-align: top;
|
||||
background-color: initial;
|
||||
}
|
||||
|
||||
.td {
|
||||
padding: 0;
|
||||
vertical-align: middle;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.control {
|
||||
@@ -20,6 +20,7 @@
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
padding: var(--theme-spacing-md) 0;
|
||||
}
|
||||
|
||||
.divider-cell {
|
||||
|
||||
@@ -17,6 +17,7 @@ import { SegmentedControl } from '/@/shared/components/segmented-control/segment
|
||||
import { Stack } from '/@/shared/components/stack/stack';
|
||||
import { Switch } from '/@/shared/components/switch/switch';
|
||||
import { Table } from '/@/shared/components/table/table';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { useDisclosure } from '/@/shared/hooks/use-disclosure';
|
||||
import { ItemListKey, ListDisplayType } from '/@/shared/types/types';
|
||||
|
||||
@@ -232,6 +233,7 @@ export const ListConfigTable = ({
|
||||
}: {
|
||||
options: {
|
||||
component: ReactNode;
|
||||
description?: ReactNode | string;
|
||||
id: string;
|
||||
isDivider?: boolean;
|
||||
isHidden?: boolean;
|
||||
@@ -269,7 +271,20 @@ export const ListConfigTable = ({
|
||||
|
||||
return (
|
||||
<Table.Tr key={option.id}>
|
||||
<Table.Th>{option.label}</Table.Th>
|
||||
<Table.Th>
|
||||
{option.description !== undefined ? (
|
||||
<Stack gap="xs">
|
||||
<Text isNoSelect size="sm">
|
||||
{option.label}
|
||||
</Text>
|
||||
<Text isMuted isNoSelect size="xs">
|
||||
{option.description}
|
||||
</Text>
|
||||
</Stack>
|
||||
) : (
|
||||
option.label
|
||||
)}
|
||||
</Table.Th>
|
||||
<Table.Td>
|
||||
<div className={styles.control}>{option.component}</div>
|
||||
</Table.Td>
|
||||
|
||||
Reference in New Issue
Block a user