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
This commit is contained in:
Mathura MG 2017-05-31 12:23:30 -07:00 committed by Cassie Tarakajian
parent 8e1a65daed
commit 82207a50d3
16 changed files with 261 additions and 122 deletions

View file

@ -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';

View file

@ -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
};
}

View file

@ -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) {
}
};
}

View file

@ -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 (
<section
className="accessible-output"
id="canvas-sub"
ref={(element) => { this.accessibleOutputModal = element; }}
tabIndex="0"
aria-label="accessible-output"
title="canvas text output"
>
{(() => { // eslint-disable-line
if (this.props.textOutput) {
return (
<TextOutput />
);
}
})()}
{(() => { // eslint-disable-line
if (this.props.gridOutput) {
return (
<GridOutput />
);
}
})()}
</section>
);
}
}
AccessibleOutput.propTypes = {
isPlaying: PropTypes.bool.isRequired,
previewIsRefreshing: PropTypes.bool.isRequired,
textOutput: PropTypes.bool.isRequired,
gridOutput: PropTypes.bool.isRequired
};
export default AccessibleOutput;

View file

@ -0,0 +1,38 @@
import React, { PropTypes } from 'react';
class GridOutput extends React.Component {
componentDidMount() {
this.GridOutputModal.focus();
}
render() {
return (
<section
id="gridOutput-content"
ref={(element) => { this.GridOutputModal = element; }}
>
<h2> Grid Output </h2>
<p
tabIndex="0"
role="main"
id="gridOutput-content-summary"
aria-label="grid output summary"
>
</p>
<table
id="gridOutput-content-table"
summary="grid output details"
>
</table>
<div
tabIndex="0"
role="main"
id="gridOutput-content-details"
aria-label="grid output details"
>
</div>
</section>
);
}
}
export default GridOutput;

View file

@ -261,50 +261,41 @@ class Preferences extends React.Component {
<div className="preference__options">
<input
type="radio"
onChange={() => 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)}
/>
<label htmlFor="text-output-on" className="preference__option preference__canvas">Plain-text</label>
<input
type="radio"
onChange={() => 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)}
/>
<label htmlFor="grid-output-on" className="preference__option preference__canvas">Table-text</label>
<label htmlFor="table-output-on" className="preference__option preference__canvas">Table-text</label>
<input
type="radio"
onChange={() => 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)}
/>
<label htmlFor="sound-output-on" className="preference__option preference__canvas">Sound</label>
<input
type="radio"
onChange={() => 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)}
/>
<label htmlFor="text-output-off" className="preference__option preference__canvas">Off</label>
</div>
</div>
</section>
@ -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,

View file

@ -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

View file

