mirror of
https://github.com/jeffvli/feishin.git
synced 2026-08-08 05:12:57 +02:00
redesign lyrics settings form
This commit is contained in:
@@ -561,19 +561,33 @@
|
|||||||
"dynamicImageBlur": "Image blur size",
|
"dynamicImageBlur": "Image blur size",
|
||||||
"dynamicIsImage": "Enable background image",
|
"dynamicIsImage": "Enable background image",
|
||||||
"followCurrentLyric": "Follow current lyric",
|
"followCurrentLyric": "Follow current lyric",
|
||||||
|
"followCurrentLyric_description": "Automatically scroll to keep the active lyric line in view",
|
||||||
"lyricFollowScrollAlignment": "Lyrics follow alignment",
|
"lyricFollowScrollAlignment": "Lyrics follow alignment",
|
||||||
|
"lyricFollowScrollAlignment_description": "Vertical position of the active line while following",
|
||||||
"lyricAlignment": "Lyric alignment",
|
"lyricAlignment": "Lyric alignment",
|
||||||
|
"lyricAlignment_description": "Horizontal alignment of lyric text",
|
||||||
"lyricOffset": "Lyrics offset (ms)",
|
"lyricOffset": "Lyrics offset (ms)",
|
||||||
"lyricGap": "Lyric gap",
|
"lyricGap": "Lyric gap",
|
||||||
|
"lyricGapSynchronized_description": "Vertical spacing between synchronized lyric lines",
|
||||||
|
"lyricGapUnsynchronized_description": "Vertical spacing between unsynchronized lyric lines",
|
||||||
"lyricLineLeadTime": "Line lead time (ms)",
|
"lyricLineLeadTime": "Line lead time (ms)",
|
||||||
|
"lyricLineLeadTime_description": "Advance the active line this many milliseconds before its timestamp",
|
||||||
"lyricPaddingLeft": "Lyrics left padding (%)",
|
"lyricPaddingLeft": "Lyrics left padding (%)",
|
||||||
|
"lyricPaddingLeft_description": "Left padding as a percentage of the lyrics area",
|
||||||
"lyricPaddingRight": "Lyrics right padding (%)",
|
"lyricPaddingRight": "Lyrics right padding (%)",
|
||||||
|
"lyricPaddingRight_description": "Right padding as a percentage of the lyrics area",
|
||||||
"lyricSize": "Lyric size",
|
"lyricSize": "Lyric size",
|
||||||
|
"lyricSizeSynchronized_description": "Font size for synchronized (timed) lyrics",
|
||||||
|
"lyricSizeUnsynchronized_description": "Font size for unsynchronized (plain) lyrics",
|
||||||
"lyricOpacityNonActive": "Non-active lyric opacity",
|
"lyricOpacityNonActive": "Non-active lyric opacity",
|
||||||
|
"lyricOpacityNonActive_description": "Opacity of lyric lines that are not currently active",
|
||||||
"lyricScaleNonActive": "Non-active lyric scale",
|
"lyricScaleNonActive": "Non-active lyric scale",
|
||||||
|
"lyricScaleNonActive_description": "Scale of lyric lines that are not currently active",
|
||||||
"opacity": "Opacity",
|
"opacity": "Opacity",
|
||||||
"showLyricMatch": "Show lyric match",
|
"showLyricMatch": "Show lyric match",
|
||||||
|
"showLyricMatch_description": "Show the song name and artist of the matched lyrics",
|
||||||
"showLyricProvider": "Show lyric provider",
|
"showLyricProvider": "Show lyric provider",
|
||||||
|
"showLyricProvider_description": "Show which provider supplied the current lyrics",
|
||||||
"synchronized": "Synchronized",
|
"synchronized": "Synchronized",
|
||||||
"unsynchronized": "Unsynchronized",
|
"unsynchronized": "Unsynchronized",
|
||||||
"useImageAspectRatio": "Use image aspect ratio"
|
"useImageAspectRatio": "Use image aspect ratio"
|
||||||
@@ -668,6 +682,7 @@
|
|||||||
"audio": "Audio",
|
"audio": "Audio",
|
||||||
"lyrics": "Lyrics",
|
"lyrics": "Lyrics",
|
||||||
"lyricsDisplay": "Lyrics Display",
|
"lyricsDisplay": "Lyrics Display",
|
||||||
|
"lyricsTranslation": "Lyrics Translation",
|
||||||
"transcoding": "Transcoding",
|
"transcoding": "Transcoding",
|
||||||
"discord": "Discord",
|
"discord": "Discord",
|
||||||
"logger": "Logger",
|
"logger": "Logger",
|
||||||
@@ -891,9 +906,9 @@
|
|||||||
"discordUpdateInterval_description": "The time in seconds between each update (minimum 15 seconds)",
|
"discordUpdateInterval_description": "The time in seconds between each update (minimum 15 seconds)",
|
||||||
"enableAutoTranslation_description": "Enable translation automatically when lyrics are loaded",
|
"enableAutoTranslation_description": "Enable translation automatically when lyrics are loaded",
|
||||||
"enableAutoTranslation": "Enable auto translation",
|
"enableAutoTranslation": "Enable auto translation",
|
||||||
"enableFurigana_description": "Display pronunciation guides (furigana) over Japanese kanji lyrics.",
|
"enableFurigana_description": "Display pronunciation guides (furigana) over Japanese kanji lyrics",
|
||||||
"enableFurigana": "Enable furigana generation",
|
"enableFurigana": "Enable furigana generation",
|
||||||
"enableRomaji_description": "Display a romaji pronunciation line under Japanese lyrics.",
|
"enableRomaji_description": "Display a romaji pronunciation line under Japanese lyrics",
|
||||||
"enableRomaji": "Enable romaji generation",
|
"enableRomaji": "Enable romaji generation",
|
||||||
"equalizer_descriptionMpv": "Parametric equalizer via FFmpeg lavfi (MPV)",
|
"equalizer_descriptionMpv": "Parametric equalizer via FFmpeg lavfi (MPV)",
|
||||||
"equalizer_descriptionWebAudio": "Parametric equalizer via Web Audio API",
|
"equalizer_descriptionWebAudio": "Parametric equalizer via Web Audio API",
|
||||||
@@ -1003,7 +1018,7 @@
|
|||||||
"listenbrainz": "Show ListenBrainz links",
|
"listenbrainz": "Show ListenBrainz links",
|
||||||
"lastfmApiKey_description": "The API key for {{lastfm}}. Required for cover art",
|
"lastfmApiKey_description": "The API key for {{lastfm}}. Required for cover art",
|
||||||
"lastfmApiKey": "{{lastfm}} API key",
|
"lastfmApiKey": "{{lastfm}} API key",
|
||||||
"lyricFetch_description": "Fetch lyrics from various internet sources",
|
"lyricFetch_description": "Fetch lyrics from the selected providers",
|
||||||
"lyricFetch": "Fetch lyrics from the internet",
|
"lyricFetch": "Fetch lyrics from the internet",
|
||||||
"lyricFetchProvider_description": "Select the providers to fetch lyrics from",
|
"lyricFetchProvider_description": "Select the providers to fetch lyrics from",
|
||||||
"lyricFetchProvider": "Providers to fetch lyrics from",
|
"lyricFetchProvider": "Providers to fetch lyrics from",
|
||||||
@@ -1212,9 +1227,9 @@
|
|||||||
"transcodeFormat": "Format to transcode",
|
"transcodeFormat": "Format to transcode",
|
||||||
"translationApiKey_description": "API key for translation (global service endpoint only)",
|
"translationApiKey_description": "API key for translation (global service endpoint only)",
|
||||||
"translationApiKey": "Translation API key",
|
"translationApiKey": "Translation API key",
|
||||||
"translationApiProvider_description": "API provider for translation",
|
"translationApiProvider_description": "Selects the API provider used for translation",
|
||||||
"translationApiProvider": "Translation API provider",
|
"translationApiProvider": "Translation API provider",
|
||||||
"translationTargetLanguage_description": "Target language for translation",
|
"translationTargetLanguage_description": "Sets the target language used for automatic translation",
|
||||||
"translationTargetLanguage": "Translation target language",
|
"translationTargetLanguage": "Translation target language",
|
||||||
"trayEnabled_description": "Show/hide tray icon/menu. If disabled, also disables minimize/exit to tray",
|
"trayEnabled_description": "Show/hide tray icon/menu. If disabled, also disables minimize/exit to tray",
|
||||||
"trayEnabled": "Show tray",
|
"trayEnabled": "Show tray",
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
import { languages } from '/@/i18n/i18n';
|
import { languages } from '/@/i18n/i18n';
|
||||||
import {
|
import {
|
||||||
SettingOption,
|
ListConfigBooleanControl,
|
||||||
SettingsSection,
|
ListConfigTable,
|
||||||
} from '/@/renderer/features/settings/components/settings-section';
|
} from '/@/renderer/features/shared/components/list-config-menu';
|
||||||
import {
|
import {
|
||||||
useLyricsDisplaySettings,
|
useLyricsDisplaySettings,
|
||||||
useLyricsSettings,
|
useLyricsSettings,
|
||||||
@@ -19,9 +19,7 @@ import { SegmentedControl } from '/@/shared/components/segmented-control/segment
|
|||||||
import { Select } from '/@/shared/components/select/select';
|
import { Select } from '/@/shared/components/select/select';
|
||||||
import { Slider } from '/@/shared/components/slider/slider';
|
import { Slider } from '/@/shared/components/slider/slider';
|
||||||
import { Stack } from '/@/shared/components/stack/stack';
|
import { Stack } from '/@/shared/components/stack/stack';
|
||||||
import { Switch } from '/@/shared/components/switch/switch';
|
|
||||||
import { TextInput } from '/@/shared/components/text-input/text-input';
|
import { TextInput } from '/@/shared/components/text-input/text-input';
|
||||||
import { Text } from '/@/shared/components/text/text';
|
|
||||||
import { LyricSource } from '/@/shared/types/domain-types';
|
import { LyricSource } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
const localSettings = isElectron() ? window.api.localSettings : null;
|
const localSettings = isElectron() ? window.api.localSettings : null;
|
||||||
@@ -58,131 +56,169 @@ export const LyricsSettingsForm = ({ settingsKey }: LyricsSettingsFormProps) =>
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const displayOptions: SettingOption[] = [
|
const displayOptions = [
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<NumberInput
|
<Slider
|
||||||
onBlur={(e) => {
|
defaultValue={displaySettings.fontSize}
|
||||||
const value = Number(e.currentTarget.value);
|
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 });
|
updateDisplaySetting({ fontSize: value });
|
||||||
}}
|
}}
|
||||||
rightSection={
|
|
||||||
<Text pr="md" size="sm">
|
|
||||||
px
|
|
||||||
</Text>
|
|
||||||
}
|
|
||||||
step={1}
|
step={1}
|
||||||
value={displaySettings.fontSize}
|
w="75%"
|
||||||
width={100}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: '',
|
description: t('page.fullscreenPlayer.config.lyricSizeSynchronized', {
|
||||||
title: t(
|
context: 'description',
|
||||||
|
}),
|
||||||
|
id: 'fontSize',
|
||||||
|
label: t(
|
||||||
`${t('page.fullscreenPlayer.config.lyricSize')} (${t('page.fullscreenPlayer.config.synchronized')})`,
|
`${t('page.fullscreenPlayer.config.lyricSize')} (${t('page.fullscreenPlayer.config.synchronized')})`,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<NumberInput
|
<Slider
|
||||||
onBlur={(e) => {
|
defaultValue={displaySettings.fontSizeUnsync}
|
||||||
const value = Number(e.currentTarget.value);
|
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 });
|
updateDisplaySetting({ fontSizeUnsync: value });
|
||||||
}}
|
}}
|
||||||
rightSection={
|
|
||||||
<Text pr="md" size="sm">
|
|
||||||
px
|
|
||||||
</Text>
|
|
||||||
}
|
|
||||||
step={1}
|
step={1}
|
||||||
value={displaySettings.fontSizeUnsync}
|
w="75%"
|
||||||
width={100}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: '',
|
description: t('page.fullscreenPlayer.config.lyricSizeUnsynchronized', {
|
||||||
title: t(
|
context: 'description',
|
||||||
|
}),
|
||||||
|
id: 'fontSizeUnsync',
|
||||||
|
label: t(
|
||||||
`${t('page.fullscreenPlayer.config.lyricSize')} (${t('page.fullscreenPlayer.config.unsynchronized')})`,
|
`${t('page.fullscreenPlayer.config.lyricSize')} (${t('page.fullscreenPlayer.config.unsynchronized')})`,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<NumberInput
|
<Slider
|
||||||
onBlur={(e) => {
|
defaultValue={displaySettings.gap}
|
||||||
const value = Number(e.currentTarget.value);
|
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 });
|
updateDisplaySetting({ gap: value });
|
||||||
}}
|
}}
|
||||||
rightSection={
|
|
||||||
<Text pr="md" size="sm">
|
|
||||||
px
|
|
||||||
</Text>
|
|
||||||
}
|
|
||||||
step={1}
|
step={1}
|
||||||
value={displaySettings.gap}
|
w="75%"
|
||||||
width={100}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: '',
|
description: t('page.fullscreenPlayer.config.lyricGapSynchronized', {
|
||||||
title: t(
|
context: 'description',
|
||||||
|
}),
|
||||||
|
id: 'gap',
|
||||||
|
label: t(
|
||||||
`${t('page.fullscreenPlayer.config.lyricGap')} (${t('page.fullscreenPlayer.config.synchronized')})`,
|
`${t('page.fullscreenPlayer.config.lyricGap')} (${t('page.fullscreenPlayer.config.synchronized')})`,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<NumberInput
|
<Slider
|
||||||
onBlur={(e) => {
|
defaultValue={displaySettings.gapUnsync}
|
||||||
const value = Number(e.currentTarget.value);
|
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 });
|
updateDisplaySetting({ gapUnsync: value });
|
||||||
}}
|
}}
|
||||||
rightSection={
|
|
||||||
<Text pr="md" size="sm">
|
|
||||||
px
|
|
||||||
</Text>
|
|
||||||
}
|
|
||||||
step={1}
|
step={1}
|
||||||
value={displaySettings.gapUnsync}
|
w="75%"
|
||||||
width={100}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: '',
|
description: t('page.fullscreenPlayer.config.lyricGapUnsynchronized', {
|
||||||
title: t(
|
context: 'description',
|
||||||
|
}),
|
||||||
|
id: 'gapUnsync',
|
||||||
|
label: t(
|
||||||
`${t('page.fullscreenPlayer.config.lyricGap')} (${t('page.fullscreenPlayer.config.unsynchronized')})`,
|
`${t('page.fullscreenPlayer.config.lyricGap')} (${t('page.fullscreenPlayer.config.unsynchronized')})`,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<Slider
|
<Slider
|
||||||
defaultValue={displaySettings.paddingLeft ?? 0}
|
defaultValue={displaySettings.paddingLeft ?? 0}
|
||||||
label={(value) => `${value}%`}
|
label={(value) => `${value}%`}
|
||||||
|
marks={[
|
||||||
|
{ label: '0', value: 0 },
|
||||||
|
{ label: '25', value: 25 },
|
||||||
|
{ label: '50', value: 50 },
|
||||||
|
]}
|
||||||
max={50}
|
max={50}
|
||||||
min={0}
|
min={0}
|
||||||
onChangeEnd={(value) => {
|
onChangeEnd={(value) => {
|
||||||
updateDisplaySetting({ paddingLeft: value });
|
updateDisplaySetting({ paddingLeft: value });
|
||||||
}}
|
}}
|
||||||
step={1}
|
step={1}
|
||||||
w={100}
|
w="100%"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: '',
|
description: t('page.fullscreenPlayer.config.lyricPaddingLeft', {
|
||||||
title: t('page.fullscreenPlayer.config.lyricPaddingLeft'),
|
context: 'description',
|
||||||
|
}),
|
||||||
|
id: 'paddingLeft',
|
||||||
|
label: t('page.fullscreenPlayer.config.lyricPaddingLeft'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<Slider
|
<Slider
|
||||||
defaultValue={displaySettings.paddingRight ?? 0}
|
defaultValue={displaySettings.paddingRight ?? 0}
|
||||||
label={(value) => `${value}%`}
|
label={(value) => `${value}%`}
|
||||||
|
marks={[
|
||||||
|
{ label: '0', value: 0 },
|
||||||
|
{ label: '25', value: 25 },
|
||||||
|
{ label: '50', value: 50 },
|
||||||
|
]}
|
||||||
max={50}
|
max={50}
|
||||||
min={0}
|
min={0}
|
||||||
onChangeEnd={(value) => {
|
onChangeEnd={(value) => {
|
||||||
updateDisplaySetting({ paddingRight: value });
|
updateDisplaySetting({ paddingRight: value });
|
||||||
}}
|
}}
|
||||||
step={1}
|
step={1}
|
||||||
w={100}
|
w="100%"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: '',
|
description: t('page.fullscreenPlayer.config.lyricPaddingRight', {
|
||||||
title: t('page.fullscreenPlayer.config.lyricPaddingRight'),
|
context: 'description',
|
||||||
|
}),
|
||||||
|
id: 'paddingRight',
|
||||||
|
label: t('page.fullscreenPlayer.config.lyricPaddingRight'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<SegmentedControl
|
<SegmentedControl
|
||||||
data={[
|
data={[
|
||||||
{ label: t('common.left'), value: 'left' },
|
{ label: t('common.left'), value: 'left' },
|
||||||
@@ -196,41 +232,57 @@ export const LyricsSettingsForm = ({ settingsKey }: LyricsSettingsFormProps) =>
|
|||||||
updateLyricsSetting({ alignment: value as 'center' | 'left' | 'right' })
|
updateLyricsSetting({ alignment: value as 'center' | 'left' | 'right' })
|
||||||
}
|
}
|
||||||
value={lyricsSettings.alignment}
|
value={lyricsSettings.alignment}
|
||||||
|
w="100%"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: '',
|
description: t('page.fullscreenPlayer.config.lyricAlignment', {
|
||||||
title: t('page.fullscreenPlayer.config.lyricAlignment'),
|
context: 'description',
|
||||||
|
}),
|
||||||
|
id: 'alignment',
|
||||||
|
label: t('page.fullscreenPlayer.config.lyricAlignment'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<Switch
|
<ListConfigBooleanControl
|
||||||
aria-label="Follow lyrics"
|
onChange={(value) => updateLyricsSetting({ follow: value })}
|
||||||
defaultChecked={lyricsSettings.follow}
|
value={lyricsSettings.follow}
|
||||||
onChange={(e) => updateLyricsSetting({ follow: e.currentTarget.checked })}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: '',
|
description: t('page.fullscreenPlayer.config.followCurrentLyric', {
|
||||||
title: t('page.fullscreenPlayer.config.followCurrentLyric'),
|
context: 'description',
|
||||||
|
}),
|
||||||
|
id: 'follow',
|
||||||
|
label: t('page.fullscreenPlayer.config.followCurrentLyric'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<Slider
|
<Slider
|
||||||
defaultValue={lyricsSettings.followScrollAlignment ?? 0}
|
defaultValue={lyricsSettings.followScrollAlignment ?? 0}
|
||||||
label={(value) => value.toString()}
|
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}
|
max={50}
|
||||||
min={-50}
|
min={-50}
|
||||||
onChangeEnd={(value) => {
|
onChangeEnd={(value) => {
|
||||||
updateLyricsSetting({ followScrollAlignment: value });
|
updateLyricsSetting({ followScrollAlignment: value });
|
||||||
}}
|
}}
|
||||||
step={1}
|
step={1}
|
||||||
w={100}
|
w="100%"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: '',
|
description: t('page.fullscreenPlayer.config.lyricFollowScrollAlignment', {
|
||||||
title: t('page.fullscreenPlayer.config.lyricFollowScrollAlignment'),
|
context: 'description',
|
||||||
|
}),
|
||||||
|
id: 'followScrollAlignment',
|
||||||
|
label: t('page.fullscreenPlayer.config.lyricFollowScrollAlignment'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<NumberInput
|
<NumberInput
|
||||||
defaultValue={lyricsSettings.lineLeadTimeMs}
|
defaultValue={lyricsSettings.lineLeadTimeMs}
|
||||||
max={3000}
|
max={3000}
|
||||||
@@ -243,14 +295,24 @@ export const LyricsSettingsForm = ({ settingsKey }: LyricsSettingsFormProps) =>
|
|||||||
width={100}
|
width={100}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: '',
|
description: t('page.fullscreenPlayer.config.lyricLineLeadTime', {
|
||||||
title: t('page.fullscreenPlayer.config.lyricLineLeadTime'),
|
context: 'description',
|
||||||
|
}),
|
||||||
|
id: 'lineLeadTimeMs',
|
||||||
|
label: t('page.fullscreenPlayer.config.lyricLineLeadTime'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<Slider
|
<Slider
|
||||||
defaultValue={displaySettings.opacityNonActive}
|
defaultValue={displaySettings.opacityNonActive}
|
||||||
label={(e) => (e * 100).toFixed(0) + '%'}
|
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}
|
max={1.0}
|
||||||
min={0.0}
|
min={0.0}
|
||||||
onChangeEnd={(e) => {
|
onChangeEnd={(e) => {
|
||||||
@@ -259,17 +321,25 @@ export const LyricsSettingsForm = ({ settingsKey }: LyricsSettingsFormProps) =>
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
step={0.01}
|
step={0.01}
|
||||||
w={100}
|
w="100%"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: '',
|
description: t('page.fullscreenPlayer.config.lyricOpacityNonActive', {
|
||||||
title: t(`${t('page.fullscreenPlayer.config.lyricOpacityNonActive')}`, {}),
|
context: 'description',
|
||||||
|
}),
|
||||||
|
id: 'opacityNonActive',
|
||||||
|
label: t('page.fullscreenPlayer.config.lyricOpacityNonActive'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<Slider
|
<Slider
|
||||||
defaultValue={displaySettings.scaleNonActive}
|
defaultValue={displaySettings.scaleNonActive}
|
||||||
label={(e) => (e * 100).toFixed(0) + '%'}
|
label={(e) => (e * 100).toFixed(0) + '%'}
|
||||||
|
marks={[
|
||||||
|
{ label: '50%', value: 0.5 },
|
||||||
|
{ label: '75%', value: 0.75 },
|
||||||
|
{ label: '100%', value: 1 },
|
||||||
|
]}
|
||||||
max={1.0}
|
max={1.0}
|
||||||
min={0.5}
|
min={0.5}
|
||||||
onChangeEnd={(e) => {
|
onChangeEnd={(e) => {
|
||||||
@@ -278,69 +348,74 @@ export const LyricsSettingsForm = ({ settingsKey }: LyricsSettingsFormProps) =>
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
step={0.01}
|
step={0.01}
|
||||||
w={100}
|
w="100%"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: '',
|
description: t('page.fullscreenPlayer.config.lyricScaleNonActive', {
|
||||||
title: t(`${t('page.fullscreenPlayer.config.lyricScaleNonActive')}`, {}),
|
context: 'description',
|
||||||
|
}),
|
||||||
|
id: 'scaleNonActive',
|
||||||
|
label: t('page.fullscreenPlayer.config.lyricScaleNonActive'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<Switch
|
<ListConfigBooleanControl
|
||||||
aria-label="Show match"
|
onChange={(value) => updateLyricsSetting({ showMatch: value })}
|
||||||
defaultChecked={lyricsSettings.showMatch}
|
value={lyricsSettings.showMatch}
|
||||||
onChange={(e) => updateLyricsSetting({ showMatch: e.currentTarget.checked })}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: '',
|
description: t('page.fullscreenPlayer.config.showLyricMatch', {
|
||||||
title: t('page.fullscreenPlayer.config.showLyricMatch'),
|
context: 'description',
|
||||||
|
}),
|
||||||
|
id: 'showMatch',
|
||||||
|
label: t('page.fullscreenPlayer.config.showLyricMatch'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<Switch
|
<ListConfigBooleanControl
|
||||||
aria-label="Show provider"
|
onChange={(value) => updateLyricsSetting({ showProvider: value })}
|
||||||
defaultChecked={lyricsSettings.showProvider}
|
value={lyricsSettings.showProvider}
|
||||||
onChange={(e) => updateLyricsSetting({ showProvider: e.currentTarget.checked })}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: '',
|
description: t('page.fullscreenPlayer.config.showLyricProvider', {
|
||||||
title: t('page.fullscreenPlayer.config.showLyricProvider'),
|
context: 'description',
|
||||||
|
}),
|
||||||
|
id: 'showProvider',
|
||||||
|
label: t('page.fullscreenPlayer.config.showLyricProvider'),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const lyricOptions: SettingOption[] = [
|
const lyricOptions = [
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<Switch
|
<ListConfigBooleanControl
|
||||||
aria-label="Prefer local lyrics"
|
onChange={(value) => updateLyricsSetting({ preferLocalLyrics: value })}
|
||||||
defaultChecked={lyricsSettings.preferLocalLyrics}
|
value={lyricsSettings.preferLocalLyrics}
|
||||||
onChange={(e) =>
|
|
||||||
updateLyricsSetting({ preferLocalLyrics: e.currentTarget.checked })
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: t('setting.preferLocalLyrics', {
|
description: t('setting.preferLocalLyrics', {
|
||||||
context: 'description',
|
context: 'description',
|
||||||
}),
|
}),
|
||||||
|
id: 'preferLocalLyrics',
|
||||||
isHidden: !isElectron(),
|
isHidden: !isElectron(),
|
||||||
title: t('setting.preferLocalLyrics'),
|
label: t('setting.preferLocalLyrics'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<Switch
|
<ListConfigBooleanControl
|
||||||
aria-label="Enable fetching lyrics"
|
onChange={(value) => updateLyricsSetting({ fetch: value })}
|
||||||
defaultChecked={lyricsSettings.fetch}
|
value={lyricsSettings.fetch}
|
||||||
onChange={(e) => updateLyricsSetting({ fetch: e.currentTarget.checked })}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: t('setting.lyricFetch', {
|
description: t('setting.lyricFetch', {
|
||||||
context: 'description',
|
context: 'description',
|
||||||
}),
|
}),
|
||||||
|
id: 'fetch',
|
||||||
isHidden: !isElectron(),
|
isHidden: !isElectron(),
|
||||||
title: t('setting.lyricFetch'),
|
label: t('setting.lyricFetch'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
aria-label="Lyric providers"
|
aria-label="Lyric providers"
|
||||||
clearable
|
clearable
|
||||||
@@ -350,97 +425,96 @@ export const LyricsSettingsForm = ({ settingsKey }: LyricsSettingsFormProps) =>
|
|||||||
localSettings?.set('lyrics', e);
|
localSettings?.set('lyrics', e);
|
||||||
updateLyricsSetting({ sources: e.map((source) => source as LyricSource) });
|
updateLyricsSetting({ sources: e.map((source) => source as LyricSource) });
|
||||||
}}
|
}}
|
||||||
width={300}
|
width="100%"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: t('setting.lyricFetchProvider', {
|
description: t('setting.lyricFetchProvider', {
|
||||||
context: 'description',
|
context: 'description',
|
||||||
}),
|
}),
|
||||||
|
id: 'sources',
|
||||||
isHidden: !isElectron(),
|
isHidden: !isElectron(),
|
||||||
title: t('setting.lyricFetchProvider'),
|
label: t('setting.lyricFetchProvider'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<Switch
|
<ListConfigBooleanControl
|
||||||
aria-label="Enable furigana"
|
onChange={(value) => updateLyricsSetting({ enableFurigana: value })}
|
||||||
defaultChecked={lyricsSettings.enableFurigana}
|
value={lyricsSettings.enableFurigana ?? false}
|
||||||
onChange={(e) =>
|
|
||||||
updateLyricsSetting({ enableFurigana: e.currentTarget.checked })
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: t('setting.enableFurigana', {
|
description: t('setting.enableFurigana', {
|
||||||
context: 'description',
|
context: 'description',
|
||||||
}),
|
}),
|
||||||
title: t('setting.enableFurigana'),
|
id: 'enableFurigana',
|
||||||
|
label: t('setting.enableFurigana'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<Switch
|
<ListConfigBooleanControl
|
||||||
aria-label="Enable romaji"
|
onChange={(value) => updateLyricsSetting({ enableRomaji: value })}
|
||||||
defaultChecked={lyricsSettings.enableRomaji}
|
value={lyricsSettings.enableRomaji ?? false}
|
||||||
onChange={(e) => updateLyricsSetting({ enableRomaji: e.currentTarget.checked })}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: t('setting.enableRomaji', {
|
description: t('setting.enableRomaji', {
|
||||||
context: 'description',
|
context: 'description',
|
||||||
}),
|
}),
|
||||||
title: t('setting.enableRomaji'),
|
id: 'enableRomaji',
|
||||||
|
label: t('setting.enableRomaji'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<Switch
|
<ListConfigBooleanControl
|
||||||
aria-label="Enable NetEase translations"
|
onChange={(value) => {
|
||||||
defaultChecked={lyricsSettings.enableNeteaseTranslation}
|
updateLyricsSetting({ enableNeteaseTranslation: value });
|
||||||
onChange={(e) => {
|
localSettings?.set('enableNeteaseTranslation', value);
|
||||||
const isChecked = e.currentTarget.checked;
|
|
||||||
updateLyricsSetting({ enableNeteaseTranslation: isChecked });
|
|
||||||
localSettings?.set('enableNeteaseTranslation', isChecked);
|
|
||||||
}}
|
}}
|
||||||
|
value={lyricsSettings.enableNeteaseTranslation}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: t('setting.neteaseTranslation', {
|
description: t('setting.neteaseTranslation', {
|
||||||
context: 'description',
|
context: 'description',
|
||||||
}),
|
}),
|
||||||
|
id: 'enableNeteaseTranslation',
|
||||||
isHidden: !isElectron(),
|
isHidden: !isElectron(),
|
||||||
title: t('setting.neteaseTranslation'),
|
label: t('setting.neteaseTranslation'),
|
||||||
},
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const translationSettings = [
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<NumberInput
|
<ListConfigBooleanControl
|
||||||
defaultValue={lyricsSettings.delayMs}
|
onChange={(value) => updateLyricsSetting({ enableAutoTranslation: value })}
|
||||||
onBlur={(e) => {
|
value={lyricsSettings.enableAutoTranslation}
|
||||||
const value = Number(e.currentTarget.value);
|
|
||||||
updateLyricsSetting({ delayMs: value });
|
|
||||||
}}
|
|
||||||
step={10}
|
|
||||||
width={100}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: t('setting.lyricOffset', {
|
description: t('setting.enableAutoTranslation', {
|
||||||
context: 'description',
|
context: 'description',
|
||||||
}),
|
}),
|
||||||
|
id: 'enableAutoTranslation',
|
||||||
isHidden: !isElectron(),
|
isHidden: !isElectron(),
|
||||||
title: t('setting.lyricOffset'),
|
label: t('setting.enableAutoTranslation'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<Select
|
<Select
|
||||||
data={languages}
|
data={languages}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
updateLyricsSetting({ translationTargetLanguage: value });
|
updateLyricsSetting({ translationTargetLanguage: value });
|
||||||
}}
|
}}
|
||||||
value={lyricsSettings.translationTargetLanguage}
|
value={lyricsSettings.translationTargetLanguage}
|
||||||
|
width="100%"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: t('setting.translationTargetLanguage', {
|
description: t('setting.translationTargetLanguage', {
|
||||||
context: 'description',
|
context: 'description',
|
||||||
}),
|
}),
|
||||||
|
id: 'translationTargetLanguage',
|
||||||
isHidden: !isElectron(),
|
isHidden: !isElectron(),
|
||||||
title: t('setting.translationTargetLanguage'),
|
label: t('setting.translationTargetLanguage'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<Select
|
<Select
|
||||||
clearable
|
clearable
|
||||||
data={['Microsoft Azure', 'Google Cloud']}
|
data={['Microsoft Azure', 'Google Cloud']}
|
||||||
@@ -448,54 +522,45 @@ export const LyricsSettingsForm = ({ settingsKey }: LyricsSettingsFormProps) =>
|
|||||||
updateLyricsSetting({ translationApiProvider: value });
|
updateLyricsSetting({ translationApiProvider: value });
|
||||||
}}
|
}}
|
||||||
value={lyricsSettings.translationApiProvider}
|
value={lyricsSettings.translationApiProvider}
|
||||||
|
width="100%"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: t('setting.translationApiProvider', {
|
description: t('setting.translationApiProvider', {
|
||||||
context: 'description',
|
context: 'description',
|
||||||
}),
|
}),
|
||||||
|
id: 'translationApiProvider',
|
||||||
isHidden: !isElectron(),
|
isHidden: !isElectron(),
|
||||||
title: t('setting.translationApiProvider'),
|
label: t('setting.translationApiProvider'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
control: (
|
component: (
|
||||||
<TextInput
|
<TextInput
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
updateLyricsSetting({ translationApiKey: e.currentTarget.value });
|
updateLyricsSetting({ translationApiKey: e.currentTarget.value });
|
||||||
}}
|
}}
|
||||||
value={lyricsSettings.translationApiKey}
|
value={lyricsSettings.translationApiKey}
|
||||||
|
width="100%"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
description: t('setting.translationApiKey', {
|
description: t('setting.translationApiKey', {
|
||||||
context: 'description',
|
context: 'description',
|
||||||
}),
|
}),
|
||||||
|
id: 'translationApiKey',
|
||||||
isHidden: !isElectron(),
|
isHidden: !isElectron(),
|
||||||
title: t('setting.translationApiKey'),
|
label: 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'),
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="md" p="md">
|
<Stack gap="sm">
|
||||||
<Fieldset legend={t('page.setting.lyricsDisplay')}>
|
|
||||||
<SettingsSection options={displayOptions} />
|
|
||||||
</Fieldset>
|
|
||||||
<Fieldset legend={t('page.setting.lyrics')}>
|
<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>
|
</Fieldset>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ import { SegmentedControl } from '/@/shared/components/segmented-control/segment
|
|||||||
import { Select } from '/@/shared/components/select/select';
|
import { Select } from '/@/shared/components/select/select';
|
||||||
import { Slider } from '/@/shared/components/slider/slider';
|
import { Slider } from '/@/shared/components/slider/slider';
|
||||||
import { Stack } from '/@/shared/components/stack/stack';
|
import { Stack } from '/@/shared/components/stack/stack';
|
||||||
import { Text } from '/@/shared/components/text/text';
|
|
||||||
import { useMediaQuery } from '/@/shared/hooks/use-media-query';
|
import { useMediaQuery } from '/@/shared/hooks/use-media-query';
|
||||||
import { useThrottledCallback } from '/@/shared/hooks/use-throttled-callback';
|
import { useThrottledCallback } from '/@/shared/hooks/use-throttled-callback';
|
||||||
import { useThrottledValue } from '/@/shared/hooks/use-throttled-value';
|
import { useThrottledValue } from '/@/shared/hooks/use-throttled-value';
|
||||||
@@ -201,17 +200,9 @@ const AutoDJButton = () => {
|
|||||||
w="96px"
|
w="96px"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
description: t('setting.autoDJ_itemCount_description'),
|
||||||
id: 'itemCount',
|
id: 'itemCount',
|
||||||
label: (
|
label: t('setting.autoDJ_itemCount'),
|
||||||
<Stack gap="xs">
|
|
||||||
<Text isNoSelect size="sm">
|
|
||||||
{t('setting.autoDJ_itemCount')}
|
|
||||||
</Text>
|
|
||||||
<Text isMuted isNoSelect size="xs">
|
|
||||||
{t('setting.autoDJ_itemCount_description')}
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: (
|
component: (
|
||||||
@@ -239,17 +230,9 @@ const AutoDJButton = () => {
|
|||||||
w="144px"
|
w="144px"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
description: t('setting.autoDJ_timing_description'),
|
||||||
id: 'timing',
|
id: 'timing',
|
||||||
label: (
|
label: t('setting.autoDJ_timing'),
|
||||||
<Stack gap="xs">
|
|
||||||
<Text isNoSelect size="sm">
|
|
||||||
{t('setting.autoDJ_timing')}
|
|
||||||
</Text>
|
|
||||||
<Text isMuted isNoSelect size="xs">
|
|
||||||
{t('setting.autoDJ_timing_description')}
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -279,17 +262,9 @@ const AutoDJButton = () => {
|
|||||||
value={settings.allowDuplicates}
|
value={settings.allowDuplicates}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
description: t('setting.autoDJ_allowDuplicates_description'),
|
||||||
id: 'allowDuplicates',
|
id: 'allowDuplicates',
|
||||||
label: (
|
label: t('setting.autoDJ_allowDuplicates'),
|
||||||
<Stack gap="xs">
|
|
||||||
<Text isNoSelect size="sm">
|
|
||||||
{t('setting.autoDJ_allowDuplicates')}
|
|
||||||
</Text>
|
|
||||||
<Text isMuted isNoSelect size="xs">
|
|
||||||
{t('setting.autoDJ_allowDuplicates_description')}
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: (
|
component: (
|
||||||
@@ -304,17 +279,9 @@ const AutoDJButton = () => {
|
|||||||
value={settings.onlySimilar}
|
value={settings.onlySimilar}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
description: t('setting.autoDJ_onlySimilar_description'),
|
||||||
id: 'onlySimilar',
|
id: 'onlySimilar',
|
||||||
label: (
|
label: t('setting.autoDJ_onlySimilar'),
|
||||||
<Stack gap="xs">
|
|
||||||
<Text isNoSelect size="sm">
|
|
||||||
{t('setting.autoDJ_onlySimilar')}
|
|
||||||
</Text>
|
|
||||||
<Text isMuted isNoSelect size="xs">
|
|
||||||
{t('setting.autoDJ_onlySimilar_description')}
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[setSettings, settings.allowDuplicates, settings.onlySimilar, t],
|
[setSettings, settings.allowDuplicates, settings.onlySimilar, t],
|
||||||
|
|||||||
@@ -6,13 +6,13 @@
|
|||||||
width: 50%;
|
width: 50%;
|
||||||
padding: var(--theme-spacing-md) var(--theme-spacing-md) var(--theme-spacing-md) 0;
|
padding: var(--theme-spacing-md) var(--theme-spacing-md) var(--theme-spacing-md) 0;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
vertical-align: middle;
|
vertical-align: top;
|
||||||
background-color: initial;
|
background-color: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
.td {
|
.td {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
vertical-align: middle;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control {
|
.control {
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
padding: var(--theme-spacing-md) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.divider-cell {
|
.divider-cell {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { SegmentedControl } from '/@/shared/components/segmented-control/segment
|
|||||||
import { Stack } from '/@/shared/components/stack/stack';
|
import { Stack } from '/@/shared/components/stack/stack';
|
||||||
import { Switch } from '/@/shared/components/switch/switch';
|
import { Switch } from '/@/shared/components/switch/switch';
|
||||||
import { Table } from '/@/shared/components/table/table';
|
import { Table } from '/@/shared/components/table/table';
|
||||||
|
import { Text } from '/@/shared/components/text/text';
|
||||||
import { useDisclosure } from '/@/shared/hooks/use-disclosure';
|
import { useDisclosure } from '/@/shared/hooks/use-disclosure';
|
||||||
import { ItemListKey, ListDisplayType } from '/@/shared/types/types';
|
import { ItemListKey, ListDisplayType } from '/@/shared/types/types';
|
||||||
|
|
||||||
@@ -232,6 +233,7 @@ export const ListConfigTable = ({
|
|||||||
}: {
|
}: {
|
||||||
options: {
|
options: {
|
||||||
component: ReactNode;
|
component: ReactNode;
|
||||||
|
description?: ReactNode | string;
|
||||||
id: string;
|
id: string;
|
||||||
isDivider?: boolean;
|
isDivider?: boolean;
|
||||||
isHidden?: boolean;
|
isHidden?: boolean;
|
||||||
@@ -269,7 +271,20 @@ export const ListConfigTable = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Table.Tr key={option.id}>
|
<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>
|
<Table.Td>
|
||||||
<div className={styles.control}>{option.component}</div>
|
<div className={styles.control}>{option.component}</div>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
|
|||||||
Reference in New Issue
Block a user