diff --git a/client/src/templates/Challenges/classic/Editor.js b/client/src/templates/Challenges/classic/Editor.js index 29322e747d..44f4e5f6ef 100644 --- a/client/src/templates/Challenges/classic/Editor.js +++ b/client/src/templates/Challenges/classic/Editor.js @@ -6,7 +6,9 @@ import { createSelector } from 'reselect'; import { canFocusEditorSelector, executeChallenge, + inAccessibilityModeSelector, setEditorFocusability, + setAccessibilityMode, updateFile } from '../redux'; import { userSelector, isDonationModalOpenSelector } from '../../../redux'; @@ -22,6 +24,8 @@ const propTypes = { executeChallenge: PropTypes.func.isRequired, ext: PropTypes.string, fileKey: PropTypes.string, + inAccessibilityMode: PropTypes.bool.isRequired, + setAccessibilityMode: PropTypes.func.isRequired, setEditorFocusability: PropTypes.func, theme: PropTypes.string, updateFile: PropTypes.func.isRequired @@ -29,16 +33,19 @@ const propTypes = { const mapStateToProps = createSelector( canFocusEditorSelector, + inAccessibilityModeSelector, isDonationModalOpenSelector, userSelector, - (canFocus, open, { theme = 'default' }) => ({ + (canFocus, accessibilityMode, open, { theme = 'default' }) => ({ canFocus: open ? false : canFocus, + inAccessibilityMode: accessibilityMode, theme }) ); const mapDispatchToProps = { setEditorFocusability, + setAccessibilityMode, executeChallenge, updateFile }; @@ -114,7 +121,12 @@ class Editor extends Component { editorDidMount = (editor, monaco) => { this._editor = editor; - if (this.props.canFocus) { + this._editor.updateOptions({ + accessibilitySupport: this.props.inAccessibilityMode ? 'on' : 'auto' + }); + // Users who are using screen readers should not have to move focus from + // the editor to the description every time they open a challenge. + if (this.props.canFocus && !this.props.inAccessibilityMode) { this._editor.focus(); } else this.focusOnHotkeys(); this._editor.addAction({ @@ -135,9 +147,33 @@ class Editor extends Component { this.props.setEditorFocusability(false); } }); + this._editor.addAction({ + id: 'toggle-accessibility', + label: 'Toggle Accessibility Mode', + keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.F1], + run: () => { + const currentAccessibility = this.props.inAccessibilityMode; + // The store needs to be updated first, as onDidChangeConfiguration is + // called before updateOptions returns + this.props.setAccessibilityMode(!currentAccessibility); + this._editor.updateOptions({ + accessibilitySupport: currentAccessibility ? 'auto' : 'on' + }); + } + }); this._editor.onDidFocusEditorWidget(() => this.props.setEditorFocusability(true) ); + // This is to persist changes caused by the accessibility tooltip. + // Unfortunately it relies on Monaco's implementation details + this._editor.onDidChangeConfiguration(() => { + if ( + this._editor.getConfiguration().accessibilitySupport === 2 && + !this.props.inAccessibilityMode + ) { + this.props.setAccessibilityMode(true); + } + }); }; focusOnHotkeys() { diff --git a/client/src/templates/Challenges/redux/index.js b/client/src/templates/Challenges/redux/index.js index a8c7a8435a..7385813d6b 100644 --- a/client/src/templates/Challenges/redux/index.js +++ b/client/src/templates/Challenges/redux/index.js @@ -29,6 +29,7 @@ const initialState = { }, challengeTests: [], consoleOut: '', + inAccessibilityMode: false, isCodeLocked: false, isBuildEnabled: true, modal: { @@ -78,7 +79,8 @@ export const types = createTypes( 'moveToTab', - 'setEditorFocusability' + 'setEditorFocusability', + 'setAccessibilityMode' ], ns ); @@ -152,6 +154,7 @@ export const submitChallenge = createAction(types.submitChallenge); export const moveToTab = createAction(types.moveToTab); export const setEditorFocusability = createAction(types.setEditorFocusability); +export const setAccessibilityMode = createAction(types.setAccessibilityMode); export const currentTabSelector = state => state[ns].currentTab; export const challengeFilesSelector = state => state[ns].challengeFiles; @@ -223,6 +226,8 @@ export const challengeDataSelector = state => { }; export const canFocusEditorSelector = state => state[ns].canFocusEditor; +export const inAccessibilityModeSelector = state => + state[ns].inAccessibilityMode; const MAX_LOGS_SIZE = 64 * 1024; @@ -359,6 +364,10 @@ export const reducer = handleActions( [types.setEditorFocusability]: (state, { payload }) => ({ ...state, canFocusEditor: payload + }), + [types.setAccessibilityMode]: (state, { payload }) => ({ + ...state, + inAccessibilityMode: payload }) }, initialState