@ -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 (
<section
className="text-output"
id="canvas-sub"
ref={(element) => { this.canvasTextOutput = element; }}
tabIndex="0"
aria-label="text-output"
title="canvas text output"
id="textOutput-content"
ref={(element) => { this.TextOutputModal = element; }}
>
<h2> Output </h2>
<section id="textOutput-content">
</section>
<h2> Text Output </h2>
<p
tabIndex="0"
role="main"
@ -31,10 +19,8 @@ class TextOutput extends React.Component {
>
</p>
<table
tabIndex="0"
role="main"
id="textOutput-content-table"
aria-label="text output details"
summary="text output details"
>
</table>
<div
@ -49,9 +35,4 @@ class TextOutput extends React.Component {
}
}
TextOutput.propTypes = {
isPlaying: PropTypes.bool.isRequired,
previewIsRefreshing: PropTypes.bool.isRequired
};
export default TextOutput;

View file

@ -64,7 +64,7 @@ class Toolbar extends React.Component {
className="toolbar__play-sketch-button"
onClick={() => {
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 {
</button>
<button
className={stopButtonClass}
onClick={() => { this.props.stopTextOutput(); this.props.stopSketch(); }}
onClick={() => { this.props.stopAccessibleOutput(); this.props.stopSketch(); }}
aria-label="stop sketch"
>
<InlineSVG src={stopUrl} alt="Stop Sketch" />
@ -184,8 +184,8 @@ Toolbar.propTypes = {
isPlaying: PropTypes.bool.isRequired,
preferencesIsVisible: PropTypes.bool.isRequired,
stopSketch: PropTypes.func.isRequired,
startTextOutput: PropTypes.func.isRequired,
stopTextOutput: PropTypes.func.isRequired,
startAccessibleOutput: PropTypes.func.isRequired,
stopAccessibleOutput: PropTypes.func.isRequired,
setProjectName: PropTypes.func.isRequired,
openPreferences: PropTypes.func.isRequired,
owner: PropTypes.shape({

View file

@ -7,7 +7,7 @@ import Editor from '../components/Editor';
import Sidebar from '../components/Sidebar';
import PreviewFrame from '../components/PreviewFrame';
import Toolbar from '../components/Toolbar';
import TextOutput from '../components/TextOutput';
import AccessibleOutput from '../components/AccessibleOutput';
import Preferences from '../components/Preferences';
import NewFileModal from '../components/NewFileModal';
import NewFolderModal from '../components/NewFolderModal';
@ -165,15 +165,14 @@ class IDEView extends React.Component {
this.props.startSketchAndRefresh();
} else if (e.keyCode === 50 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac)) && e.shiftKey) {
e.preventDefault();
this.props.setTextOutput(0);
this.props.setTextOutput(false);
this.props.setGridOutput(false);
this.props.setSoundOutput(false);
} else if (e.keyCode === 49 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac)) && e.shiftKey) {
e.preventDefault();
if (this.props.preferences.textOutput === 3) {
this.props.preferences.textOutput = 1;
} else {
this.props.preferences.textOutput += 1;
}
this.props.setTextOutput(this.props.preferences.textOutput);
this.props.setTextOutput(true);
this.props.setGridOutput(true);
this.props.setSoundOutput(true);
}
}
@ -217,8 +216,8 @@ class IDEView extends React.Component {
className="Toolbar"
isPlaying={this.props.ide.isPlaying}
stopSketch={this.props.stopSketch}
startTextOutput={this.props.startTextOutput}
stopTextOutput={this.props.stopTextOutput}
startAccessibleOutput={this.props.startAccessibleOutput}
stopAccessibleOutput={this.props.stopAccessibleOutput}
projectName={this.props.project.name}
setProjectName={this.props.setProjectName}
showEditProjectName={this.props.showEditProjectName}
@ -228,6 +227,8 @@ class IDEView extends React.Component {
serveSecure={this.props.project.serveSecure}
setServeSecure={this.props.setServeSecure}
setTextOutput={this.props.setTextOutput}
setGridOutput={this.props.setGridOutput}
setSoundOutput={this.props.setSoundOutput}
owner={this.props.project.owner}
project={this.props.project}
infiniteLoop={this.props.ide.infiniteLoop}
@ -254,7 +255,10 @@ class IDEView extends React.Component {
lintWarning={this.props.preferences.lintWarning}
setLintWarning={this.props.setLintWarning}
textOutput={this.props.preferences.textOutput}
gridOutput={this.props.preferences.gridOutput}
setTextOutput={this.props.setTextOutput}
setGridOutput={this.props.setGridOutput}
setSoundOutput={this.props.setSoundOutput}
theme={this.props.preferences.theme}
setTheme={this.props.setTheme}
/>
@ -346,11 +350,13 @@ class IDEView extends React.Component {
</div>
<div>
{(() => {
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 (
<TextOutput
<AccessibleOutput
isPlaying={this.props.ide.isPlaying}
previewIsRefreshing={this.props.ide.previewIsRefreshing}
textOutput={this.props.preferences.textOutput}
gridOutput={this.props.preferences.gridOutput}
/>
);
}
@ -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,

View file

@ -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:

View file

@ -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:

View file

@ -151,5 +151,5 @@
}
.preference__option.preference__canvas:not(:last-child) {
padding-right: #{20 / $base-font-size}rem;
padding-right: #{14 / $base-font-size}rem;
}

View file

@ -31,7 +31,7 @@
@extend %hidden-element;
}
.text-output {
.accessible-output {
@extend %hidden-element;
}

View file

@ -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 }
}

@ -1 +1 @@
Subproject commit 344fedf8d868c62adc571bf4212c6bea3cd20247
Subproject commit 5b924f3460886b82d72d0ab0d709f323f8b9a588