mirror of
https://github.com/jeffvli/feishin.git
synced 2026-07-26 04:16:39 +02:00
reorganize and redesign settings
This commit is contained in:
@@ -7,6 +7,10 @@ import styles from './hotkeys-manager-settings.module.css';
|
||||
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { SettingsOptions } from '/@/renderer/features/settings/components/settings-option';
|
||||
import {
|
||||
SettingOption,
|
||||
SettingsSection,
|
||||
} from '/@/renderer/features/settings/components/settings-section';
|
||||
import { useSettingSearchContext } from '/@/renderer/features/settings/context/search-context';
|
||||
import { BindingActions, useHotkeySettings, useSettingsStoreActions } from '/@/renderer/store';
|
||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
@@ -150,12 +154,12 @@ export const HotkeyManagerSettings = () => {
|
||||
20,
|
||||
);
|
||||
|
||||
const handleSetHotkey = useCallback(debouncedSetHotkey, [
|
||||
bindings,
|
||||
globalMediaHotkeys,
|
||||
setSettings,
|
||||
debouncedSetHotkey,
|
||||
]);
|
||||
const handleSetHotkey = useCallback(
|
||||
(binding: BindingActions, e: KeyboardEvent<HTMLInputElement>) => {
|
||||
debouncedSetHotkey(binding, e);
|
||||
},
|
||||
[debouncedSetHotkey],
|
||||
);
|
||||
|
||||
const handleSetGlobalHotkey = useCallback(
|
||||
(binding: BindingActions, e: ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -232,85 +236,97 @@ export const HotkeyManagerSettings = () => {
|
||||
});
|
||||
}, [bindings, keyword]);
|
||||
|
||||
const options: SettingOption[] = [];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsOptions
|
||||
control={<></>}
|
||||
description={t('setting.applicationHotkeys', {
|
||||
context: 'description',
|
||||
postProcess: 'sentenceCase',
|
||||
})}
|
||||
title={t('setting.applicationHotkeys', { postProcess: 'sentenceCase' })}
|
||||
/>
|
||||
<div className={styles.container}>
|
||||
{filteredBindings.map((binding) => (
|
||||
<Group key={`hotkey-${binding}`} wrap="nowrap">
|
||||
<TextInput
|
||||
readOnly
|
||||
style={{ userSelect: 'none' }}
|
||||
value={BINDINGS_MAP[binding as keyof typeof BINDINGS_MAP]}
|
||||
/>
|
||||
<TextInput
|
||||
id={`hotkey-${binding}`}
|
||||
leftSection={<Icon icon="keyboard" />}
|
||||
onBlur={() => setSelected(null)}
|
||||
onChange={() => {}}
|
||||
onKeyDownCapture={(e) => {
|
||||
if (selected !== (binding as BindingActions)) return;
|
||||
handleSetHotkey(binding as BindingActions, e);
|
||||
}}
|
||||
readOnly
|
||||
rightSection={
|
||||
<ActionIcon
|
||||
icon="edit"
|
||||
onClick={() => {
|
||||
setSelected(binding as BindingActions);
|
||||
document.getElementById(`hotkey-${binding}`)?.focus();
|
||||
}}
|
||||
variant="transparent"
|
||||
<SettingsSection
|
||||
extra={
|
||||
<>
|
||||
<SettingsOptions
|
||||
control={<></>}
|
||||
description={t('setting.applicationHotkeys', {
|
||||
context: 'description',
|
||||
postProcess: 'sentenceCase',
|
||||
})}
|
||||
title={t('setting.applicationHotkeys', { postProcess: 'sentenceCase' })}
|
||||
/>
|
||||
<div className={styles.container}>
|
||||
{filteredBindings.map((binding) => (
|
||||
<Group key={`hotkey-${binding}`} wrap="nowrap">
|
||||
<TextInput
|
||||
readOnly
|
||||
style={{ userSelect: 'none' }}
|
||||
value={BINDINGS_MAP[binding as keyof typeof BINDINGS_MAP]}
|
||||
/>
|
||||
}
|
||||
style={{
|
||||
opacity: selected === (binding as BindingActions) ? 0.8 : 1,
|
||||
outline: duplicateHotkeyMap.includes(
|
||||
bindings[binding as keyof typeof BINDINGS_MAP].hotkey!,
|
||||
)
|
||||
? '1px dashed red'
|
||||
: undefined,
|
||||
}}
|
||||
value={bindings[binding as keyof typeof BINDINGS_MAP].hotkey}
|
||||
/>
|
||||
{isElectron() && (
|
||||
<Checkbox
|
||||
checked={bindings[binding as keyof typeof BINDINGS_MAP].isGlobal}
|
||||
disabled={
|
||||
bindings[binding as keyof typeof BINDINGS_MAP].hotkey === ''
|
||||
}
|
||||
onChange={(e) =>
|
||||
handleSetGlobalHotkey(binding as BindingActions, e)
|
||||
}
|
||||
size="md"
|
||||
style={{
|
||||
opacity: bindings[binding as keyof typeof BINDINGS_MAP]
|
||||
.allowGlobal
|
||||
? 1
|
||||
: 0,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{bindings[binding as keyof typeof BINDINGS_MAP].hotkey && (
|
||||
<ActionIcon
|
||||
icon="x"
|
||||
iconProps={{
|
||||
color: 'error',
|
||||
}}
|
||||
onClick={() => handleClearHotkey(binding as BindingActions)}
|
||||
variant="transparent"
|
||||
/>
|
||||
)}
|
||||
</Group>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
<TextInput
|
||||
id={`hotkey-${binding}`}
|
||||
leftSection={<Icon icon="keyboard" />}
|
||||
onBlur={() => setSelected(null)}
|
||||
onChange={() => {}}
|
||||
onKeyDownCapture={(e) => {
|
||||
if (selected !== (binding as BindingActions)) return;
|
||||
handleSetHotkey(binding as BindingActions, e);
|
||||
}}
|
||||
readOnly
|
||||
rightSection={
|
||||
<ActionIcon
|
||||
icon="edit"
|
||||
onClick={() => {
|
||||
setSelected(binding as BindingActions);
|
||||
document
|
||||
.getElementById(`hotkey-${binding}`)
|
||||
?.focus();
|
||||
}}
|
||||
variant="transparent"
|
||||
/>
|
||||
}
|
||||
style={{
|
||||
opacity: selected === (binding as BindingActions) ? 0.8 : 1,
|
||||
outline: duplicateHotkeyMap.includes(
|
||||
bindings[binding as keyof typeof BINDINGS_MAP].hotkey!,
|
||||
)
|
||||
? '1px dashed red'
|
||||
: undefined,
|
||||
}}
|
||||
value={bindings[binding as keyof typeof BINDINGS_MAP].hotkey}
|
||||
/>
|
||||
{isElectron() && (
|
||||
<Checkbox
|
||||
checked={
|
||||
bindings[binding as keyof typeof BINDINGS_MAP].isGlobal
|
||||
}
|
||||
disabled={
|
||||
bindings[binding as keyof typeof BINDINGS_MAP]
|
||||
.hotkey === ''
|
||||
}
|
||||
onChange={(e) =>
|
||||
handleSetGlobalHotkey(binding as BindingActions, e)
|
||||
}
|
||||
size="md"
|
||||
style={{
|
||||
opacity: bindings[binding as keyof typeof BINDINGS_MAP]
|
||||
.allowGlobal
|
||||
? 1
|
||||
: 0,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{bindings[binding as keyof typeof BINDINGS_MAP].hotkey && (
|
||||
<ActionIcon
|
||||
icon="x"
|
||||
iconProps={{
|
||||
color: 'error',
|
||||
}}
|
||||
onClick={() => handleClearHotkey(binding as BindingActions)}
|
||||
variant="transparent"
|
||||
/>
|
||||
)}
|
||||
</Group>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
options={options}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
import isElectron from 'is-electron';
|
||||
import { Fragment } from 'react/jsx-runtime';
|
||||
|
||||
import { HotkeyManagerSettings } from '/@/renderer/features/settings/components/hotkeys/hotkey-manager-settings';
|
||||
import { MediaSessionSettings } from '/@/renderer/features/settings/components/hotkeys/media-session-settings';
|
||||
import { WindowHotkeySettings } from '/@/renderer/features/settings/components/hotkeys/window-hotkey-settings';
|
||||
import { Divider } from '/@/shared/components/divider/divider';
|
||||
import { Stack } from '/@/shared/components/stack/stack';
|
||||
|
||||
const sections = [
|
||||
{ component: WindowHotkeySettings, hidden: !isElectron(), key: 'window' },
|
||||
{ component: MediaSessionSettings, key: 'media-session' },
|
||||
{ component: HotkeyManagerSettings, key: 'hotkey-manager' },
|
||||
];
|
||||
|
||||
export const HotkeysTab = () => {
|
||||
return (
|
||||
<Stack gap="md">
|
||||
{isElectron() && <WindowHotkeySettings />}
|
||||
<HotkeyManagerSettings />
|
||||
{sections.map(({ component: Section, hidden, key }, index) => (
|
||||
<Fragment key={key}>
|
||||
{!hidden && <Section />}
|
||||
{index < sections.length - 1 && <Divider />}
|
||||
</Fragment>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import isElectron from 'is-electron';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
SettingOption,
|
||||
SettingsSection,
|
||||
} from '/@/renderer/features/settings/components/settings-section';
|
||||
import { usePlaybackSettings, useSettingsStoreActions } from '/@/renderer/store/settings.store';
|
||||
import { Switch } from '/@/shared/components/switch/switch';
|
||||
import { PlayerType } from '/@/shared/types/types';
|
||||
|
||||
const isWindows = isElectron() ? window.api.utils.isWindows() : null;
|
||||
const isDesktop = isElectron();
|
||||
const ipc = isElectron() ? window.api.ipc : null;
|
||||
|
||||
export const MediaSessionSettings = () => {
|
||||
const { t } = useTranslation();
|
||||
const { mediaSession, type: playbackType } = usePlaybackSettings();
|
||||
const { toggleMediaSession } = useSettingsStoreActions();
|
||||
|
||||
function handleMediaSessionChange() {
|
||||
const current = mediaSession;
|
||||
toggleMediaSession();
|
||||
ipc?.send('settings-set', { property: 'mediaSession', value: !current });
|
||||
}
|
||||
|
||||
const mediaSessionOptions: SettingOption[] = [
|
||||
{
|
||||
control: (
|
||||
<Switch
|
||||
aria-label="Toggle media Session"
|
||||
defaultChecked={mediaSession}
|
||||
disabled={!isWindows || !isDesktop || playbackType !== PlayerType.WEB}
|
||||
onChange={handleMediaSessionChange}
|
||||
/>
|
||||
),
|
||||
description: t('setting.mediaSession', {
|
||||
context: 'description',
|
||||
postProcess: 'sentenceCase',
|
||||
}),
|
||||
isHidden: !isWindows || !isDesktop,
|
||||
note: t('common.restartRequired', { postProcess: 'sentenceCase' }),
|
||||
title: t('setting.mediaSession', { postProcess: 'sentenceCase' }),
|
||||
},
|
||||
];
|
||||
|
||||
return <SettingsSection options={mediaSessionOptions} />;
|
||||
};
|
||||
Reference in New Issue
Block a user