From 82207a50d39d87872c257a441609baef8f817b9d Mon Sep 17 00:00:00 2001 From: Mathura MG Date: Wed, 31 May 2017 12:23:30 -0700 Subject: [PATCH] Accessibility (#361) * add p5 interceptor submodule * update package * remoce interceptor * update interceptor; * merge scripts * change postinstall script * refactor interceptor files * remove merge conflicts * change source files * add registry class * provide seperate outputs for text and grid * switch textOutput to boolean * make both modules usable together * update interceptor for safari * fix grid label * add sound output as well * change file strucure * change constants * change input lables * switch submodule branch * change variable name * change grid to table * remove role from table elements * switch submodule branch --- client/constants.js | 6 +- client/modules/IDE/actions/ide.js | 8 +-- client/modules/IDE/actions/preferences.js | 37 +++++++++- .../IDE/components/AccessibleOutput.jsx | 51 ++++++++++++++ client/modules/IDE/components/GridOutput.jsx | 38 ++++++++++ client/modules/IDE/components/Preferences.jsx | 55 +++++++-------- .../modules/IDE/components/PreviewFrame.jsx | 69 +++++++++++-------- client/modules/IDE/components/TextOutput.jsx | 31 ++------- client/modules/IDE/components/Toolbar.jsx | 8 +-- client/modules/IDE/pages/IDEView.jsx | 48 ++++++++----- client/modules/IDE/reducers/ide.js | 10 +-- client/modules/IDE/reducers/preferences.js | 8 ++- client/styles/components/_preferences.scss | 6 +- client/styles/layout/_ide.scss | 2 +- server/models/user.js | 4 +- static/p5-interceptor | 2 +- 16 files changed, 261 insertions(+), 122 deletions(-) create mode 100644 client/modules/IDE/components/AccessibleOutput.jsx create mode 100644 client/modules/IDE/components/GridOutput.jsx diff --git a/client/constants.js b/client/constants.js index 149dc79c..8f1a5573 100644 --- a/client/constants.js +++ b/client/constants.js @@ -4,8 +4,8 @@ export const TOGGLE_SKETCH = 'TOGGLE_SKETCH'; export const START_SKETCH = 'START_SKETCH'; export const STOP_SKETCH = 'STOP_SKETCH'; -export const START_TEXT_OUTPUT = 'START_TEXT_OUTPUT'; -export const STOP_TEXT_OUTPUT = 'STOP_TEXT_OUTPUT'; +export const START_ACCESSIBLE_OUTPUT = 'START_ACCESSIBLE_OUTPUT'; +export const STOP_ACCESSIBLE_OUTPUT = 'STOP_ACCESSIBLE_OUTPUT'; export const OPEN_PREFERENCES = 'OPEN_PREFERENCES'; export const CLOSE_PREFERENCES = 'CLOSE_PREFERENCES'; @@ -69,6 +69,8 @@ export const SET_AUTOSAVE = 'SET_AUTOSAVE'; export const SET_LINT_WARNING = 'SET_LINT_WARNING'; export const SET_PREFERENCES = 'SET_PREFERENCES'; export const SET_TEXT_OUTPUT = 'SET_TEXT_OUTPUT'; +export const SET_GRID_OUTPUT = 'SET_GRID_OUTPUT'; +export const SET_SOUND_OUTPUT = 'SET_SOUND_OUTPUT'; export const OPEN_PROJECT_OPTIONS = 'OPEN_PROJECT_OPTIONS'; export const CLOSE_PROJECT_OPTIONS = 'CLOSE_PROJECT_OPTIONS'; diff --git a/client/modules/IDE/actions/ide.js b/client/modules/IDE/actions/ide.js index 4f5c0662..0e8fa3ae 100644 --- a/client/modules/IDE/actions/ide.js +++ b/client/modules/IDE/actions/ide.js @@ -31,15 +31,15 @@ export function endSketchRefresh() { }; } -export function startTextOutput() { +export function startAccessibleOutput() { return { - type: ActionTypes.START_TEXT_OUTPUT + type: ActionTypes.START_ACCESSIBLE_OUTPUT }; } -export function stopTextOutput() { +export function stopAccessibleOutput() { return { - type: ActionTypes.STOP_TEXT_OUTPUT + type: ActionTypes.STOP_ACCESSIBLE_OUTPUT }; } diff --git a/client/modules/IDE/actions/preferences.js b/client/modules/IDE/actions/preferences.js index bdc40f76..4f735280 100644 --- a/client/modules/IDE/actions/preferences.js +++ b/client/modules/IDE/actions/preferences.js @@ -137,6 +137,42 @@ export function setTextOutput(value) { }; } +export function setGridOutput(value) { + return (dispatch, getState) => { + dispatch({ + type: ActionTypes.SET_GRID_OUTPUT, + value + }); + const state = getState(); + if (state.user.authenticated) { + const formParams = { + preferences: { + gridOutput: value + } + }; + updatePreferences(formParams, dispatch); + } + }; +} + +export function setSoundOutput(value) { + return (dispatch, getState) => { + dispatch({ + type: ActionTypes.SET_SOUND_OUTPUT, + value + }); + const state = getState(); + if (state.user.authenticated) { + const formParams = { + preferences: { + soundOutput: value + } + }; + updatePreferences(formParams, dispatch); + } + }; +} + export function setTheme(value) { // return { // type: ActionTypes.SET_THEME, @@ -180,4 +216,3 @@ export function setAutorefresh(value) { } }; } - diff --git a/client/modules/IDE/components/AccessibleOutput.jsx b/client/modules/IDE/components/AccessibleOutput.jsx new file mode 100644 index 00000000..a3688bd7 --- /dev/null +++ b/client/modules/IDE/components/AccessibleOutput.jsx @@ -0,0 +1,51 @@ +import React, { PropTypes } from 'react'; +import GridOutput from '../components/GridOutput'; +import TextOutput from '../components/TextOutput'; + +class AccessibleOutput extends React.Component { + componentDidMount() { + this.accessibleOutputModal.focus(); + } + componentDidUpdate(prevProps) { + // if the user explicitly clicks on the play button, want to refocus on the text output + if (this.props.isPlaying && this.props.previewIsRefreshing) { + this.accessibleOutputModal.focus(); + } + } + render() { + return ( +
{ this.accessibleOutputModal = element; }} + tabIndex="0" + aria-label="accessible-output" + title="canvas text output" + > + {(() => { // eslint-disable-line + if (this.props.textOutput) { + return ( + + ); + } + })()} + {(() => { // eslint-disable-line + if (this.props.gridOutput) { + return ( + + ); + } + })()} +
+ ); + } +} + +AccessibleOutput.propTypes = { + isPlaying: PropTypes.bool.isRequired, + previewIsRefreshing: PropTypes.bool.isRequired, + textOutput: PropTypes.bool.isRequired, + gridOutput: PropTypes.bool.isRequired +}; + +export default AccessibleOutput; diff --git a/client/modules/IDE/components/GridOutput.jsx b/client/modules/IDE/components/GridOutput.jsx new file mode 100644 index 00000000..17fb1213 --- /dev/null +++ b/client/modules/IDE/components/GridOutput.jsx @@ -0,0 +1,38 @@ +import React, { PropTypes } from 'react'; + +class GridOutput extends React.Component { + componentDidMount() { + this.GridOutputModal.focus(); + } + render() { + return ( +
{ this.GridOutputModal = element; }} + > +

Grid Output

+

+

+ +
+
+
+
+ ); + } +} + +export default GridOutput; diff --git a/client/modules/IDE/components/Preferences.jsx b/client/modules/IDE/components/Preferences.jsx index cb549a13..24203322 100644 --- a/client/modules/IDE/components/Preferences.jsx +++ b/client/modules/IDE/components/Preferences.jsx @@ -261,50 +261,41 @@ class Preferences extends React.Component {
this.props.setTextOutput(1)} + type="checkbox" + onChange={(event) => { + this.props.setTextOutput(event.target.checked); + }} aria-label="text output on" name="text output" id="text-output-on" - className="preference__radio-button" value="On" - checked={Boolean(this.props.textOutput === 1)} + checked={(this.props.textOutput)} /> this.props.setTextOutput(2)} - aria-label="table text output on" - name="table text output" - id="grid-output-on" - className="preference__radio-button" - value="Grid On" - checked={Boolean(this.props.textOutput === 2)} + type="checkbox" + onChange={(event) => { + this.props.setGridOutput(event.target.checked); + }} + aria-label="table output on" + name="table output" + id="table-output-on" + value="On" + checked={(this.props.gridOutput)} /> - + this.props.setTextOutput(3)} + type="checkbox" + onChange={(event) => { + this.props.setSoundOutput(event.target.checked); + }} aria-label="sound output on" name="sound output" id="sound-output-on" - className="preference__radio-button" value="On" - checked={Boolean(this.props.textOutput === 3)} + checked={(this.props.soundOutput)} /> - this.props.setTextOutput(0)} - aria-label="text output off" - name="text output" - id="text-output-off" - className="preference__radio-button" - value="Off" - checked={!(this.props.textOutput)} - /> - -
@@ -324,8 +315,12 @@ Preferences.propTypes = { setFontSize: PropTypes.func.isRequired, autosave: PropTypes.bool.isRequired, setAutosave: PropTypes.func.isRequired, - textOutput: PropTypes.number.isRequired, + textOutput: PropTypes.bool.isRequired, + gridOutput: PropTypes.bool.isRequired, + soundOutput: PropTypes.bool.isRequired, setTextOutput: PropTypes.func.isRequired, + setGridOutput: PropTypes.func.isRequired, + setSoundOutput: PropTypes.func.isRequired, lintWarning: PropTypes.bool.isRequired, setLintWarning: PropTypes.func.isRequired, theme: PropTypes.string.isRequired, diff --git a/client/modules/IDE/components/PreviewFrame.jsx b/client/modules/IDE/components/PreviewFrame.jsx index 33af7507..622e7329 100644 --- a/client/modules/IDE/components/PreviewFrame.jsx +++ b/client/modules/IDE/components/PreviewFrame.jsx @@ -114,7 +114,7 @@ class PreviewFrame extends React.Component { } // if user switches textoutput preferences - if (this.props.isTextOutputPlaying !== prevProps.isTextOutputPlaying) { + if (this.props.isAccessibleOutputPlaying !== prevProps.isAccessibleOutputPlaying) { this.renderSketch(); return; } @@ -124,6 +124,16 @@ class PreviewFrame extends React.Component { return; } + if (this.props.gridOutput !== prevProps.gridOutput) { + this.renderSketch(); + return; + } + + if (this.props.soundOutput !== prevProps.soundOutput) { + this.renderSketch(); + return; + } + if (this.props.fullView && this.props.files[0].id !== prevProps.files[0].id) { this.renderSketch(); } @@ -165,38 +175,41 @@ class PreviewFrame extends React.Component { '/loop-protect.min.js', '/hijackConsole.js' ]; - if (this.props.isTextOutputPlaying || (this.props.textOutput !== 0 && this.props.isPlaying)) { + if (this.props.isAccessibleOutputPlaying || ((this.props.textOutput || this.props.gridOutput || this.props.soundOutput) && this.props.isPlaying)) { let interceptorScripts = []; - if (this.props.textOutput === 0) { - this.props.setTextOutput(1); + interceptorScripts = [ + '/p5-interceptor/registry.js', + '/p5-interceptor/loadData.js', + '/p5-interceptor/interceptorHelperFunctions.js', + '/p5-interceptor/baseInterceptor.js', + '/p5-interceptor/entities/entity.min.js', + '/p5-interceptor/ntc.min.js' + ]; + if (!this.props.textOutput && !this.props.gridOutput && !this.props.soundOutput) { + this.props.setTextOutput(true); } - if (this.props.textOutput === 1) { - interceptorScripts = [ - '/p5-interceptor/registry.js', - '/p5-interceptor/loadData.js', - '/p5-interceptor/interceptorHelperFunctions.js', - '/p5-interceptor/baseInterceptor.js', - '/p5-interceptor/entities/entity.min.js', + if (this.props.textOutput) { + let textInterceptorScripts = []; + textInterceptorScripts = [ '/p5-interceptor/textInterceptor/interceptorFunctions.js', - '/p5-interceptor/textInterceptor/interceptorP5.js', - '/p5-interceptor/ntc.min.js' + '/p5-interceptor/textInterceptor/interceptorP5.js' ]; - } else if (this.props.textOutput === 2) { - interceptorScripts = [ - '/p5-interceptor/registry.js', - '/p5-interceptor/loadData.js', - '/p5-interceptor/interceptorHelperFunctions.js', - '/p5-interceptor/baseInterceptor.js', - '/p5-interceptor/entities/entity.min.js', + interceptorScripts = interceptorScripts.concat(textInterceptorScripts); + } + if (this.props.gridOutput) { + let gridInterceptorScripts = []; + gridInterceptorScripts = [ '/p5-interceptor/gridInterceptor/interceptorFunctions.js', - '/p5-interceptor/gridInterceptor/interceptorP5.js', - '/p5-interceptor/ntc.min.js' + '/p5-interceptor/gridInterceptor/interceptorP5.js' ]; - } else if (this.props.textOutput === 3) { - interceptorScripts = [ - '/p5-interceptor/loadData.js', + interceptorScripts = interceptorScripts.concat(gridInterceptorScripts); + } + if (this.props.soundOutput) { + let soundInterceptorScripts = []; + soundInterceptorScripts = [ '/p5-interceptor/soundInterceptor/interceptorP5.js' ]; + interceptorScripts = interceptorScripts.concat(soundInterceptorScripts); } scriptsToInject = scriptsToInject.concat(interceptorScripts); } @@ -373,8 +386,10 @@ class PreviewFrame extends React.Component { PreviewFrame.propTypes = { isPlaying: PropTypes.bool.isRequired, - isTextOutputPlaying: PropTypes.bool.isRequired, - textOutput: PropTypes.number.isRequired, + isAccessibleOutputPlaying: PropTypes.bool.isRequired, + textOutput: PropTypes.bool.isRequired, + gridOutput: PropTypes.bool.isRequired, + soundOutput: PropTypes.bool.isRequired, setTextOutput: PropTypes.func.isRequired, htmlFile: PropTypes.shape({ content: PropTypes.string.isRequired diff --git a/client/modules/IDE/components/TextOutput.jsx b/client/modules/IDE/components/TextOutput.jsx index 67d67db6..f9b528e0 100644 --- a/client/modules/IDE/components/TextOutput.jsx +++ b/client/modules/IDE/components/TextOutput.jsx @@ -1,28 +1,16 @@ -import React, { PropTypes } from 'react'; +import React from 'react'; class TextOutput extends React.Component { componentDidMount() { - this.canvasTextOutput.focus(); - } - componentDidUpdate(prevProps) { - // if the user explicitly clicks on the play button, want to refocus on the text output - if (this.props.isPlaying && this.props.previewIsRefreshing) { - this.canvasTextOutput.focus(); - } + this.TextOutputModal.focus(); } render() { return (
{ this.canvasTextOutput = element; }} - tabIndex="0" - aria-label="text-output" - title="canvas text output" + id="textOutput-content" + ref={(element) => { this.TextOutputModal = element; }} > -

