diff --git a/client/package-lock.json b/client/package-lock.json index 775d03bb41..7b42127fcf 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -16613,9 +16613,9 @@ } }, "react-hotkeys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/react-hotkeys/-/react-hotkeys-2.0.0.tgz", - "integrity": "sha512-3n3OU8vLX/pfcJrR3xJ1zlww6KS1kEJt0Whxc4FiGV+MJrQ1mYSYI3qS/11d2MJDFm8IhOXMTFQirfu6AVOF6Q==", + "version": "2.0.0-pre9", + "resolved": "https://registry.npmjs.org/react-hotkeys/-/react-hotkeys-2.0.0-pre9.tgz", + "integrity": "sha512-YujzB+kGB5F6rq6/NkNN2t3uSwYfBsC9qWligGKyDe7roMSmzFYO2N88mwSc+9zmHhy/ZrDyB+aqbzVIaK8haw==", "requires": { "prop-types": "^15.6.1" }, diff --git a/client/package.json b/client/package.json index 8eb26b0134..34d2587b18 100644 --- a/client/package.json +++ b/client/package.json @@ -53,7 +53,7 @@ "react-final-form": "^6.3.0", "react-ga": "^2.6.0", "react-helmet": "^5.2.1", - "react-hotkeys": "^2.0.0", + "react-hotkeys": "^2.0.0-pre9", "react-identicons": "^1.1.7", "react-instantsearch-dom": "^5.7.0", "react-monaco-editor": "^0.30.1", diff --git a/client/src/templates/Challenges/classic/Editor.js b/client/src/templates/Challenges/classic/Editor.js index 8e82b2a873..e496fc83ae 100644 --- a/client/src/templates/Challenges/classic/Editor.js +++ b/client/src/templates/Challenges/classic/Editor.js @@ -3,7 +3,6 @@ import PropTypes from 'prop-types'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { navigate } from 'gatsby'; import { executeChallenge, updateFile } from '../redux'; import { userSelector, isDonationModalOpenSelector } from '../../../redux'; @@ -13,13 +12,12 @@ const MonacoEditor = React.lazy(() => import('react-monaco-editor')); const propTypes = { canFocus: PropTypes.bool, + containerRef: PropTypes.any.isRequired, contents: PropTypes.string, dimensions: PropTypes.object, executeChallenge: PropTypes.func.isRequired, ext: PropTypes.string, fileKey: PropTypes.string, - nextChallengePath: PropTypes.string.isRequired, - prevChallengePath: PropTypes.string.isRequired, theme: PropTypes.string, updateFile: PropTypes.func.isRequired }; @@ -123,26 +121,13 @@ class Editor extends Component { run: this.props.executeChallenge }); this._editor.addAction({ - id: 'navigate-prev', - label: 'Navigate to previous challenge', - keybindings: [ - /* eslint-disable no-bitwise */ - monaco.KeyMod.chord( - monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.US_COMMA - ) - ], - run: () => navigate(this.props.prevChallengePath) - }); - this._editor.addAction({ - id: 'navigate-next', - label: 'Navigate to next challenge', - keybindings: [ - /* eslint-disable no-bitwise */ - monaco.KeyMod.chord( - monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.US_DOT - ) - ], - run: () => navigate(this.props.nextChallengePath) + id: 'leave-editor', + label: 'Leave editor', + keybindings: [monaco.KeyCode.Escape], + run: () => { + if (this.props.containerRef.current) + this.props.containerRef.current.focus(); + } }); }; diff --git a/client/src/templates/Challenges/classic/Show.js b/client/src/templates/Challenges/classic/Show.js index eb56ea0d79..409dcaae2d 100644 --- a/client/src/templates/Challenges/classic/Show.js +++ b/client/src/templates/Challenges/classic/Show.js @@ -19,6 +19,7 @@ import VideoModal from '../components/VideoModal'; import ResetModal from '../components/ResetModal'; import MobileLayout from './MobileLayout'; import DesktopLayout from './DesktopLayout'; +import Hotkeys from '../components/Hotkeys'; import { getGuideUrl } from '../utils'; import { challengeTypes } from '../../../../utils/challengeTypes'; @@ -32,7 +33,8 @@ import { initTests, updateChallengeMeta, challengeMounted, - consoleOutputSelector + consoleOutputSelector, + executeChallenge } from '../redux'; import './classic.css'; @@ -51,7 +53,8 @@ const mapDispatchToProps = dispatch => initConsole, initTests, updateChallengeMeta, - challengeMounted + challengeMounted, + executeChallenge }, dispatch ); @@ -62,6 +65,7 @@ const propTypes = { data: PropTypes.shape({ challengeNode: ChallengeNode }), + executeChallenge: PropTypes.func.isRequired, files: PropTypes.shape({ key: PropTypes.string }), @@ -99,6 +103,8 @@ class ShowClassic extends Component { this.state = { resizing: false }; + + this.containerRef = React.createRef(); } onResize() { this.setState({ resizing: true }); @@ -219,19 +225,13 @@ class ShowClassic extends Component { } renderEditor() { - const { - files, - pageContext: { - challengeMeta: { prevChallengePath, nextChallengePath } - } - } = this.props; + const { files } = this.props; const challengeFile = first(Object.keys(files).map(key => files[key])); return ( challengeFile && ( @@ -261,42 +261,56 @@ class ShowClassic extends Component { render() { const { forumTopicId, title } = this.getChallenge(); + const { + executeChallenge, + pageContext: { + challengeMeta: { introPath, nextChallengePath, prevChallengePath } + } + } = this.props; return ( - - - - + + - - - - - - - - - + + + + + + + + + + + + ); } } diff --git a/client/src/templates/Challenges/components/Challenge-Title.js b/client/src/templates/Challenges/components/Challenge-Title.js index bf20e04e24..3a8d91b46e 100644 --- a/client/src/templates/Challenges/components/Challenge-Title.js +++ b/client/src/templates/Challenges/components/Challenge-Title.js @@ -1,17 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import Link from '../../../components/helpers/Link'; -import { GlobalHotKeys } from 'react-hotkeys'; -import { navigate } from 'gatsby'; import './challenge-title.css'; import GreenPass from '../../../assets/icons/GreenPass'; -const keyMap = { - NAVIGATE_PREV: ['ctrl+shift+<', 'cmd+shift+<'], - NAVIGATE_NEXT: ['ctrl+shift+>', 'cmd+shift+>'] -}; - const propTypes = { children: PropTypes.string, introPath: PropTypes.string, @@ -29,13 +22,8 @@ function ChallengeTitle({ prevChallengePath, showPrevNextBtns }) { - const handlers = { - NAVIGATE_PREV: () => navigate(prevChallengePath), - NAVIGATE_NEXT: () => navigate(nextChallengePath) - }; return (
- {showPrevNextBtns ? ( { + if (executeChallenge) executeChallenge(); + }, + NAVIGATE_PREV: () => navigate(prevChallengePath), + NAVIGATE_NEXT: () => navigate(introPath ? introPath : nextChallengePath) + }; + return ( + + {children} + + ); +} + +Hotkeys.displayName = 'Hotkeys'; +Hotkeys.propTypes = propTypes; + +export default Hotkeys; diff --git a/client/src/templates/Challenges/projects/backend/Show.js b/client/src/templates/Challenges/projects/backend/Show.js index 41c291ce45..a060a76393 100644 --- a/client/src/templates/Challenges/projects/backend/Show.js +++ b/client/src/templates/Challenges/projects/backend/Show.js @@ -33,6 +33,7 @@ import { Form } from '../../../../components/formHelpers'; import Spacer from '../../../../components/helpers/Spacer'; import { ChallengeNode } from '../../../../redux/propTypes'; import { isSignedInSelector } from '../../../../redux'; +import Hotkeys from '../../components/Hotkeys'; import { backend } from '../../../../../utils/challengeTypes'; @@ -186,6 +187,11 @@ export class BackEnd extends Component { return ( + diff --git a/client/src/templates/Challenges/projects/frontend/Show.js b/client/src/templates/Challenges/projects/frontend/Show.js index 0ff95c4d42..f61eaa9f53 100644 --- a/client/src/templates/Challenges/projects/frontend/Show.js +++ b/client/src/templates/Challenges/projects/frontend/Show.js @@ -24,6 +24,7 @@ import ProjectForm from '../ProjectForm'; import ProjectToolPanel from '../Tool-Panel'; import CompletionModal from '../../components/CompletionModal'; import HelpModal from '../../components/HelpModal'; +import Hotkeys from '../../components/Hotkeys'; const mapStateToProps = () => ({}); const mapDispatchToProps = dispatch => @@ -110,6 +111,11 @@ export class Project extends Component { const blockNameTitle = `${blockName} - ${title}`; return ( +