Skip to content

Commit a22ca52

Browse files
Show all keybinds mapped to a key in keyboard view (#182)
* Make the keybinds keyboard view show all mapped keybinds And show the keybind in the view * Make table text selectable and match style of other similar data table * Use layout to make only the list of actions grow when there are multiple ones * Fix alignment issue for multiple tables * Make keyboard scrollable instead of entire window * Fix some widths and margins * Tiny alignment fix * Apply suggestions from code review Co-authored-by: Alexander Bock <[email protected]> --------- Co-authored-by: Alexander Bock <[email protected]>
1 parent 1159c01 commit a22ca52

File tree

5 files changed

+146
-93
lines changed

5 files changed

+146
-93
lines changed

src/panels/KeybindsPanel/FullKeyboard/FullKeyboard.tsx

Lines changed: 58 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import Keyboard, { KeyboardButtonTheme } from 'react-simple-keyboard';
2-
import { Flex, Stack } from '@mantine/core';
2+
import { Flex, ScrollArea, Stack } from '@mantine/core';
33

44
import { useAppSelector } from '@/redux/hooks';
5-
import { Action } from '@/types/types';
5+
import { KeybindInfoType } from '@/types/types';
66

77
import {
88
ArrowsLayout,
@@ -24,7 +24,7 @@ import 'react-simple-keyboard/build/css/index.css';
2424
import './FullKeyboard.css';
2525

2626
interface Props {
27-
setSelectedActions: (action: Action[]) => void;
27+
setSelectedActions: (action: KeybindInfoType[]) => void;
2828
setActiveModifiers: (modifiers: string[]) => void;
2929
activeModifiers: string[];
3030
selectedKey: string;
@@ -51,19 +51,30 @@ export function FullKeyboard({
5151
}
5252
}
5353

54-
function getActionsForKey(key: string): Action[] {
54+
function getActionsForKey(key: string): KeybindInfoType[] {
5555
// Find all action identifiers matching the given key and current modifiers
56+
// If no modifiers are active, it will match all keybinds with the given key
5657
const currentKeybinds = keybinds.filter(
5758
(keybind) =>
5859
keybind.key &&
59-
arraysEqual(activeModifiers, keybind.modifiers) &&
60+
(activeModifiers.length > 0
61+
? arraysEqual(activeModifiers, keybind.modifiers)
62+
: true) &&
6063
equals(keybind.key, key)
6164
);
6265

6366
// Find the matching actions for the current keybind
64-
const currentActions = currentKeybinds.map((keybind) =>
65-
actions.find((actions) => actions.identifier === keybind.action)
66-
);
67+
const currentActions = currentKeybinds.map((keybind) => {
68+
const action = actions.find((actions) => actions.identifier === keybind.action);
69+
if (!action) {
70+
return undefined; // If no action is found, return undefined
71+
}
72+
// Combine the keybind and action data into one object
73+
return {
74+
...keybind,
75+
...action
76+
};
77+
});
6778
// Remove the undefined items that were created from false matches
6879
return currentActions.filter((action) => action !== undefined);
6980
}
@@ -129,48 +140,50 @@ export function FullKeyboard({
129140
}
130141

131142
return (
132-
<Flex className={'simple-keyboard-background'} my={'lg'}>
133-
<Keyboard
134-
baseClass={'simple-keyboard-main'}
135-
layoutName={'default'}
136-
buttonTheme={buttonHighlights()}
137-
layout={{ default: KeyboardLayout }}
138-
display={KeyboardDisplayNames}
139-
onKeyPress={onKeyPress}
140-
{...commonKeyboardOptions}
141-
/>
142-
<Stack justify={'space-between'}>
143-
<Keyboard
144-
baseClass={'simple-keyboard-control'}
145-
buttonTheme={buttonHighlights()}
146-
layout={{ default: ControlPadLayout }}
147-
onKeyPress={onKeyPress}
148-
{...commonKeyboardOptions}
149-
/>
150-
<Keyboard
151-
baseClass={'simple-keyboard-arrows'}
152-
buttonTheme={buttonHighlights()}
153-
layout={{ default: ArrowsLayout }}
154-
onKeyPress={onKeyPress}
155-
{...commonKeyboardOptions}
156-
/>
157-
</Stack>
158-
<Flex align={'flex-end'}>
159-
<Keyboard
160-
baseClass={'simple-keyboard-numpad'}
161-
buttonTheme={buttonHighlights()}
162-
onKeyPress={onKeyPress}
163-
layout={{ default: NumpadLayout }}
164-
{...commonKeyboardOptions}
165-
/>
143+
<ScrollArea>
144+
<Flex className={'simple-keyboard-background'} my={'xs'}>
166145
<Keyboard
167-
baseClass={'simple-keyboard-numpadEnd'}
146+
baseClass={'simple-keyboard-main'}
147+
layoutName={'default'}
168148
buttonTheme={buttonHighlights()}
169-
layout={{ default: NumpadEndLayout }}
149+
layout={{ default: KeyboardLayout }}
150+
display={KeyboardDisplayNames}
170151
onKeyPress={onKeyPress}
171152
{...commonKeyboardOptions}
172153
/>
154+
<Stack justify={'space-between'}>
155+
<Keyboard
156+
baseClass={'simple-keyboard-control'}
157+
buttonTheme={buttonHighlights()}
158+
layout={{ default: ControlPadLayout }}
159+
onKeyPress={onKeyPress}
160+
{...commonKeyboardOptions}
161+
/>
162+
<Keyboard
163+
baseClass={'simple-keyboard-arrows'}
164+
buttonTheme={buttonHighlights()}
165+
layout={{ default: ArrowsLayout }}
166+
onKeyPress={onKeyPress}
167+
{...commonKeyboardOptions}
168+
/>
169+
</Stack>
170+
<Flex align={'flex-end'}>
171+
<Keyboard
172+
baseClass={'simple-keyboard-numpad'}
173+
buttonTheme={buttonHighlights()}
174+
onKeyPress={onKeyPress}
175+
layout={{ default: NumpadLayout }}
176+
{...commonKeyboardOptions}
177+
/>
178+
<Keyboard
179+
baseClass={'simple-keyboard-numpadEnd'}
180+
buttonTheme={buttonHighlights()}
181+
layout={{ default: NumpadEndLayout }}
182+
onKeyPress={onKeyPress}
183+
{...commonKeyboardOptions}
184+
/>
185+
</Flex>
173186
</Flex>
174-
</Flex>
187+
</ScrollArea>
175188
);
176189
}

src/panels/KeybindsPanel/KeybindInfo.tsx

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,55 @@ import { useTranslation } from 'react-i18next';
22
import { Badge, Code, Group, Paper, Table, Text } from '@mantine/core';
33

44
import { CopyToClipboardButton } from '@/components/CopyToClipboardButton/CopyToClipboardButton';
5-
import { Action } from '@/types/types';
5+
import styles from '@/theme/global.module.css';
6+
import { KeybindInfoType } from '@/types/types';
7+
8+
import { KeybindButtons } from './KeybindButtons';
69

710
interface Props {
8-
action: Action;
11+
action: KeybindInfoType;
912
}
1013

1114
export function KeybindInfo({ action }: Props) {
1215
const { t } = useTranslation('panel-keybinds', { keyPrefix: 'keybind-info' });
1316

1417
return (
1518
<Paper key={action.identifier} p={'sm'}>
16-
<Text ml={'xs'} mb={2} fw={'bold'}>
17-
{action.name}
18-
</Text>
19+
<Group justify={'space-between'} align={'center'} mb={'xs'} ml={'xs'}>
20+
<Text mb={2} fw={'bold'} className={styles.selectable}>
21+
{action.name}
22+
</Text>
23+
<KeybindButtons selectedKey={action.key} modifiers={action.modifiers} />
24+
</Group>
1925
<Table
2026
data={{
2127
body: [
22-
[`${t('info')}:`, action.documentation],
2328
[
24-
`${t('is-local.title')}:`,
29+
<Text size={'sm'}>{t('info')}:</Text>,
30+
// Using a custom, large, width for the longest text to make the
31+
// table look better, and multiple stacked tables align better
32+
<Text size={'sm'} className={styles.selectable} miw={'300px'}>
33+
{action.documentation}
34+
</Text>
35+
],
36+
[
37+
<Text size={'sm'}>{t('is-local.title')}:</Text>,
2538
action.isLocal ? (
2639
<Badge variant={'filled'}>{t('is-local.yes')}</Badge>
2740
) : (
2841
<Badge variant={'outline'}>{t('is-local.no')}</Badge>
2942
)
3043
],
31-
[`${t('gui-path')}:`, <Code>{action.guiPath}</Code>],
3244
[
33-
`${t('identifier')}:`,
45+
<Text size={'sm'}>{t('gui-path')}:</Text>,
46+
<Code className={styles.selectable}>{action.guiPath}</Code>
47+
],
48+
[
49+
<Text size={'sm'}>{t('identifier')}:</Text>,
3450
<Group gap={'xs'} wrap={'nowrap'}>
35-
<Code style={{ wordBreak: 'break-word' }}>{action.identifier}</Code>
51+
<Code className={styles.selectable} style={{ wordBreak: 'break-word' }}>
52+
{action.identifier}
53+
</Code>
3654
<CopyToClipboardButton value={action.identifier} />
3755
</Group>
3856
]

src/panels/KeybindsPanel/KeybindsPanel.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,21 @@ export function KeybindsPanel() {
1313
const { height: windowHeight } = useWindowSize();
1414
const { ref, height: tabsHeight } = useElementSize();
1515

16+
// The size of the tabs list is not completely correct for some reason (likely the
17+
// padding in the panel), so adjust the height so there is no overflow.
18+
const panelHeight = windowHeight - tabsHeight - 20;
19+
1620
return (
1721
<Tabs defaultValue={'keyboardLayout'}>
1822
<Tabs.List ref={ref}>
1923
<Tabs.Tab value={'keyboardLayout'}>{t('keyboard-view-tab-title')}</Tabs.Tab>
2024
<Tabs.Tab value={'listLayout'}>{t('list-view-tab-title')}</Tabs.Tab>
2125
</Tabs.List>
2226

23-
<Tabs.Panel value={'keyboardLayout'}>
27+
<Tabs.Panel value={'keyboardLayout'} h={panelHeight}>
2428
<KeyboardLayout />
2529
</Tabs.Panel>
26-
<Tabs.Panel value={'listLayout'} h={windowHeight - tabsHeight}>
30+
<Tabs.Panel value={'listLayout'} h={panelHeight}>
2731
<ListLayout />
2832
</Tabs.Panel>
2933
</Tabs>
Lines changed: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,69 @@
11
import { useState } from 'react';
22
import { useTranslation } from 'react-i18next';
3-
import { Container, Divider, Grid, Group, Stack, Text, Title } from '@mantine/core';
3+
import { Container, Divider, Group, Stack, Text, Title } from '@mantine/core';
44

5-
import { Action } from '@/types/types';
5+
import { Layout } from '@/components/Layout/Layout';
6+
import { KeybindInfoType } from '@/types/types';
67

78
import { FullKeyboard } from './FullKeyboard/FullKeyboard';
89
import { KeybindButtons } from './KeybindButtons';
910
import { KeybindInfo } from './KeybindInfo';
1011

1112
export function KeyboardLayout() {
12-
const [selectedActions, setSelectedActions] = useState<Action[]>([]);
13+
const [selectedActions, setSelectedActions] = useState<KeybindInfoType[]>([]);
1314
const [activeModifiers, setActiveModifiers] = useState<string[]>([]);
1415
const [selectedKey, setSelectedKey] = useState<string>('');
1516

1617
const { t } = useTranslation('panel-keybinds', { keyPrefix: 'keyboard-layout' });
1718

1819
const hasSelectedKeys = selectedKey !== '' || activeModifiers.length > 0;
1920
return (
20-
<Container maw={'none'}>
21-
<FullKeyboard
22-
setSelectedActions={setSelectedActions}
23-
setActiveModifiers={setActiveModifiers}
24-
setSelectedKey={setSelectedKey}
25-
selectedKey={selectedKey}
26-
activeModifiers={activeModifiers}
27-
/>
28-
<Group align={'top'}>
29-
<Stack>
30-
<Title order={2}>{t('selected-keybind-title')}:</Title>
31-
<KeybindButtons selectedKey={selectedKey} modifiers={activeModifiers} />
32-
</Stack>
33-
<Divider orientation={'vertical'} mx={'xs'} />
34-
<Stack flex={1}>
35-
<Title order={2}>{t('mapped-actions-title')}:</Title>
36-
{selectedActions.length > 0 ? (
37-
<Grid mx={'xs'}>
38-
{selectedActions.map((selectedAction) => (
39-
<KeybindInfo key={selectedAction.identifier} action={selectedAction} />
40-
))}
41-
</Grid>
42-
) : (
43-
<Text>
44-
{hasSelectedKeys ? t('no-mapped-action-text') : t('no-key-selected-text')}
45-
</Text>
46-
)}
47-
</Stack>
48-
</Group>
21+
<Container maw={'none'} h={'100%'}>
22+
<Layout>
23+
<Layout.FixedSection>
24+
<FullKeyboard
25+
setSelectedActions={setSelectedActions}
26+
setActiveModifiers={setActiveModifiers}
27+
setSelectedKey={setSelectedKey}
28+
selectedKey={selectedKey}
29+
activeModifiers={activeModifiers}
30+
/>
31+
</Layout.FixedSection>
32+
33+
<Layout.GrowingSection>
34+
<Group align={'top'} mt={'xs'}>
35+
<Stack>
36+
<Title order={2}>{t('selected-keybind-title')}:</Title>
37+
<KeybindButtons selectedKey={selectedKey} modifiers={activeModifiers} />
38+
</Stack>
39+
<Divider orientation={'vertical'} mx={'xs'} />
40+
<Stack flex={1} pr={'xs'}>
41+
<Group justify={'space-between'} align={'center'}>
42+
<Title order={2}>{t('mapped-actions-title')} :</Title>
43+
<Text c={'dimmed'} size={'lg'}>
44+
({selectedActions.length})
45+
</Text>
46+
</Group>
47+
{selectedActions.length > 0 ? (
48+
<Stack>
49+
{selectedActions.map((selectedAction) => (
50+
<KeybindInfo
51+
key={selectedAction.identifier}
52+
action={selectedAction}
53+
/>
54+
))}
55+
</Stack>
56+
) : (
57+
<Text>
58+
{hasSelectedKeys
59+
? t('no-mapped-action-text')
60+
: t('no-key-selected-text')}
61+
</Text>
62+
)}
63+
</Stack>
64+
</Group>
65+
</Layout.GrowingSection>
66+
</Layout>
4967
</Container>
5068
);
5169
}

src/panels/KeybindsPanel/ListLayout.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import { useSearchKeySettings } from '@/components/FilterList/SearchSettingsMenu
77
import { generateMatcherFunctionByKeys } from '@/components/FilterList/util';
88
import { Layout } from '@/components/Layout/Layout';
99
import { useAppSelector } from '@/redux/hooks';
10-
import { Action, KeybindInfoType, KeybindModifiers } from '@/types/types';
10+
import { KeybindInfoType, KeybindModifiers } from '@/types/types';
1111

1212
import { KeybindInfo } from './KeybindInfo';
1313
import { KeybindListEntry } from './ListEntry';
1414

1515
export function ListLayout() {
16-
const [selectedAction, setSelectedAction] = useState<Action | null>(null);
16+
const [selectedAction, setSelectedAction] = useState<KeybindInfoType | null>(null);
1717
const [modifiersFilter, setModifiersFilter] = useState<KeybindModifiers>([]);
1818
const keybinds = useAppSelector((state) => state.actions.keybinds);
1919
const actions = useAppSelector((state) => state.actions.actions);
@@ -47,7 +47,7 @@ export function ListLayout() {
4747
.filter((info) => !!info) // Filter any undefined objects
4848
.sort((a, b) => (a.key > b.key ? 1 : -1));
4949

50-
function onClick(action: Action): void {
50+
function onClick(action: KeybindInfoType): void {
5151
if (action.identifier === selectedAction?.identifier) {
5252
setSelectedAction(null);
5353
} else {
@@ -57,7 +57,7 @@ export function ListLayout() {
5757

5858
return (
5959
<Group align={'top'} px={'xs'} h={'100%'}>
60-
<Layout pt={'xs'} flex={2}>
60+
<Layout pt={'xs'} flex={1.5}>
6161
<FilterList>
6262
<Layout.FixedSection>
6363
<Group gap={'xs'}>

0 commit comments

Comments
 (0)