Skip to content

Commit 62f5a83

Browse files
Migrate out of react-hotkeys to @mantine/hooks (#2968)
* Migrate playground hulk hotkey bindings to alt+shift+h * Migrate SubstVisualizer hotkey bindings * Migrate Data Viz hotkey bindings * Migrate CSE machine hotkey bindings * Remove react-hotkeys from package.json * Fix PR comments * Fix snapshots * Use Blueprint Card component instead of div * Move documentation from props to component * Fix format and snapshots --------- Co-authored-by: Richard Dominick <34370238+RichDom2185@users.noreply.github.com>
1 parent b8be875 commit 62f5a83

File tree

14 files changed

+602
-732
lines changed

14 files changed

+602
-732
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@
7373
"react-drag-drop-files": "^2.3.10",
7474
"react-draggable": "^4.4.5",
7575
"react-dropzone": "^14.2.3",
76-
"react-hotkeys": "^2.0.0",
7776
"react-i18next": "^14.1.0",
7877
"react-konva": "^18.2.10",
7978
"react-latex-next": "^3.0.0",

src/commons/assessmentWorkspace/__tests__/__snapshots__/AssessmentWorkspace.tsx.snap

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,6 @@ exports[`AssessmentWorkspace AssessmentWorkspace page with ContestVoting questio
220220
>
221221
<div
222222
class="Editor bp5-card bp5-elevation-0"
223-
tabindex="-1"
224223
>
225224
<div
226225
class="row editor-react-ace"
@@ -1236,7 +1235,6 @@ exports[`AssessmentWorkspace AssessmentWorkspace page with ContestVoting questio
12361235
>
12371236
<div
12381237
class="repl-input-parent row bp5-card bp5-elevation-0"
1239-
tabindex="-1"
12401238
>
12411239
<div
12421240
class=" ace_editor ace_hidpi ace-source ace_dark repl-react-ace react-ace"
@@ -2021,7 +2019,6 @@ exports[`AssessmentWorkspace AssessmentWorkspace page with MCQ question renders
20212019
>
20222020
<div
20232021
class="repl-input-parent row bp5-card bp5-elevation-0"
2024-
tabindex="-1"
20252022
>
20262023
<div
20272024
class=" ace_editor ace_hidpi ace-source ace_dark repl-react-ace react-ace"
@@ -2354,7 +2351,6 @@ exports[`AssessmentWorkspace AssessmentWorkspace page with overdue assessment re
23542351
>
23552352
<div
23562353
class="Editor bp5-card bp5-elevation-0"
2357-
tabindex="-1"
23582354
>
23592355
<div
23602356
class="row editor-react-ace"
@@ -2841,7 +2837,6 @@ exports[`AssessmentWorkspace AssessmentWorkspace page with overdue assessment re
28412837
>
28422838
<div
28432839
class="repl-input-parent row bp5-card bp5-elevation-0"
2844-
tabindex="-1"
28452840
>
28462841
<div
28472842
class=" ace_editor ace_hidpi ace-source ace_dark repl-react-ace react-ace"
@@ -3204,7 +3199,6 @@ exports[`AssessmentWorkspace AssessmentWorkspace page with programming question
32043199
>
32053200
<div
32063201
class="Editor bp5-card bp5-elevation-0"
3207-
tabindex="-1"
32083202
>
32093203
<div
32103204
class="row editor-react-ace"
@@ -3691,7 +3685,6 @@ exports[`AssessmentWorkspace AssessmentWorkspace page with programming question
36913685
>
36923686
<div
36933687
class="repl-input-parent row bp5-card bp5-elevation-0"
3694-
tabindex="-1"
36953688
>
36963689
<div
36973690
class=" ace_editor ace_hidpi ace-source ace_dark repl-react-ace react-ace"
@@ -4054,7 +4047,6 @@ exports[`AssessmentWorkspace AssessmentWorkspace renders Grading tab correctly i
40544047
>
40554048
<div
40564049
class="Editor bp5-card bp5-elevation-0"
4057-
tabindex="-1"
40584050
>
40594051
<div
40604052
class="row editor-react-ace"
@@ -4759,7 +4751,6 @@ exports[`AssessmentWorkspace AssessmentWorkspace renders Grading tab correctly i
47594751
>
47604752
<div
47614753
class="repl-input-parent row bp5-card bp5-elevation-0"
4762-
tabindex="-1"
47634754
>
47644755
<div
47654756
class=" ace_editor ace_hidpi ace-source ace_dark repl-react-ace react-ace"

src/commons/editor/Editor.tsx

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@ import 'ace-builds/src-noconflict/ext-searchbox';
44
import 'ace-builds/src-noconflict/ext-settings_menu';
55
import 'js-slang/dist/editors/ace/theme/source';
66

7-
import { Classes } from '@blueprintjs/core';
7+
import { Card } from '@blueprintjs/core';
88
import * as AceBuilds from 'ace-builds';
99
import { Ace, require as acequire, createEditSession } from 'ace-builds';
10-
import classNames from 'classnames';
1110
import { Chapter, Variant } from 'js-slang/dist/types';
1211
import React from 'react';
1312
import AceEditor, { IAceEditorProps, IEditorProps } from 'react-ace';
1413
import { IAceEditor } from 'react-ace/lib/types';
15-
import { HotKeys } from 'react-hotkeys';
1614
import { EditorBinding } from '../WorkspaceSettingsContext';
1715
import { getModeString, selectMode } from '../utils/AceHelper';
1816
import { objectEntries } from '../utils/TypeHelper';
@@ -327,11 +325,6 @@ const moveCursor = (editor: AceEditor['editor'], position: Position) => {
327325
editor.renderer.scrollCursorIntoView(position, 0.5);
328326
};
329327

330-
/* Override handler, so does not trigger when focus is in editor */
331-
const handlers = {
332-
goGreen: () => {}
333-
};
334-
335328
const EditorBase = React.memo((props: EditorProps & LocalStateProps) => {
336329
const reactAceRef: React.MutableRefObject<AceEditor | null> = React.useRef(null);
337330
const [filePath, setFilePath] = React.useState<string | undefined>(undefined);
@@ -652,14 +645,11 @@ const EditorBase = React.memo((props: EditorProps & LocalStateProps) => {
652645
}, []);
653646

654647
return (
655-
<HotKeys
656-
className={classNames('Editor', Classes.CARD, Classes.ELEVATION_0)}
657-
handlers={handlers}
658-
>
648+
<Card className="Editor">
659649
<div className="row editor-react-ace" data-testid="Editor">
660650
<AceEditor {...aceEditorProps} ref={reactAceRef} />
661651
</div>
662-
</HotKeys>
652+
</Card>
663653
);
664654
});
665655

src/commons/hotkeys/HotKeys.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { getHotkeyHandler, HotkeyItem } from '@mantine/hooks';
2+
import React, { PropsWithChildren } from 'react';
3+
4+
type HotKeysProps = {
5+
bindings: HotkeyItem[];
6+
};
7+
8+
/**
9+
* This HOC was created to facilitate the migration out of react-hotkeys in favor of @mantine/hooks useHotkeys,
10+
* as SideContentCseMachine.tsx and SideContentDataVisualizer still use class-based React.
11+
*
12+
* NOTE:
13+
* - New hotkey implementations should NOT use this component. Use functional React and the useHotkeys hook
14+
* from @mantine/hooks directly.
15+
*
16+
* TODO:
17+
* - Eventually migrate out of class-based React in the aforementioned components and use useHotkeys directly.
18+
*/
19+
const HotKeys: React.FC<
20+
PropsWithChildren<
21+
HotKeysProps & {
22+
style?: React.CSSProperties;
23+
}
24+
>
25+
> = ({ bindings, children, style }) => {
26+
const handler = getHotkeyHandler(bindings);
27+
28+
return (
29+
<div
30+
tabIndex={-1} // tab index necessary to fire keydown events on div element
31+
onKeyDown={handler}
32+
style={style}
33+
>
34+
{children}
35+
</div>
36+
);
37+
};
38+
39+
export default HotKeys;

src/commons/repl/Repl.tsx

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { Card, Classes, Pre } from '@blueprintjs/core';
1+
import { Card, Pre } from '@blueprintjs/core';
22
import { Ace } from 'ace-builds';
33
import classNames from 'classnames';
44
import { parseError } from 'js-slang';
55
import { Chapter, Variant } from 'js-slang/dist/types';
66
import React from 'react';
7-
import { HotKeys } from 'react-hotkeys';
87

98
import { InterpreterOutput } from '../application/ApplicationTypes';
109
import { ExternalLibraryName } from '../application/types/ExternalTypes';
@@ -52,12 +51,9 @@ const Repl: React.FC<ReplProps> = props => {
5251
<div className="repl-output-parent">
5352
{cards}
5453
{!props.inputHidden && (
55-
<HotKeys
56-
className={classNames('repl-input-parent', 'row', Classes.CARD, Classes.ELEVATION_0)}
57-
handlers={handlers}
58-
>
54+
<Card className={classNames('repl-input-parent', 'row')}>
5955
<ReplInput {...props} />
60-
</HotKeys>
56+
</Card>
6157
)}
6258
</div>
6359
</div>
@@ -133,9 +129,4 @@ export const Output: React.FC<OutputProps> = props => {
133129
}
134130
};
135131

136-
/* Override handler, so does not trigger when focus is in editor */
137-
const handlers = {
138-
goGreen: () => {}
139-
};
140-
141132
export default Repl;

src/commons/repl/__tests__/__snapshots__/Repl.tsx.snap

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,10 @@ exports[`Repl renders correctly 1`] = `
135135
}
136136
usingSubst={false}
137137
/>
138-
<HotKeysEnabled
139-
className="repl-input-parent row bp5-card bp5-elevation-0"
140-
handlers={
141-
Object {
142-
"goGreen": [Function],
143-
}
144-
}
138+
<Blueprint5.Card
139+
className="repl-input-parent row"
140+
elevation={0}
141+
interactive={false}
145142
>
146143
<ReplInput
147144
externalLibrary="NONE"
@@ -201,7 +198,7 @@ exports[`Repl renders correctly 1`] = `
201198
sourceChapter={1}
202199
sourceVariant="default"
203200
/>
204-
</HotKeysEnabled>
201+
</Blueprint5.Card>
205202
</div>
206203
</div>
207204
`;

src/commons/sideContent/__tests__/__snapshots__/SideContentCseMachine.tsx.snap

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,14 @@
22

33
exports[`CSE Machine component renders correctly 1`] = `
44
<div
5-
onBlur={[Function]}
6-
onFocus={[Function]}
75
onKeyDown={[Function]}
8-
onKeyPress={[Function]}
9-
onKeyUp={[Function]}
106
style={
117
Object {
128
"maxHeight": "100%",
139
"overflow": "auto",
1410
}
1511
}
16-
tabIndex="-1"
12+
tabIndex={-1}
1713
>
1814
<div
1915
className="sa-substituter bp5-dark"

src/commons/sideContent/content/SideContentCseMachine.tsx

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ import {
99
Tooltip
1010
} from '@blueprintjs/core';
1111
import { IconNames } from '@blueprintjs/icons';
12+
import { HotkeyItem } from '@mantine/hooks';
1213
import classNames from 'classnames';
1314
import { Chapter } from 'js-slang/dist/types';
1415
import { debounce } from 'lodash';
1516
import React from 'react';
16-
import { HotKeys } from 'react-hotkeys';
1717
import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux';
1818
import { bindActionCreators } from 'redux';
19+
import HotKeys from 'src/commons/hotkeys/HotKeys';
1920
import { Output } from 'src/commons/repl/Repl';
2021
import type { PlaygroundWorkspaceState } from 'src/commons/workspace/WorkspaceTypes';
2122
import CseMachine from 'src/features/cseMachine/CseMachine';
@@ -73,13 +74,6 @@ type DispatchProps = {
7374
handleAlertSideContent: () => void;
7475
};
7576

76-
const cseMachineKeyMap = {
77-
FIRST_STEP: 'a',
78-
NEXT_STEP: 'f',
79-
PREVIOUS_STEP: 'b',
80-
LAST_STEP: 'e'
81-
};
82-
8377
class SideContentCseMachineBase extends React.Component<CseMachineProps, State> {
8478
constructor(props: CseMachineProps) {
8579
super(props);
@@ -200,24 +194,23 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State>
200194
}
201195

202196
public render() {
203-
const cseMachineHandlers = this.state.visualization
204-
? {
205-
FIRST_STEP: this.stepFirst,
206-
NEXT_STEP: this.stepNext,
207-
PREVIOUS_STEP: this.stepPrevious,
208-
LAST_STEP: this.stepLast(this.props.stepsTotal)
209-
}
210-
: {
211-
FIRST_STEP: () => {},
212-
NEXT_STEP: () => {},
213-
PREVIOUS_STEP: () => {},
214-
LAST_STEP: () => {}
215-
};
197+
const hotkeyBindings: HotkeyItem[] = this.state.visualization
198+
? [
199+
['a', this.stepFirst],
200+
['f', this.stepNext],
201+
['b', this.stepPrevious],
202+
['e', this.stepLast(this.props.stepsTotal)]
203+
]
204+
: [
205+
['a', () => {}],
206+
['f', () => {}],
207+
['b', () => {}],
208+
['e', () => {}]
209+
];
216210

217211
return (
218212
<HotKeys
219-
keyMap={cseMachineKeyMap}
220-
handlers={cseMachineHandlers}
213+
bindings={hotkeyBindings}
221214
style={{
222215
maxHeight: '100%',
223216
overflow: this.state.visualization ? 'hidden' : 'auto'

0 commit comments

Comments
 (0)