From b70fa5f356d81fc362a7b3206f28b42f55282ec5 Mon Sep 17 00:00:00 2001 From: En Rong <53928333+chownces@users.noreply.github.com> Date: Thu, 2 May 2024 12:57:00 +0800 Subject: [PATCH 01/10] Migrate playground hulk hotkey bindings to alt+shift+h --- .../AssessmentWorkspace.tsx.snap | 9 ------ src/commons/editor/Editor.tsx | 13 ++------- src/commons/repl/Repl.tsx | 11 ++------ .../__tests__/__snapshots__/Repl.tsx.snap | 9 ++---- .../sourceRecorder/SourceRecorderEditor.tsx | 13 ++------- src/pages/playground/Playground.tsx | 28 ++++++++----------- .../__snapshots__/Playground.tsx.snap | 6 ---- 7 files changed, 19 insertions(+), 70 deletions(-) diff --git a/src/commons/assessmentWorkspace/__tests__/__snapshots__/AssessmentWorkspace.tsx.snap b/src/commons/assessmentWorkspace/__tests__/__snapshots__/AssessmentWorkspace.tsx.snap index f24bedebae..dea696bdbb 100644 --- a/src/commons/assessmentWorkspace/__tests__/__snapshots__/AssessmentWorkspace.tsx.snap +++ b/src/commons/assessmentWorkspace/__tests__/__snapshots__/AssessmentWorkspace.tsx.snap @@ -220,7 +220,6 @@ exports[`AssessmentWorkspace AssessmentWorkspace page with ContestVoting questio >
{ editor.renderer.scrollCursorIntoView(position, 0.5); }; -/* Override handler, so does not trigger when focus is in editor */ -const handlers = { - goGreen: () => {} -}; - const EditorBase = React.memo((props: EditorProps & LocalStateProps) => { const reactAceRef: React.MutableRefObject = React.useRef(null); const [filePath, setFilePath] = React.useState(undefined); @@ -652,14 +646,11 @@ const EditorBase = React.memo((props: EditorProps & LocalStateProps) => { }, []); return ( - +
- +
); }); diff --git a/src/commons/repl/Repl.tsx b/src/commons/repl/Repl.tsx index 4cc233ab6e..bdd1d230a8 100644 --- a/src/commons/repl/Repl.tsx +++ b/src/commons/repl/Repl.tsx @@ -4,7 +4,6 @@ import classNames from 'classnames'; import { parseError } from 'js-slang'; import { Chapter, Variant } from 'js-slang/dist/types'; import React from 'react'; -import { HotKeys } from 'react-hotkeys'; import { InterpreterOutput } from '../application/ApplicationTypes'; import { ExternalLibraryName } from '../application/types/ExternalTypes'; @@ -52,12 +51,11 @@ const Repl: React.FC = props => {
{cards} {!props.inputHidden && ( - - +
)}
@@ -133,9 +131,4 @@ export const Output: React.FC = props => { } }; -/* Override handler, so does not trigger when focus is in editor */ -const handlers = { - goGreen: () => {} -}; - export default Repl; diff --git a/src/commons/repl/__tests__/__snapshots__/Repl.tsx.snap b/src/commons/repl/__tests__/__snapshots__/Repl.tsx.snap index ffd0f69976..9a540b5964 100644 --- a/src/commons/repl/__tests__/__snapshots__/Repl.tsx.snap +++ b/src/commons/repl/__tests__/__snapshots__/Repl.tsx.snap @@ -135,13 +135,8 @@ exports[`Repl renders correctly 1`] = ` } usingSubst={false} /> - - +
`; diff --git a/src/commons/sourceRecorder/SourceRecorderEditor.tsx b/src/commons/sourceRecorder/SourceRecorderEditor.tsx index ea933a0c48..cb403d1441 100644 --- a/src/commons/sourceRecorder/SourceRecorderEditor.tsx +++ b/src/commons/sourceRecorder/SourceRecorderEditor.tsx @@ -8,7 +8,6 @@ import classNames from 'classnames'; import { isEqual } from 'lodash'; import React from 'react'; import AceEditor, { IAceEditorProps } from 'react-ace'; -import { HotKeys } from 'react-hotkeys'; import { CodeDelta, @@ -208,10 +207,7 @@ class SourcecastEditor extends React.PureComponent +
- +
); } @@ -326,9 +322,4 @@ class SourcecastEditor extends React.PureComponent {} -}; - export default SourcecastEditor; diff --git a/src/pages/playground/Playground.tsx b/src/pages/playground/Playground.tsx index 4c7d48ac7c..2d676bf2dc 100644 --- a/src/pages/playground/Playground.tsx +++ b/src/pages/playground/Playground.tsx @@ -1,5 +1,6 @@ import { Classes } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; +import { HotkeyItem, useHotkeys } from '@mantine/hooks'; import { Ace, Range } from 'ace-builds'; import { FSModule } from 'browserfs/dist/node/core/FS'; import classNames from 'classnames'; @@ -7,7 +8,6 @@ import { Chapter, Variant } from 'js-slang/dist/types'; import { isEqual } from 'lodash'; import { decompressFromEncodedURIComponent } from 'lz-string'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { HotKeys } from 'react-hotkeys'; import { useDispatch, useStore } from 'react-redux'; import { useLocation, useNavigate } from 'react-router'; import { AnyAction, Dispatch } from 'redux'; @@ -143,8 +143,6 @@ export type PlaygroundProps = { handleCloseEditor?: () => void; }; -const keyMap = { goGreen: 'h u l k' }; - export async function handleHash( hash: string, handlers: { @@ -306,7 +304,6 @@ const Playground: React.FC = props => { } const [lastEdit, setLastEdit] = useState(new Date()); - const [isGreen, setIsGreen] = useState(false); const { selectedTab, setSelectedTab } = useSideContent( workspaceLocation, shouldAddDevice ? SideContentType.remoteExecution : SideContentType.introduction @@ -320,6 +317,14 @@ const Playground: React.FC = props => { }) ); + // Playground hotkeys + const [isGreen, setIsGreen] = useState(false); + const playgroundHotkeyBindings: HotkeyItem[] = useMemo( + () => [['alt+shift+h', () => setIsGreen(!isGreen)]], + [isGreen, setIsGreen] + ); + useHotkeys(playgroundHotkeyBindings); + const remoteExecutionTab: SideContentTab = useMemo( () => makeRemoteExecutionTabFrom(deviceSecret, setDeviceSecret), [deviceSecret] @@ -395,13 +400,6 @@ const Playground: React.FC = props => { } }, [isMobileBreakpoint, selectedTab, setSelectedTab]); - const handlers = useMemo( - () => ({ - goGreen: () => setIsGreen(!isGreen) - }), - [isGreen] - ); - const onEditorValueChange = React.useCallback( (editorTabIndex: number, newEditorValue: string) => { setLastEdit(new Date()); @@ -1051,13 +1049,9 @@ const Playground: React.FC = props => {
) : ( - +
- +
); }; diff --git a/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap b/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap index af338f9c12..7b359e341f 100644 --- a/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap +++ b/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap @@ -4,7 +4,6 @@ exports[`Playground tests Playground renders correctly 1`] = `
Date: Thu, 2 May 2024 15:10:42 +0800 Subject: [PATCH 02/10] Migrate SubstVisualizer hotkey bindings --- .../content/SideContentSubstVisualizer.tsx | 517 ++++++++---------- .../__snapshots__/Playground.tsx.snap | 331 ++++++----- 2 files changed, 378 insertions(+), 470 deletions(-) diff --git a/src/commons/sideContent/content/SideContentSubstVisualizer.tsx b/src/commons/sideContent/content/SideContentSubstVisualizer.tsx index 7ef04bedf5..71378f488e 100644 --- a/src/commons/sideContent/content/SideContentSubstVisualizer.tsx +++ b/src/commons/sideContent/content/SideContentSubstVisualizer.tsx @@ -1,17 +1,16 @@ -/* eslint-disable simple-import-sort/imports */ -import { Button, ButtonGroup, Card, Classes, Divider, Pre, Slider } from '@blueprintjs/core'; -import React from 'react'; -import AceEditor from 'react-ace'; -import { HotKeys } from 'react-hotkeys'; -import { MapDispatchToProps, connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; +import 'js-slang/dist/editors/ace/theme/source'; +import { Button, ButtonGroup, Card, Classes, Divider, Pre, Slider } from '@blueprintjs/core'; +import { getHotkeyHandler, HotkeyItem } from '@mantine/hooks'; import classNames from 'classnames'; import { HighlightRulesSelector, ModeSelector } from 'js-slang/dist/editors/ace/modes/source'; -import 'js-slang/dist/editors/ace/theme/source'; import { IStepperPropContents } from 'js-slang/dist/stepper/stepper'; -import { SideContentLocation, SideContentType } from '../SideContentTypes'; +import React, { useCallback, useEffect, useState } from 'react'; +import AceEditor from 'react-ace'; +import { useDispatch } from 'react-redux'; + import { beginAlertSideContent } from '../SideContentActions'; +import { SideContentLocation, SideContentType } from '../SideContentTypes'; const SubstDefaultText = () => { return ( @@ -50,13 +49,6 @@ const SubstDefaultText = () => { ); }; -const substKeyMap = { - FIRST_STEP: 'a', - NEXT_STEP: 'f', - PREVIOUS_STEP: 'b', - LAST_STEP: 'e' -}; - const SubstCodeDisplay = (props: { content: string }) => { return ( @@ -65,314 +57,235 @@ const SubstCodeDisplay = (props: { content: string }) => { ); }; -type SubstVisualizerProps = OwnProps & DispatchProps; - -type OwnProps = { +type SubstVisualizerProps = { content: IStepperPropContents[]; workspaceLocation: SideContentLocation; }; -type State = { - value: number; -}; +const SideContentSubstVisualizer: React.FC = props => { + const [stepValue, setStepValue] = useState(1); + const lastStepValue = props.content.length; + const hasRunCode = lastStepValue !== 0; // 'content' property is initialised to '[]' by Playground component -type DispatchProps = { - alertSideContent: () => void; -}; - -class SideContentSubstVisualizerBase extends React.Component { - constructor(props: SubstVisualizerProps) { - super(props); - this.state = { - value: 1 - }; + const dispatch = useDispatch(); + const alertSideContent = useCallback( + () => dispatch(beginAlertSideContent(SideContentType.substVisualizer, props.workspaceLocation)), + [props.workspaceLocation, dispatch] + ); - // set source mode as 2 + // set source mode as 2 + useEffect(() => { HighlightRulesSelector(2); ModeSelector(2); - } + }, []); - componentDidUpdate(prevProps: OwnProps, prevState: State) { - if (prevProps.content !== this.props.content) { - this.setState((state: State) => { - return { value: 1 }; - }); - - if (this.props.content.length > 0) { - this.props.alertSideContent(); - } + // reset stepValue when content changes + useEffect(() => { + setStepValue(1); + if (props.content.length > 0) { + alertSideContent(); } - } - - public render() { - const lastStepValue = this.props.content.length; - // 'content' property is initialised to '[]' by Playground component - const hasRunCode = lastStepValue !== 0; - const substHandlers = hasRunCode - ? { - FIRST_STEP: this.stepFirst, - NEXT_STEP: this.stepNext, - PREVIOUS_STEP: this.stepPrevious, - LAST_STEP: this.stepLast(lastStepValue) - } - : { - FIRST_STEP: () => {}, - NEXT_STEP: () => {}, - PREVIOUS_STEP: () => {}, - LAST_STEP: () => {} - }; - - return ( - -
-
- -
- -
{' '} -
- {hasRunCode ? ( - - ) : ( - - )} - {hasRunCode ? ( - - ) : null} -
-
-
- ); - } - - private getDiffMarkers = (value: number) => { - const lastStepValue = this.props.content.length; - const contIndex = value <= lastStepValue ? value - 1 : 0; - const pathified = this.props.content[contIndex]; - const redexed = pathified.code; - const redex = pathified.redex.split('\n'); - - const diffMarkers = [] as any[]; - if (redex.length > 0) { - const mainprog = redexed.split('@redex'); - let text = mainprog[0]; - let front = text.split('\n'); - - let startR = front.length - 1; - let startC = front[startR].length; - - for (let i = 0; i < mainprog.length - 1; i++) { - const endR = startR + redex.length - 1; - const endC = - redex.length === 1 - ? startC + redex[redex.length - 1].length - : redex[redex.length - 1].length; - - diffMarkers.push({ - startRow: startR, - startCol: startC, - endRow: endR, - endCol: endC, - className: value % 2 === 0 ? 'beforeMarker' : 'afterMarker', - type: 'background' - }); - - text = text + redex + mainprog[i + 1]; - front = text.split('\n'); - startR = front.length - 1; - startC = front[startR].length; + }, [props.content, setStepValue, alertSideContent]); + + // Stepper function call helpers + const getPreviousFunctionCall = useCallback( + (value: number) => { + const contIndex = value <= lastStepValue ? value - 1 : 0; + const currentFunction = props.content[contIndex]?.function; + if (currentFunction === undefined) { + return null; } - } - return diffMarkers; - }; - - private getText(value: number) { - const lastStepValue = this.props.content.length; - const contIndex = value <= lastStepValue ? value - 1 : 0; - const pathified = this.props.content[contIndex]; - const redexed = pathified.code; - const redex = pathified.redex; - const split = pathified.code.split('@redex'); - if (split.length > 1) { - let text = split[0]; - for (let i = 1; i < split.length; i++) { - text = text + redex + split[i]; - } - return text; - } else { - return redexed; - } - } - - private sliderShift = (newValue: number) => { - this.setState((state: State) => { - return { value: newValue }; - }); - }; - - private stepFirst = () => { - // Move to the first step - this.sliderShift(1); - }; - - private stepLast = (lastStepValue: number) => () => { - // Move to the last step - this.sliderShift(lastStepValue); - }; - - private stepPrevious = () => { - if (this.state.value !== 1) { - this.sliderShift(this.state.value - 1); - } - }; - - private stepNext = () => { - const lastStepValue = this.props.content.length; - if (this.state.value !== lastStepValue) { - this.sliderShift(this.state.value + 1); - } - }; - - private stepPreviousFunctionCall = (value: number) => () => { - const previousFunctionCall = this.getPreviousFunctionCall(value); - if (previousFunctionCall !== null) { - this.sliderShift(previousFunctionCall); - } - }; - - private stepNextFunctionCall = (value: number) => () => { - const nextFunctionCall = this.getNextFunctionCall(value); - if (nextFunctionCall !== null) { - this.sliderShift(nextFunctionCall); - } - }; - - private hasPreviousFunctionCall = (value: number) => { - const lastStepValue = this.props.content.length; - const contIndex = value <= lastStepValue ? value - 1 : 0; - const currentFunction = this.props.content[contIndex].function; - if (currentFunction === undefined) { - return false; - } else { for (let i = contIndex - 1; i > -1; i--) { - const previousFunction = this.props.content[i].function; + const previousFunction = props.content[i].function; if (previousFunction !== undefined && currentFunction === previousFunction) { - return true; + return i + 1; } } - return false; - } - }; + return null; + }, + [lastStepValue, props.content] + ); - private hasNextFunctionCall = (value: number) => { - const lastStepValue = this.props.content.length; - const contIndex = value <= lastStepValue ? value - 1 : 0; - const currentFunction = this.props.content[contIndex].function; - if (currentFunction === undefined) { - return false; - } else { - for (let i = contIndex + 1; i < this.props.content.length; i++) { - const nextFunction = this.props.content[i].function; + const getNextFunctionCall = useCallback( + (value: number) => { + const contIndex = value <= lastStepValue ? value - 1 : 0; + const currentFunction = props.content[contIndex]?.function; + if (currentFunction === undefined) { + return null; + } + for (let i = contIndex + 1; i < props.content.length; i++) { + const nextFunction = props.content[i].function; if (nextFunction !== undefined && currentFunction === nextFunction) { - return true; + return i + 1; } } - return false; - } - }; - - private getPreviousFunctionCall = (value: number) => { - const lastStepValue = this.props.content.length; - const contIndex = value <= lastStepValue ? value - 1 : 0; - const currentFunction = this.props.content[contIndex].function; - if (currentFunction === undefined) { return null; - } - for (let i = contIndex - 1; i > -1; i--) { - const previousFunction = this.props.content[i].function; - if (previousFunction !== undefined && currentFunction === previousFunction) { - return i + 1; - } - } - return null; - }; + }, + [lastStepValue, props.content] + ); - private getNextFunctionCall = (value: number) => { - const lastStepValue = this.props.content.length; - const contIndex = value <= lastStepValue ? value - 1 : 0; - const currentFunction = this.props.content[contIndex].function; - if (currentFunction === undefined) { - return null; - } - for (let i = contIndex + 1; i < this.props.content.length; i++) { - const nextFunction = this.props.content[i].function; - if (nextFunction !== undefined && currentFunction === nextFunction) { - return i + 1; + // Stepper handlers + const hasPreviousFunctionCall = getPreviousFunctionCall(stepValue) !== null; + const hasNextFunctionCall = getNextFunctionCall(stepValue) !== null; + const stepPreviousFunctionCall = () => + setStepValue(getPreviousFunctionCall(stepValue) ?? stepValue); + const stepNextFunctionCall = () => setStepValue(getNextFunctionCall(stepValue) ?? stepValue); + const stepFirst = () => setStepValue(1); + const stepLast = () => setStepValue(lastStepValue); + const stepPrevious = () => setStepValue(Math.max(1, stepValue - 1)); + const stepNext = () => setStepValue(Math.min(props.content.length, stepValue + 1)); + + // Setup hotkey bindings + const hotkeyBindings: HotkeyItem[] = hasRunCode + ? [ + ['a', stepFirst], + ['f', stepNext], + ['b', stepPrevious], + ['e', stepLast] + ] + : [ + ['a', () => {}], + ['f', () => {}], + ['b', () => {}], + ['e', () => {}] + ]; + const hotkeyHandler = getHotkeyHandler(hotkeyBindings); + + // Rendering helpers + const getText = useCallback( + (value: number) => { + const contIndex = value <= lastStepValue ? value - 1 : 0; + const pathified = props.content[contIndex]; + const redexed = pathified.code; + const redex = pathified.redex; + const split = pathified.code.split('@redex'); + if (split.length > 1) { + let text = split[0]; + for (let i = 1; i < split.length; i++) { + text = text + redex + split[i]; + } + return text; + } else { + return redexed; } - } - return null; - }; -} + }, + [lastStepValue, props.content] + ); -const mapDispatchToProps: MapDispatchToProps = ( - dispatch, - { workspaceLocation } -) => - bindActionCreators( - { - alertSideContent: () => - beginAlertSideContent(SideContentType.substVisualizer, workspaceLocation) + const getDiffMarkers = useCallback( + (value: number) => { + const contIndex = value <= lastStepValue ? value - 1 : 0; + const pathified = props.content[contIndex]; + const redexed = pathified.code; + const redex = pathified.redex.split('\n'); + + const diffMarkers = [] as any[]; + if (redex.length > 0) { + const mainprog = redexed.split('@redex'); + let text = mainprog[0]; + let front = text.split('\n'); + + let startR = front.length - 1; + let startC = front[startR].length; + + for (let i = 0; i < mainprog.length - 1; i++) { + const endR = startR + redex.length - 1; + const endC = + redex.length === 1 + ? startC + redex[redex.length - 1].length + : redex[redex.length - 1].length; + + diffMarkers.push({ + startRow: startR, + startCol: startC, + endRow: endR, + endCol: endC, + className: value % 2 === 0 ? 'beforeMarker' : 'afterMarker', + type: 'background' + }); + + text = text + redex + mainprog[i + 1]; + front = text.split('\n'); + startR = front.length - 1; + startC = front[startR].length; + } + } + return diffMarkers; }, - dispatch + [lastStepValue, props.content] + ); + + return ( +
+ +
+ +
{' '} +
+ {hasRunCode ? ( + + ) : ( + + )} + {hasRunCode ? ( + + ) : null} +
); -export default connect(null, mapDispatchToProps)(SideContentSubstVisualizerBase); +}; + +export default SideContentSubstVisualizer; diff --git a/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap b/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap index 7b359e341f..a2ffc433e2 100644 --- a/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap +++ b/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap @@ -2445,190 +2445,185 @@ and also the class="side-content-text" >
-
+
+
+
+
+
+ + + 1 + + +
+
+
+ - - + - + -
-
- + + + + +
+
+ +
+
+
+ Welcome to the Stepper!
-
-
- Welcome to the Stepper! -
-
- On this tab, the REPL will be hidden from view, so do check that your code has no errors before running the stepper. You may use this tool by writing your program on the left, then dragging the slider above to see its evaluation. -
-
- On even-numbered steps, the part of the program that will be evaluated next is highlighted in yellow. On odd-numbered steps, the result of the evaluation is highlighted in green. You can change the maximum steps limit (500-5000, default 1000) in the control bar. -
-
-
- Some useful keyboard shortcuts: -
-
- a: Move to the first step -
- e: Move to the last step -
- f: Move to the next step -
- b: Move to the previous step -
-
- Note that these shortcuts are only active when the browser focus is on this tab (click on or above the explanation text). -
-
+
+ On this tab, the REPL will be hidden from view, so do check that your code has no errors before running the stepper. You may use this tool by writing your program on the left, then dragging the slider above to see its evaluation. +
+
+ On even-numbered steps, the part of the program that will be evaluated next is highlighted in yellow. On odd-numbered steps, the result of the evaluation is highlighted in green. You can change the maximum steps limit (500-5000, default 1000) in the control bar. +
+
+
+ Some useful keyboard shortcuts: +
+
+ a: Move to the first step +
+ e: Move to the last step +
+ f: Move to the next step +
+ b: Move to the previous step +
+
+ Note that these shortcuts are only active when the browser focus is on this tab (click on or above the explanation text).
From 3d8064711897955793396b162dadcf44f8a423f8 Mon Sep 17 00:00:00 2001 From: En Rong <53928333+chownces@users.noreply.github.com> Date: Thu, 2 May 2024 16:24:27 +0800 Subject: [PATCH 03/10] Migrate Data Viz hotkey bindings --- src/commons/hotkeys/HotKeys.tsx | 32 +++ .../content/SideContentDataVisualizer.tsx | 39 +-- .../__snapshots__/Playground.tsx.snap | 270 +++++++++--------- 3 files changed, 178 insertions(+), 163 deletions(-) create mode 100644 src/commons/hotkeys/HotKeys.tsx diff --git a/src/commons/hotkeys/HotKeys.tsx b/src/commons/hotkeys/HotKeys.tsx new file mode 100644 index 0000000000..b9b9ff2c64 --- /dev/null +++ b/src/commons/hotkeys/HotKeys.tsx @@ -0,0 +1,32 @@ +import { getHotkeyHandler, HotkeyItem } from '@mantine/hooks'; +import { PropsWithChildren } from 'react'; + +/** + * This HOC was created to facilitate the migration out of react-hotkeys in favor of @mantine/hooks useHotkeys, + * as SideContentCseMachine.tsx and SideContentDataVisualizer still use class-based React. + * + * NOTE: + * - New hotkey implementations should NOT use this component. Use functional React and the useHotkeys hook + * from @mantine/hooks directly. + * + * TODO: + * - Eventually migrate out of class-based React in the aforementioned components and use useHotkeys directly. + */ +type HotKeysProps = { + bindings: HotkeyItem[]; +}; + +const HotKeys: React.FC> = ({ bindings, children }) => { + const handler = getHotkeyHandler(bindings); + + return ( +
+ {children} +
+ ); +}; + +export default HotKeys; diff --git a/src/commons/sideContent/content/SideContentDataVisualizer.tsx b/src/commons/sideContent/content/SideContentDataVisualizer.tsx index 8a5306ce62..a26f71c270 100644 --- a/src/commons/sideContent/content/SideContentDataVisualizer.tsx +++ b/src/commons/sideContent/content/SideContentDataVisualizer.tsx @@ -1,10 +1,11 @@ import { Button, Card, Classes } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; +import { HotkeyItem } from '@mantine/hooks'; import classNames from 'classnames'; import React from 'react'; -import { configure, GlobalHotKeys } from 'react-hotkeys'; import { connect, MapDispatchToProps } from 'react-redux'; import { bindActionCreators } from 'redux'; +import HotKeys from 'src/commons/hotkeys/HotKeys'; import DataVisualizer from '../../../features/dataVisualizer/dataVisualizer'; import { Step } from '../../../features/dataVisualizer/dataVisualizerTypes'; @@ -25,11 +26,6 @@ type DispatchProps = { alertSideContent: () => void; }; -const dataVisualizerKeyMap = { - PREVIOUS_STEP: 'left', - NEXT_STEP: 'right' -}; - /** * This class is responsible for the visualization of data structures via the * data_data function in Source. It adds a listener to the DataVisualizer singleton @@ -49,29 +45,18 @@ class SideContentDataVisualizerBase extends React.Component boolean = () => this.state.currentStep === 0; const finalStep: () => boolean = () => !this.state.steps || this.state.currentStep === this.state.steps.length - 1; - const dataVisualizerHandlers = { - PREVIOUS_STEP: this.onPrevButtonClick, - NEXT_STEP: this.onNextButtonClick - }; - - configure({ - ignoreEventsCondition: event => { - return ( - (event.key === 'ArrowLeft' && firstStep()) || (event.key === 'ArrowRight' && finalStep()) - ); - }, - ignoreRepeatedEventsWhenKeyHeldDown: false, - stopEventPropagationAfterIgnoring: false - }); - - const step: Step | undefined = this.state.steps[this.state.currentStep]; + const hotkeyBindings: HotkeyItem[] = [ + ['ArrowLeft', this.onPrevButtonClick], + ['ArrowRight', this.onNextButtonClick] + ]; return ( - +
{this.state.steps.length > 1 ? (
)}
- + ); } private onPrevButtonClick = () => { + const firstStep = 0; this.setState(state => { - return { currentStep: state.currentStep - 1 }; + return { currentStep: Math.max(firstStep, state.currentStep - 1) }; }); }; private onNextButtonClick = () => { + const finalStep = this.state.steps.length - 1; this.setState(state => { - return { currentStep: state.currentStep + 1 }; + return { currentStep: Math.min(finalStep, state.currentStep + 1) }; }); }; } diff --git a/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap b/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap index a2ffc433e2..04a932a9e5 100644 --- a/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap +++ b/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap @@ -811,70 +811,74 @@ and also the class="side-content-text" >
-

- The data visualizer helps you to visualize data structures. -
-
- It is activated by calling the function - - - draw_data(x - - 1 - - , x - - 2 - - , ... x - - n - - ) - - , where - - - x - - k - - - - would be the - - - k - - th - - - - data structure that you want to visualize and - - n - - is the number of structures. -
-
- The data visualizer uses box-and-pointer diagrams, as introduced in - - - - Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 2, Section 2 - - - . -

+ The data visualizer helps you to visualize data structures. +
+
+ It is activated by calling the function + + + draw_data(x + + 1 + + , x + + 2 + + , ... x + + n + + ) + + , where + + + x + + k + + + + would be the + + + k + + th + + + + data structure that you want to visualize and + + n + + is the number of structures. +
+
+ The data visualizer uses box-and-pointer diagrams, as introduced in + + + + Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 2, Section 2 + + + . +

+
@@ -2217,21 +2221,9 @@ exports[`Playground tests Playground with link renders correctly 1`] = ` > + class="bp5-icon bp5-icon-large bp5-icon-flow-review" + data-icon="flow-review" + />
@@ -2367,70 +2359,74 @@ and also the class="side-content-text" >
-

- The data visualizer helps you to visualize data structures. -
-
- It is activated by calling the function - - - draw_data(x - - 1 - - , x - - 2 - - , ... x - - n - - ) - - , where - - - x - - k - - - - would be the - - - k - - th - - - - data structure that you want to visualize and - - n - - is the number of structures. -
-
- The data visualizer uses box-and-pointer diagrams, as introduced in - - - - Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 2, Section 2 - - - . -

+ The data visualizer helps you to visualize data structures. +
+
+ It is activated by calling the function + + + draw_data(x + + 1 + + , x + + 2 + + , ... x + + n + + ) + + , where + + + x + + k + + + + would be the + + + k + + th + + + + data structure that you want to visualize and + + n + + is the number of structures. +
+
+ The data visualizer uses box-and-pointer diagrams, as introduced in + + + + Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 2, Section 2 + + + . +

+
From 10286c1fe767a947e515b08d7b07417e9b3ecc38 Mon Sep 17 00:00:00 2001 From: En Rong <53928333+chownces@users.noreply.github.com> Date: Thu, 2 May 2024 16:49:25 +0800 Subject: [PATCH 04/10] Migrate CSE machine hotkey bindings --- src/commons/hotkeys/HotKeys.tsx | 9 ++++- .../SideContentCseMachine.tsx.snap | 6 +-- .../content/SideContentCseMachine.tsx | 39 ++++++++----------- .../__snapshots__/Playground.tsx.snap | 18 +++++++-- 4 files changed, 40 insertions(+), 32 deletions(-) diff --git a/src/commons/hotkeys/HotKeys.tsx b/src/commons/hotkeys/HotKeys.tsx index b9b9ff2c64..4fd9de0d1f 100644 --- a/src/commons/hotkeys/HotKeys.tsx +++ b/src/commons/hotkeys/HotKeys.tsx @@ -16,13 +16,20 @@ type HotKeysProps = { bindings: HotkeyItem[]; }; -const HotKeys: React.FC> = ({ bindings, children }) => { +const HotKeys: React.FC< + PropsWithChildren< + HotKeysProps & { + style?: React.CSSProperties; + } + > +> = ({ bindings, children, style }) => { const handler = getHotkeyHandler(bindings); return (
{children}
diff --git a/src/commons/sideContent/__tests__/__snapshots__/SideContentCseMachine.tsx.snap b/src/commons/sideContent/__tests__/__snapshots__/SideContentCseMachine.tsx.snap index f33e89daec..628fdc3378 100644 --- a/src/commons/sideContent/__tests__/__snapshots__/SideContentCseMachine.tsx.snap +++ b/src/commons/sideContent/__tests__/__snapshots__/SideContentCseMachine.tsx.snap @@ -2,18 +2,14 @@ exports[`CSE Machine component renders correctly 1`] = `
void; }; -const cseMachineKeyMap = { - FIRST_STEP: 'a', - NEXT_STEP: 'f', - PREVIOUS_STEP: 'b', - LAST_STEP: 'e' -}; - class SideContentCseMachineBase extends React.Component { constructor(props: CseMachineProps) { super(props); @@ -200,24 +194,23 @@ class SideContentCseMachineBase extends React.Component } public render() { - const cseMachineHandlers = this.state.visualization - ? { - FIRST_STEP: this.stepFirst, - NEXT_STEP: this.stepNext, - PREVIOUS_STEP: this.stepPrevious, - LAST_STEP: this.stepLast(this.props.stepsTotal) - } - : { - FIRST_STEP: () => {}, - NEXT_STEP: () => {}, - PREVIOUS_STEP: () => {}, - LAST_STEP: () => {} - }; + const hotkeyBindings: HotkeyItem[] = this.state.visualization + ? [ + ['a', this.stepFirst], + ['f', this.stepNext], + ['b', this.stepPrevious], + ['e', this.stepLast(this.props.stepsTotal)] + ] + : [ + ['a', () => {}], + ['f', () => {}], + ['b', () => {}], + ['e', () => {}] + ]; return (
From 27b1ce5627d7c7f00ba45e67c589b609d41a4532 Mon Sep 17 00:00:00 2001 From: En Rong <53928333+chownces@users.noreply.github.com> Date: Thu, 2 May 2024 16:59:32 +0800 Subject: [PATCH 05/10] Remove react-hotkeys from package.json --- package.json | 1 - yarn.lock | 7 ------- 2 files changed, 8 deletions(-) diff --git a/package.json b/package.json index c87138181f..ff853fb185 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,6 @@ "react-drag-drop-files": "^2.3.10", "react-draggable": "^4.4.5", "react-dropzone": "^14.2.3", - "react-hotkeys": "^2.0.0", "react-i18next": "^14.1.0", "react-konva": "^18.2.10", "react-latex-next": "^2.1.0", diff --git a/yarn.lock b/yarn.lock index f50c974cbe..16ac919027 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11274,13 +11274,6 @@ react-fast-compare@^3.0.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== -react-hotkeys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/react-hotkeys/-/react-hotkeys-2.0.0.tgz#a7719c7340cbba888b0e9184f806a9ec0ac2c53f" - integrity sha512-3n3OU8vLX/pfcJrR3xJ1zlww6KS1kEJt0Whxc4FiGV+MJrQ1mYSYI3qS/11d2MJDFm8IhOXMTFQirfu6AVOF6Q== - dependencies: - prop-types "^15.6.1" - react-i18next@^14.1.0: version "14.1.0" resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-14.1.0.tgz#44da74fbffd416f5d0c5307ef31735cf10cc91d9" From 086b6141891a566d7ef095f07f5881f40ee6cfd4 Mon Sep 17 00:00:00 2001 From: En Rong <53928333+chownces@users.noreply.github.com> Date: Thu, 2 May 2024 17:30:13 +0800 Subject: [PATCH 06/10] Fix PR comments --- src/commons/editor/Editor.tsx | 7 +++---- src/commons/repl/Repl.tsx | 8 +++----- src/pages/playground/Playground.tsx | 4 ++-- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/commons/editor/Editor.tsx b/src/commons/editor/Editor.tsx index e099abf927..00e565bb8a 100644 --- a/src/commons/editor/Editor.tsx +++ b/src/commons/editor/Editor.tsx @@ -4,10 +4,9 @@ import 'ace-builds/src-noconflict/ext-searchbox'; import 'ace-builds/src-noconflict/ext-settings_menu'; import 'js-slang/dist/editors/ace/theme/source'; -import { Classes } from '@blueprintjs/core'; +import { Card } from '@blueprintjs/core'; import * as AceBuilds from 'ace-builds'; import { Ace, require as acequire, createEditSession } from 'ace-builds'; -import classNames from 'classnames'; import { Chapter, Variant } from 'js-slang/dist/types'; import React from 'react'; import AceEditor, { IAceEditorProps, IEditorProps } from 'react-ace'; @@ -646,11 +645,11 @@ const EditorBase = React.memo((props: EditorProps & LocalStateProps) => { }, []); return ( -
+
-
+ ); }); diff --git a/src/commons/repl/Repl.tsx b/src/commons/repl/Repl.tsx index bdd1d230a8..ad9ed588b5 100644 --- a/src/commons/repl/Repl.tsx +++ b/src/commons/repl/Repl.tsx @@ -1,4 +1,4 @@ -import { Card, Classes, Pre } from '@blueprintjs/core'; +import { Card, Pre } from '@blueprintjs/core'; import { Ace } from 'ace-builds'; import classNames from 'classnames'; import { parseError } from 'js-slang'; @@ -51,11 +51,9 @@ const Repl: React.FC = props => {
{cards} {!props.inputHidden && ( -
+ -
+ )}
diff --git a/src/pages/playground/Playground.tsx b/src/pages/playground/Playground.tsx index 2d676bf2dc..33eff6549b 100644 --- a/src/pages/playground/Playground.tsx +++ b/src/pages/playground/Playground.tsx @@ -320,8 +320,8 @@ const Playground: React.FC = props => { // Playground hotkeys const [isGreen, setIsGreen] = useState(false); const playgroundHotkeyBindings: HotkeyItem[] = useMemo( - () => [['alt+shift+h', () => setIsGreen(!isGreen)]], - [isGreen, setIsGreen] + () => [['alt+shift+h', () => setIsGreen(v => !v)]], + [setIsGreen] ); useHotkeys(playgroundHotkeyBindings); From c0727e760dd0d52d13697532673f27b7de2ca218 Mon Sep 17 00:00:00 2001 From: En Rong <53928333+chownces@users.noreply.github.com> Date: Thu, 2 May 2024 17:36:07 +0800 Subject: [PATCH 07/10] Fix snapshots --- src/commons/repl/__tests__/__snapshots__/Repl.tsx.snap | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/commons/repl/__tests__/__snapshots__/Repl.tsx.snap b/src/commons/repl/__tests__/__snapshots__/Repl.tsx.snap index 9a540b5964..3d26fde8e7 100644 --- a/src/commons/repl/__tests__/__snapshots__/Repl.tsx.snap +++ b/src/commons/repl/__tests__/__snapshots__/Repl.tsx.snap @@ -135,8 +135,10 @@ exports[`Repl renders correctly 1`] = ` } usingSubst={false} /> -
-
+
`; From f9ada4f055d43f3c16fd82407705f3c0d71a9290 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Thu, 2 May 2024 18:11:27 +0800 Subject: [PATCH 08/10] Use Blueprint Card component instead of div --- src/commons/sourceRecorder/SourceRecorderEditor.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/commons/sourceRecorder/SourceRecorderEditor.tsx b/src/commons/sourceRecorder/SourceRecorderEditor.tsx index cb403d1441..954fbfd2d0 100644 --- a/src/commons/sourceRecorder/SourceRecorderEditor.tsx +++ b/src/commons/sourceRecorder/SourceRecorderEditor.tsx @@ -2,9 +2,8 @@ import 'ace-builds/src-noconflict/ext-searchbox'; import 'ace-builds/src-noconflict/mode-javascript'; import 'js-slang/dist/editors/ace/theme/source'; -import { Classes } from '@blueprintjs/core'; +import { Card } from '@blueprintjs/core'; import { Ace } from 'ace-builds'; -import classNames from 'classnames'; import { isEqual } from 'lodash'; import React from 'react'; import AceEditor, { IAceEditorProps } from 'react-ace'; @@ -207,7 +206,7 @@ class SourcecastEditor extends React.PureComponent +
-
+ ); } From c1e0a970d1b63b9aa0031497c55edf32481502c5 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Thu, 2 May 2024 18:11:54 +0800 Subject: [PATCH 09/10] Move documentation from props to component --- src/commons/hotkeys/HotKeys.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/commons/hotkeys/HotKeys.tsx b/src/commons/hotkeys/HotKeys.tsx index 4fd9de0d1f..0baf92ee20 100644 --- a/src/commons/hotkeys/HotKeys.tsx +++ b/src/commons/hotkeys/HotKeys.tsx @@ -1,5 +1,9 @@ import { getHotkeyHandler, HotkeyItem } from '@mantine/hooks'; -import { PropsWithChildren } from 'react'; +import React, { PropsWithChildren } from 'react'; + +type HotKeysProps = { + bindings: HotkeyItem[]; +}; /** * This HOC was created to facilitate the migration out of react-hotkeys in favor of @mantine/hooks useHotkeys, @@ -12,10 +16,6 @@ import { PropsWithChildren } from 'react'; * TODO: * - Eventually migrate out of class-based React in the aforementioned components and use useHotkeys directly. */ -type HotKeysProps = { - bindings: HotkeyItem[]; -}; - const HotKeys: React.FC< PropsWithChildren< HotKeysProps & { From 44c602bfbfd1fd9f699118deb9334f4d311b9504 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Thu, 2 May 2024 18:18:48 +0800 Subject: [PATCH 10/10] Fix format and snapshots --- .../AssessmentWorkspace.tsx.snap | 234 +++++++++++++++--- .../sourceRecorder/SourceRecorderEditor.tsx | 2 +- 2 files changed, 196 insertions(+), 40 deletions(-) diff --git a/src/commons/assessmentWorkspace/__tests__/__snapshots__/AssessmentWorkspace.tsx.snap b/src/commons/assessmentWorkspace/__tests__/__snapshots__/AssessmentWorkspace.tsx.snap index dea696bdbb..c3cbff879d 100644 --- a/src/commons/assessmentWorkspace/__tests__/__snapshots__/AssessmentWorkspace.tsx.snap +++ b/src/commons/assessmentWorkspace/__tests__/__snapshots__/AssessmentWorkspace.tsx.snap @@ -2990,9 +2990,21 @@ exports[`AssessmentWorkspace AssessmentWorkspace page with programming question > @@ -3009,9 +3021,21 @@ exports[`AssessmentWorkspace AssessmentWorkspace page with programming question > @@ -3023,9 +3047,21 @@ exports[`AssessmentWorkspace AssessmentWorkspace page with programming question > @@ -3080,9 +3116,21 @@ exports[`AssessmentWorkspace AssessmentWorkspace page with programming question
@@ -3319,9 +3391,21 @@ exports[`AssessmentWorkspace AssessmentWorkspace page with programming question >
@@ -3346,9 +3430,21 @@ exports[`AssessmentWorkspace AssessmentWorkspace page with programming question >
@@ -3422,9 +3518,21 @@ exports[`AssessmentWorkspace AssessmentWorkspace page with programming question > @@ -3437,10 +3545,22 @@ exports[`AssessmentWorkspace AssessmentWorkspace page with programming question @@ -3466,9 +3586,21 @@ exports[`AssessmentWorkspace AssessmentWorkspace page with programming question > @@ -3624,9 +3756,21 @@ exports[`AssessmentWorkspace AssessmentWorkspace page with programming question > @@ -3639,9 +3783,21 @@ exports[`AssessmentWorkspace AssessmentWorkspace page with programming question > diff --git a/src/commons/sourceRecorder/SourceRecorderEditor.tsx b/src/commons/sourceRecorder/SourceRecorderEditor.tsx index 954fbfd2d0..0f7b9af4d0 100644 --- a/src/commons/sourceRecorder/SourceRecorderEditor.tsx +++ b/src/commons/sourceRecorder/SourceRecorderEditor.tsx @@ -206,7 +206,7 @@ class SourcecastEditor extends React.PureComponent +