Output

-
-
+

Text Output

{ this.props.clearConsole(); - this.props.startTextOutput(); + this.props.startAccessibleOutput(); this.props.startSketchAndRefresh(); }} aria-label="play sketch" @@ -85,7 +85,7 @@ class Toolbar extends React.Component {
{(() => { - if ((this.props.preferences.textOutput && this.props.ide.isPlaying) || this.props.ide.isTextOutputPlaying) { + if (((this.props.preferences.textOutput || this.props.preferences.gridOutput || this.props.preferences.soundOutput) && this.props.ide.isPlaying) || this.props.ide.isAccessibleOutputPlaying) { return ( - ); } @@ -362,9 +368,13 @@ class IDEView extends React.Component { files={this.props.files} content={this.props.selectedFile.content} isPlaying={this.props.ide.isPlaying} - isTextOutputPlaying={this.props.ide.isTextOutputPlaying} + isAccessibleOutputPlaying={this.props.ide.isAccessibleOutputPlaying} textOutput={this.props.preferences.textOutput} + gridOutput={this.props.preferences.gridOutput} + soundOutput={this.props.preferences.soundOutput} setTextOutput={this.props.setTextOutput} + setGridOutput={this.props.setGridOutput} + setSoundOutput={this.props.setSoundOutput} dispatchConsoleEvent={this.props.dispatchConsoleEvent} autorefresh={this.props.preferences.autorefresh} previewIsRefreshing={this.props.ide.previewIsRefreshing} @@ -494,7 +504,7 @@ IDEView.propTypes = { saveProject: PropTypes.func.isRequired, ide: PropTypes.shape({ isPlaying: PropTypes.bool.isRequired, - isTextOutputPlaying: PropTypes.bool.isRequired, + isAccessibleOutputPlaying: PropTypes.bool.isRequired, consoleEvent: PropTypes.array, modalIsVisible: PropTypes.bool.isRequired, sidebarIsExpanded: PropTypes.bool.isRequired, @@ -516,8 +526,8 @@ IDEView.propTypes = { helpType: PropTypes.string }).isRequired, stopSketch: PropTypes.func.isRequired, - startTextOutput: PropTypes.func.isRequired, - stopTextOutput: PropTypes.func.isRequired, + startAccessibleOutput: PropTypes.func.isRequired, + stopAccessibleOutput: PropTypes.func.isRequired, project: PropTypes.shape({ id: PropTypes.string, name: PropTypes.string.isRequired, @@ -542,7 +552,9 @@ IDEView.propTypes = { isTabIndent: PropTypes.bool.isRequired, autosave: PropTypes.bool.isRequired, lintWarning: PropTypes.bool.isRequired, - textOutput: PropTypes.number.isRequired, + textOutput: PropTypes.bool.isRequired, + gridOutput: PropTypes.bool.isRequired, + soundOutput: PropTypes.bool.isRequired, theme: PropTypes.string.isRequired, autorefresh: PropTypes.bool.isRequired }).isRequired, @@ -554,6 +566,8 @@ IDEView.propTypes = { setAutosave: PropTypes.func.isRequired, setLintWarning: PropTypes.func.isRequired, setTextOutput: PropTypes.func.isRequired, + setGridOutput: PropTypes.func.isRequired, + setSoundOutput: PropTypes.func.isRequired, files: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, diff --git a/client/modules/IDE/reducers/ide.js b/client/modules/IDE/reducers/ide.js index 6ad8bf1f..30d0b553 100644 --- a/client/modules/IDE/reducers/ide.js +++ b/client/modules/IDE/reducers/ide.js @@ -2,7 +2,7 @@ import * as ActionTypes from '../../../constants'; const initialState = { isPlaying: false, - isTextOutputPlaying: false, + isAccessibleOutputPlaying: false, modalIsVisible: false, sidebarIsExpanded: false, consoleIsExpanded: true, @@ -27,10 +27,10 @@ const ide = (state = initialState, action) => { return Object.assign({}, state, { isPlaying: true }); case ActionTypes.STOP_SKETCH: return Object.assign({}, state, { isPlaying: false }); - case ActionTypes.START_TEXT_OUTPUT: - return Object.assign({}, state, { isTextOutputPlaying: true }); - case ActionTypes.STOP_TEXT_OUTPUT: - return Object.assign({}, state, { isTextOutputPlaying: false }); + case ActionTypes.START_ACCESSIBLE_OUTPUT: + return Object.assign({}, state, { isAccessibleOutputPlaying: true }); + case ActionTypes.STOP_ACCESSIBLE_OUTPUT: + return Object.assign({}, state, { isAccessibleOutputPlaying: false }); case ActionTypes.CONSOLE_EVENT: return Object.assign({}, state, { consoleEvent: action.event }); case ActionTypes.SHOW_MODAL: diff --git a/client/modules/IDE/reducers/preferences.js b/client/modules/IDE/reducers/preferences.js index f4161670..8441f042 100644 --- a/client/modules/IDE/reducers/preferences.js +++ b/client/modules/IDE/reducers/preferences.js @@ -6,7 +6,9 @@ const initialState = { isTabIndent: true, autosave: true, lintWarning: false, - textOutput: 0, + textOutput: false, + gridOutput: false, + soundOutput: false, theme: 'light', autorefresh: false }; @@ -31,6 +33,10 @@ const preferences = (state = initialState, action) => { return Object.assign({}, state, { lintWarning: action.value }); case ActionTypes.SET_TEXT_OUTPUT: return Object.assign({}, state, { textOutput: action.value }); + case ActionTypes.SET_GRID_OUTPUT: + return Object.assign({}, state, { gridOutput: action.value }); + case ActionTypes.SET_SOUND_OUTPUT: + return Object.assign({}, state, { soundOutput: action.value }); case ActionTypes.SET_PREFERENCES: return action.preferences; case ActionTypes.SET_THEME: diff --git a/client/styles/components/_preferences.scss b/client/styles/components/_preferences.scss index e684cd96..f4938d3f 100644 --- a/client/styles/components/_preferences.scss +++ b/client/styles/components/_preferences.scss @@ -126,7 +126,7 @@ } .preference__option:last-child { - padding-right: 0; + padding-right: 0; } .preference__preview-button { @@ -151,5 +151,5 @@ } .preference__option.preference__canvas:not(:last-child) { - padding-right: #{20 / $base-font-size}rem; -} \ No newline at end of file + padding-right: #{14 / $base-font-size}rem; +} diff --git a/client/styles/layout/_ide.scss b/client/styles/layout/_ide.scss index 87e3724c..cb446951 100644 --- a/client/styles/layout/_ide.scss +++ b/client/styles/layout/_ide.scss @@ -31,7 +31,7 @@ @extend %hidden-element; } -.text-output { +.accessible-output { @extend %hidden-element; } diff --git a/server/models/user.js b/server/models/user.js index cd43c7c9..0f89cb15 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -19,7 +19,9 @@ const userSchema = new Schema({ isTabIndent: { type: Boolean, default: false }, autosave: { type: Boolean, default: true }, lintWarning: { type: Boolean, default: false }, - textOutput: { type: Number, default: 0 }, + textOutput: { type: Boolean, default: false }, + gridOutput: { type: Boolean, default: false }, + soundOutput: { type: Boolean, default: false }, theme: { type: String, default: 'light' }, autorefresh: { type: Boolean, default: false } } diff --git a/static/p5-interceptor b/static/p5-interceptor index 344fedf8..5b924f34 160000 --- a/static/p5-interceptor +++ b/static/p5-interceptor @@ -1 +1 @@ -Subproject commit 344fedf8d868c62adc571bf4212c6bea3cd20247 +Subproject commit 5b924f3460886b82d72d0ab0d709f323f8b9a588