mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-15 13:00:25 +02:00
split lyrics settings by key (#1389)
This commit is contained in:
@@ -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;
|
||||
onUpdateOffset: (offsetMs: number) => void;
|
||||
setIndex: (idx: number) => void;
|
||||
settingsKey?: string;
|
||||
synced?: boolean;
|
||||
}
|
||||
|
||||
@@ -35,6 +36,7 @@ export const LyricsActions = ({
|
||||
onTranslateLyric,
|
||||
onUpdateOffset,
|
||||
setIndex,
|
||||
settingsKey = 'default',
|
||||
}: LyricsActionsProps) => {
|
||||
const { t } = useTranslation();
|
||||
const currentSong = usePlayerSong();
|
||||
|
||||
@@ -64,3 +64,13 @@
|
||||
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,
|
||||
UnsynchronizedLyricsProps,
|
||||
} 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 { ComponentErrorBoundary } from '/@/renderer/features/shared/components/component-error-boundary';
|
||||
import { queryClient } from '/@/renderer/lib/react-query';
|
||||
import { useLyricsSettings, usePlayerSong } from '/@/renderer/store';
|
||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
import { Center } from '/@/shared/components/center/center';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||
@@ -35,9 +37,10 @@ import {
|
||||
|
||||
type LyricsProps = {
|
||||
fadeOutNoLyricsMessage?: boolean;
|
||||
settingsKey?: string;
|
||||
};
|
||||
|
||||
export const Lyrics = ({ fadeOutNoLyricsMessage = true }: LyricsProps) => {
|
||||
export const Lyrics = ({ fadeOutNoLyricsMessage = true, settingsKey = 'default' }: LyricsProps) => {
|
||||
const currentSong = usePlayerSong();
|
||||
const {
|
||||
enableAutoTranslation,
|
||||
@@ -301,9 +304,23 @@ export const Lyrics = ({ fadeOutNoLyricsMessage = true }: LyricsProps) => {
|
||||
}
|
||||
}, [currentOffsetMs, lyrics, synced]);
|
||||
|
||||
const handleOpenSettings = () => {
|
||||
openLyricsSettingsModal(settingsKey);
|
||||
};
|
||||
|
||||
return (
|
||||
<ComponentErrorBoundary>
|
||||
<div className={styles.lyricsContainer}>
|
||||
<ActionIcon
|
||||
className={styles.settingsIcon}
|
||||
icon="settings2"
|
||||
iconProps={{ size: 'lg' }}
|
||||
onClick={handleOpenSettings}
|
||||
pos="absolute"
|
||||
right={0}
|
||||
top={0}
|
||||
variant="transparent"
|
||||
/>
|
||||
{isLoadingLyrics ? (
|
||||
<Spinner container />
|
||||
) : (
|
||||
@@ -335,11 +352,13 @@ export const Lyrics = ({ fadeOutNoLyricsMessage = true }: LyricsProps) => {
|
||||
<SynchronizedLyrics
|
||||
{...(lyrics as SynchronizedLyricsProps)}
|
||||
offsetMs={currentOffsetMs}
|
||||
settingsKey={settingsKey}
|
||||
translatedLyrics={showTranslation ? translatedLyrics : null}
|
||||
/>
|
||||
) : (
|
||||
<UnsynchronizedLyrics
|
||||
{...(lyrics as UnsynchronizedLyricsProps)}
|
||||
settingsKey={settingsKey}
|
||||
translatedLyrics={showTranslation ? translatedLyrics : null}
|
||||
/>
|
||||
)}
|
||||
@@ -362,6 +381,7 @@ export const Lyrics = ({ fadeOutNoLyricsMessage = true }: LyricsProps) => {
|
||||
}
|
||||
onUpdateOffset={handleUpdateOffset}
|
||||
setIndex={setIndex}
|
||||
settingsKey={settingsKey}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ import styles from './synchronized-lyrics.module.css';
|
||||
|
||||
import { LyricLine } from '/@/renderer/features/lyrics/lyric-line';
|
||||
import {
|
||||
useLyricsDisplaySettings,
|
||||
useLyricsSettings,
|
||||
usePlaybackType,
|
||||
usePlayerActions,
|
||||
@@ -22,6 +23,7 @@ const mpris = isElectron() && utils?.isLinux() ? window.api.mpris : null;
|
||||
export interface SynchronizedLyricsProps extends Omit<FullLyricsMetadata, 'lyrics'> {
|
||||
lyrics: SynchronizedLyricsArray;
|
||||
offsetMs?: number;
|
||||
settingsKey?: string;
|
||||
style?: React.CSSProperties;
|
||||
translatedLyrics?: null | string;
|
||||
}
|
||||
@@ -32,12 +34,22 @@ export const SynchronizedLyrics = ({
|
||||
name,
|
||||
offsetMs,
|
||||
remote,
|
||||
settingsKey = 'default',
|
||||
source,
|
||||
style,
|
||||
translatedLyrics,
|
||||
}: SynchronizedLyricsProps) => {
|
||||
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 status = usePlayerStatus();
|
||||
const timestamp = usePlayerTimestamp();
|
||||
|
||||
@@ -3,11 +3,12 @@ import { useMemo } from 'react';
|
||||
import styles from './unsynchronized-lyrics.module.css';
|
||||
|
||||
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';
|
||||
|
||||
export interface UnsynchronizedLyricsProps extends Omit<FullLyricsMetadata, 'lyrics'> {
|
||||
lyrics: string;
|
||||
settingsKey?: string;
|
||||
translatedLyrics?: null | string;
|
||||
}
|
||||
|
||||
@@ -16,10 +17,23 @@ export const UnsynchronizedLyrics = ({
|
||||
lyrics,
|
||||
name,
|
||||
remote,
|
||||
settingsKey = 'default',
|
||||
source,
|
||||
translatedLyrics,
|
||||
}: 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(() => {
|
||||
return lyrics.split('\n');
|
||||
}, [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',
|
||||
},
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user