Merge pull request #48 from MathuraMG/editor

additions to make editor accessible
This commit is contained in:
Cassie Tarakajian 2016-08-15 11:35:00 -04:00 committed by GitHub
commit 4171d27922
22 changed files with 277 additions and 50 deletions

View file

@ -21,8 +21,8 @@ function Nav(props) {
Save Save
</a> </a>
</li> </li>
<li className="nav__item" onClick={props.cloneProject}> <li className="nav__item">
<a className="nav__clone"> <a className="nav__clone" onClick={props.cloneProject}>
Duplicate Duplicate
</a> </a>
</li> </li>

View file

@ -44,6 +44,10 @@ export const CONSOLE_EVENT = 'CONSOLE_EVENT';
export const EXPAND_CONSOLE = 'EXPAND_CONSOLE'; export const EXPAND_CONSOLE = 'EXPAND_CONSOLE';
export const COLLAPSE_CONSOLE = 'COLLAPSE_CONSOLE'; export const COLLAPSE_CONSOLE = 'COLLAPSE_CONSOLE';
export const UPDATE_LINT_MESSAGE = 'UPDATE_LINT_MESSAGE';
export const CLEAR_LINT_MESSAGE = 'CLEAR_LINT_MESSAGE';
export const UPDATE_LINENUMBER = 'UPDATE_LINENUMBER';
export const SHOW_FILE_OPTIONS = 'SHOW_FILE_OPTIONS'; export const SHOW_FILE_OPTIONS = 'SHOW_FILE_OPTIONS';
export const HIDE_FILE_OPTIONS = 'HIDE_FILE_OPTIONS'; export const HIDE_FILE_OPTIONS = 'HIDE_FILE_OPTIONS';
@ -53,6 +57,7 @@ export const SHOW_EDIT_FILE_NAME = 'SHOW_EDIT_FILE_NAME';
export const HIDE_EDIT_FILE_NAME = 'HIDE_EDIT_FILE_NAME'; export const HIDE_EDIT_FILE_NAME = 'HIDE_EDIT_FILE_NAME';
export const SET_AUTOSAVE = 'SET_AUTOSAVE'; export const SET_AUTOSAVE = 'SET_AUTOSAVE';
export const SET_LINT_WARNING = 'SET_LINT_WARNING';
export const SET_PREFERENCES = 'SET_PREFERENCES'; export const SET_PREFERENCES = 'SET_PREFERENCES';
// eventually, handle errors more specifically and better // eventually, handle errors more specifically and better

View file

@ -0,0 +1,23 @@
import * as ActionTypes from '../../../constants';
export function updateLintMessage(severity, line, message) {
return {
type: ActionTypes.UPDATE_LINT_MESSAGE,
severity,
line,
message
};
}
export function clearLintMessage() {
return {
type: ActionTypes.CLEAR_LINT_MESSAGE
};
}
export function updateLineNumber(lineNumber) {
return {
type: ActionTypes.UPDATE_LINENUMBER,
lineNumber
};
}

View file

@ -79,4 +79,3 @@ export function closePreferences() {
type: ActionTypes.CLOSE_PREFERENCES type: ActionTypes.CLOSE_PREFERENCES
}; };
} }

View file

@ -100,3 +100,21 @@ export function setAutosave(value) {
} }
}; };
} }
export function setLintWarning(value) {
return (dispatch, getState) => {
dispatch({
type: ActionTypes.SET_LINT_WARNING,
value
});
const state = getState();
if (state.user.authenticated) {
const formParams = {
preferences: {
lintWarning: value
}
};
updatePreferences(formParams, dispatch);
}
};
}

View file

@ -55,7 +55,7 @@ class Console extends React.Component {
}); });
return ( return (
<div ref="console" className={consoleClass} role="region" tabIndex="0" title="console"> <div ref="console" className={consoleClass} role="main" tabIndex="0" title="console">
<div className="preview-console__header"> <div className="preview-console__header">
<h2 className="preview-console__header-title">console</h2> <h2 className="preview-console__header-title">console</h2>
<button className="preview-console__collapse" onClick={this.props.collapseConsole} aria-label="collapse console"> <button className="preview-console__collapse" onClick={this.props.collapseConsole} aria-label="collapse console">

View file

@ -1,4 +1,5 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import EditorAccessibility from '../components/EditorAccessibility';
import CodeMirror from 'codemirror'; import CodeMirror from 'codemirror';
import 'codemirror/mode/javascript/javascript'; import 'codemirror/mode/javascript/javascript';
import 'codemirror/mode/css/css'; import 'codemirror/mode/css/css';
@ -10,6 +11,7 @@ import 'codemirror/addon/lint/css-lint';
import 'codemirror/addon/lint/html-lint'; import 'codemirror/addon/lint/html-lint';
import 'codemirror/addon/comment/comment'; import 'codemirror/addon/comment/comment';
import 'codemirror/keymap/sublime'; import 'codemirror/keymap/sublime';
import 'codemirror/addon/search/jump-to-line';
import { JSHINT } from 'jshint'; import { JSHINT } from 'jshint';
window.JSHINT = JSHINT; window.JSHINT = JSHINT;
import { CSSLint } from 'csslint'; import { CSSLint } from 'csslint';
@ -22,6 +24,8 @@ import { debounce } from 'throttle-debounce';
class Editor extends React.Component { class Editor extends React.Component {
componentDidMount() { componentDidMount() {
// TODO: replace with wav file and loader
this.beep = new Audio('data:audio/wav;base64,//uQRAAAAWMSLwUIYAAsYkXgoQwAEaYLWfkWgAI0wWs/ItAAAGDgYtAgAyN+QWaAAihwMWm4G8QQRDiMcCBcH3Cc+CDv/7xA4Tvh9Rz/y8QADBwMWgQAZG/ILNAARQ4GLTcDeIIIhxGOBAuD7hOfBB3/94gcJ3w+o5/5eIAIAAAVwWgQAVQ2ORaIQwEMAJiDg95G4nQL7mQVWI6GwRcfsZAcsKkJvxgxEjzFUgfHoSQ9Qq7KNwqHwuB13MA4a1q/DmBrHgPcmjiGoh//EwC5nGPEmS4RcfkVKOhJf+WOgoxJclFz3kgn//dBA+ya1GhurNn8zb//9NNutNuhz31f////9vt///z+IdAEAAAK4LQIAKobHItEIYCGAExBwe8jcToF9zIKrEdDYIuP2MgOWFSE34wYiR5iqQPj0JIeoVdlG4VD4XA67mAcNa1fhzA1jwHuTRxDUQ//iYBczjHiTJcIuPyKlHQkv/LHQUYkuSi57yQT//uggfZNajQ3Vmz+Zt//+mm3Wm3Q576v////+32///5/EOgAAADVghQAAAAA//uQZAUAB1WI0PZugAAAAAoQwAAAEk3nRd2qAAAAACiDgAAAAAAABCqEEQRLCgwpBGMlJkIz8jKhGvj4k6jzRnqasNKIeoh5gI7BJaC1A1AoNBjJgbyApVS4IDlZgDU5WUAxEKDNmmALHzZp0Fkz1FMTmGFl1FMEyodIavcCAUHDWrKAIA4aa2oCgILEBupZgHvAhEBcZ6joQBxS76AgccrFlczBvKLC0QI2cBoCFvfTDAo7eoOQInqDPBtvrDEZBNYN5xwNwxQRfw8ZQ5wQVLvO8OYU+mHvFLlDh05Mdg7BT6YrRPpCBznMB2r//xKJjyyOh+cImr2/4doscwD6neZjuZR4AgAABYAAAABy1xcdQtxYBYYZdifkUDgzzXaXn98Z0oi9ILU5mBjFANmRwlVJ3/6jYDAmxaiDG3/6xjQQCCKkRb/6kg/wW+kSJ5//rLobkLSiKmqP/0ikJuDaSaSf/6JiLYLEYnW/+kXg1WRVJL/9EmQ1YZIsv/6Qzwy5qk7/+tEU0nkls3/zIUMPKNX/6yZLf+kFgAfgGyLFAUwY//uQZAUABcd5UiNPVXAAAApAAAAAE0VZQKw9ISAAACgAAAAAVQIygIElVrFkBS+Jhi+EAuu+lKAkYUEIsmEAEoMeDmCETMvfSHTGkF5RWH7kz/ESHWPAq/kcCRhqBtMdokPdM7vil7RG98A2sc7zO6ZvTdM7pmOUAZTnJW+NXxqmd41dqJ6mLTXxrPpnV8avaIf5SvL7pndPvPpndJR9Kuu8fePvuiuhorgWjp7Mf/PRjxcFCPDkW31srioCExivv9lcwKEaHsf/7ow2Fl1T/9RkXgEhYElAoCLFtMArxwivDJJ+bR1HTKJdlEoTELCIqgEwVGSQ+hIm0NbK8WXcTEI0UPoa2NbG4y2K00JEWbZavJXkYaqo9CRHS55FcZTjKEk3NKoCYUnSQ0rWxrZbFKbKIhOKPZe1cJKzZSaQrIyULHDZmV5K4xySsDRKWOruanGtjLJXFEmwaIbDLX0hIPBUQPVFVkQkDoUNfSoDgQGKPekoxeGzA4DUvnn4bxzcZrtJyipKfPNy5w+9lnXwgqsiyHNeSVpemw4bWb9psYeq//uQZBoABQt4yMVxYAIAAAkQoAAAHvYpL5m6AAgAACXDAAAAD59jblTirQe9upFsmZbpMudy7Lz1X1DYsxOOSWpfPqNX2WqktK0DMvuGwlbNj44TleLPQ+Gsfb+GOWOKJoIrWb3cIMeeON6lz2umTqMXV8Mj30yWPpjoSa9ujK8SyeJP5y5mOW1D6hvLepeveEAEDo0mgCRClOEgANv3B9a6fikgUSu/DmAMATrGx7nng5p5iimPNZsfQLYB2sDLIkzRKZOHGAaUyDcpFBSLG9MCQALgAIgQs2YunOszLSAyQYPVC2YdGGeHD2dTdJk1pAHGAWDjnkcLKFymS3RQZTInzySoBwMG0QueC3gMsCEYxUqlrcxK6k1LQQcsmyYeQPdC2YfuGPASCBkcVMQQqpVJshui1tkXQJQV0OXGAZMXSOEEBRirXbVRQW7ugq7IM7rPWSZyDlM3IuNEkxzCOJ0ny2ThNkyRai1b6ev//3dzNGzNb//4uAvHT5sURcZCFcuKLhOFs8mLAAEAt4UWAAIABAAAAAB4qbHo0tIjVkUU//uQZAwABfSFz3ZqQAAAAAngwAAAE1HjMp2qAAAAACZDgAAAD5UkTE1UgZEUExqYynN1qZvqIOREEFmBcJQkwdxiFtw0qEOkGYfRDifBui9MQg4QAHAqWtAWHoCxu1Yf4VfWLPIM2mHDFsbQEVGwyqQoQcwnfHeIkNt9YnkiaS1oizycqJrx4KOQjahZxWbcZgztj2c49nKmkId44S71j0c8eV9yDK6uPRzx5X18eDvjvQ6yKo9ZSS6l//8elePK/Lf//IInrOF/FvDoADYAGBMGb7FtErm5MXMlmPAJQVgWta7Zx2go+8xJ0UiCb8LHHdftWyLJE0QIAIsI+UbXu67dZMjmgDGCGl1H+vpF4NSDckSIkk7Vd+sxEhBQMRU8j/12UIRhzSaUdQ+rQU5kGeFxm+hb1oh6pWWmv3uvmReDl0UnvtapVaIzo1jZbf/pD6ElLqSX+rUmOQNpJFa/r+sa4e/pBlAABoAAAAA3CUgShLdGIxsY7AUABPRrgCABdDuQ5GC7DqPQCgbbJUAoRSUj+NIEig0YfyWUho1VBBBA//uQZB4ABZx5zfMakeAAAAmwAAAAF5F3P0w9GtAAACfAAAAAwLhMDmAYWMgVEG1U0FIGCBgXBXAtfMH10000EEEEEECUBYln03TTTdNBDZopopYvrTTdNa325mImNg3TTPV9q3pmY0xoO6bv3r00y+IDGid/9aaaZTGMuj9mpu9Mpio1dXrr5HERTZSmqU36A3CumzN/9Robv/Xx4v9ijkSRSNLQhAWumap82WRSBUqXStV/YcS+XVLnSS+WLDroqArFkMEsAS+eWmrUzrO0oEmE40RlMZ5+ODIkAyKAGUwZ3mVKmcamcJnMW26MRPgUw6j+LkhyHGVGYjSUUKNpuJUQoOIAyDvEyG8S5yfK6dhZc0Tx1KI/gviKL6qvvFs1+bWtaz58uUNnryq6kt5RzOCkPWlVqVX2a/EEBUdU1KrXLf40GoiiFXK///qpoiDXrOgqDR38JB0bw7SoL+ZB9o1RCkQjQ2CBYZKd/+VJxZRRZlqSkKiws0WFxUyCwsKiMy7hUVFhIaCrNQsKkTIsLivwKKigsj8XYlwt/WKi2N4d//uQRCSAAjURNIHpMZBGYiaQPSYyAAABLAAAAAAAACWAAAAApUF/Mg+0aohSIRobBAsMlO//Kk4soosy1JSFRYWaLC4qZBYWFRGZdwqKiwkNBVmoWFSJkWFxX4FFRQWR+LsS4W/rFRb/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////VEFHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU291bmRib3kuZGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMjAwNGh0dHA6Ly93d3cuc291bmRib3kuZGUAAAAAAAAAACU='); // eslint-disable-line
this._cm = CodeMirror(this.refs.container, { // eslint-disable-line this._cm = CodeMirror(this.refs.container, { // eslint-disable-line
theme: 'p5-widget', theme: 'p5-widget',
value: this.props.file.content, value: this.props.file.content,
@ -31,12 +35,27 @@ class Editor extends React.Component {
mode: 'javascript', mode: 'javascript',
lineWrapping: true, lineWrapping: true,
gutters: ['CodeMirror-lint-markers'], gutters: ['CodeMirror-lint-markers'],
lint: true, keyMap: 'sublime',
keyMap: 'sublime' lint: {
onUpdateLinting: debounce(2000, (annotations) => {
this.props.clearLintMessage();
annotations.forEach((x) => {
if (x.from.line > -1) {
this.props.updateLintMessage(x.severity, (x.from.line + 1), x.message);
}
});
if (this.props.lintMessages.length > 0 && this.props.lintWarning) {
this.beep.play();
}
})
}
}); });
this._cm.on('change', debounce(200, () => { this._cm.on('change', debounce(200, () => {
this.props.updateFileContent(this.props.file.name, this._cm.getValue()); this.props.updateFileContent(this.props.file.name, this._cm.getValue());
})); }));
this._cm.on('keyup', () => {
this.props.updateLineNumber(parseInt((this._cm.getCursor().line) + 1, 10));
});
// this._cm.on('change', () => { // eslint-disable-line // this._cm.on('change', () => { // eslint-disable-line
// // this.props.updateFileContent('sketch.js', this._cm.getValue()); // // this.props.updateFileContent('sketch.js', this._cm.getValue());
// throttle(1000, () => console.log('debounce is working!')); // throttle(1000, () => console.log('debounce is working!'));
@ -81,11 +100,26 @@ class Editor extends React.Component {
_cm: CodeMirror.Editor _cm: CodeMirror.Editor
render() { render() {
return <div ref="container" className="editor-holder" tabIndex="0" title="code editor" role="region"></div>; return (
<div>
<div ref="container" className="editor-holder" tabIndex="0" title="code editor" role="main">
</div>
<EditorAccessibility
lintMessages={this.props.lintMessages}
lineNumber={this.props.lineNumber}
/>
</div>
);
} }
} }
Editor.propTypes = { Editor.propTypes = {
lintWarning: PropTypes.bool.isRequired,
lineNumber: PropTypes.string.isRequired,
lintMessages: PropTypes.array.isRequired,
updateLintMessage: PropTypes.func.isRequired,
clearLintMessage: PropTypes.func.isRequired,
updateLineNumber: PropTypes.func.isRequired,
indentationAmount: PropTypes.number.isRequired, indentationAmount: PropTypes.number.isRequired,
isTabIndent: PropTypes.bool.isRequired, isTabIndent: PropTypes.bool.isRequired,
updateFileContent: PropTypes.func.isRequired, updateFileContent: PropTypes.func.isRequired,

View file

@ -0,0 +1,42 @@
import React, { PropTypes } from 'react';
class EditorAccessibility extends React.Component {
componentDidMount() {
}
render() {
let messages = [];
if (this.props.lintMessages.length > 0) {
for (let i = 0; i < this.props.lintMessages.length; i++) {
messages.push(
<li>
{this.props.lintMessages[i].severity} in line
{this.props.lintMessages[i].line} :
{this.props.lintMessages[i].message}
</li>
);
}
} else {
messages.push(
<p tabIndex="0"> There are no lint messages </p>
);
}
return (
<div className="editor-accessibility">
<ul className="editor-lintmessages" title="lint messages">
{messages}
</ul>
<p> Current line
<span className="editor-linenumber" aria-live="polite" aria-atomic="true" id="current-line"> {this.props.lineNumber} </span>
</p>
</div>
);
}
}
EditorAccessibility.propTypes = {
lintMessages: PropTypes.array.isRequired,
lineNumber: PropTypes.string.isRequired,
};
export default EditorAccessibility;

View file

@ -12,24 +12,27 @@ import FileUploader from './FileUploader';
// At some point this will probably be generalized to a generic modal // At some point this will probably be generalized to a generic modal
// in which you can insert different content // in which you can insert different content
// but for now, let's just make this work // but for now, let's just make this work
function NewFileModal(props) { class NewFileModal extends React.Component {
componentDidMount() {
document.getElementById('name').focus();
}
render() {
const modalClass = classNames({ const modalClass = classNames({
modal: true, modal: true,
'modal--reduced': !props.canUploadMedia 'modal--reduced': !this.props.canUploadMedia
}); });
return ( return (
<section className={modalClass}> <section className={modalClass}>
<div className="modal-content"> <div className="modal-content">
<div className="modal__header"> <div className="modal__header">
<h2 className="modal__title">Add File</h2> <h2 className="modal__title">Add File</h2>
<button className="modal__exit-button" onClick={props.closeModal}> <button className="modal__exit-button" onClick={this.props.closeModal}>
<InlineSVG src={exitUrl} alt="Close New File Modal" /> <InlineSVG src={exitUrl} alt="Close New File Modal" />
</button> </button>
</div> </div>
<NewFileForm {...props} /> <NewFileForm {...this.props} />
{(() => { {(() => {
if (props.canUploadMedia) { if (this.props.canUploadMedia) {
return ( return (
<div> <div>
<p className="modal__divider">OR</p> <p className="modal__divider">OR</p>
@ -42,6 +45,7 @@ function NewFileModal(props) {
</div> </div>
</section> </section>
); );
}
} }
NewFileModal.propTypes = { NewFileModal.propTypes = {

View file

@ -28,6 +28,11 @@ class Preferences extends React.Component {
this.props.setAutosave(value); this.props.setAutosave(value);
} }
handleLintWarning(event) {
const value = event.target.value === 'true';
this.props.setLintWarning(value);
}
render() { render() {
const preferencesContainerClass = classNames({ const preferencesContainerClass = classNames({
preferences: true, preferences: true,
@ -49,6 +54,14 @@ class Preferences extends React.Component {
preference__option: true, preference__option: true,
'preference__option--selected': !this.props.autosave 'preference__option--selected': !this.props.autosave
}); });
let lintWarningOnClass = classNames({
preference__option: true,
'preference__option--selected': this.props.lintWarning
});
let lintWarningOffClass = classNames({
preference__option: true,
'preference__option--selected': !this.props.lintWarning
});
return ( return (
<section className={preferencesContainerClass} tabIndex="0" title="preference-menu"> <section className={preferencesContainerClass} tabIndex="0" title="preference-menu">
<div className="preferences__heading"> <div className="preferences__heading">
@ -77,6 +90,7 @@ class Preferences extends React.Component {
className="preference__value" className="preference__value"
aria-live="status" aria-live="status"
aria-live="polite" aria-live="polite"
aria-atomic="true"
role="status" role="status"
value={this.props.fontSize} value={this.props.fontSize}
onChange={this.handleUpdateFont} onChange={this.handleUpdateFont}
@ -106,6 +120,7 @@ class Preferences extends React.Component {
className="preference__value" className="preference__value"
aria-live="status" aria-live="status"
aria-live="polite" aria-live="polite"
aria-atomic="true"
role="status" role="status"
value={this.props.indentationAmount} value={this.props.indentationAmount}
onChange={this.handleUpdateIndentation} onChange={this.handleUpdateIndentation}
@ -139,6 +154,21 @@ class Preferences extends React.Component {
>Off</button> >Off</button>
</div> </div>
</div> </div>
<div className="preference">
<h4 className="preference__title">Lint Warning Sound</h4>
<div className="preference__options">
<button
className={lintWarningOnClass}
onClick={() => this.props.setLintWarning(true)}
aria-label="lint warning on"
>On</button>
<button
className={lintWarningOffClass}
onClick={() => this.props.setLintWarning(false)}
aria-label="lint warning off"
>Off</button>
</div>
</div>
</section> </section>
); );
} }
@ -155,7 +185,9 @@ Preferences.propTypes = {
isTabIndent: PropTypes.bool.isRequired, isTabIndent: PropTypes.bool.isRequired,
setFontSize: PropTypes.func.isRequired, setFontSize: PropTypes.func.isRequired,
autosave: PropTypes.bool.isRequired, autosave: PropTypes.bool.isRequired,
setAutosave: PropTypes.func.isRequired setAutosave: PropTypes.func.isRequired,
lintWarning: PropTypes.bool.isRequired,
setLintWarning: PropTypes.func.isRequired
}; };
export default Preferences; export default Preferences;

View file

@ -33,7 +33,7 @@ const hijackConsoleScript = `<script>
}); });
// catch reference errors, via http://stackoverflow.com/a/12747364/2994108 // catch reference errors, via http://stackoverflow.com/a/12747364/2994108
window.onerror = function (msg, url, lineNo, columnNo, error) { window.onerror = function (msg, url, lineNumber, columnNo, error) {
var string = msg.toLowerCase(); var string = msg.toLowerCase();
var substring = "script error"; var substring = "script error";
var data = {}; var data = {};
@ -41,7 +41,7 @@ const hijackConsoleScript = `<script>
if (string.indexOf(substring) > -1){ if (string.indexOf(substring) > -1){
data = 'Script Error: See Browser Console for Detail'; data = 'Script Error: See Browser Console for Detail';
} else { } else {
data = msg + ' Line: ' + lineNo + 'column: ' + columnNo; data = msg + ' Line: ' + lineNumber + 'column: ' + columnNo;
} }
window.parent.postMessage({ window.parent.postMessage({
method: 'error', method: 'error',
@ -161,7 +161,8 @@ class PreviewFrame extends React.Component {
return ( return (
<iframe <iframe
className="preview-frame" className="preview-frame"
role="region" aria-label="sketch output"
role="main"
tabIndex="0" tabIndex="0"
frameBorder="0" frameBorder="0"
title="sketch output" title="sketch output"

View file

@ -49,7 +49,7 @@ class Sidebar extends React.Component {
</button> </button>
</div> </div>
</div> </div>
<ul className="sidebar__file-list"> <ul className="sidebar__file-list" title="project files">
{this.props.files.map((file, fileIndex) => {this.props.files.map((file, fileIndex) =>
<SidebarItem <SidebarItem
key={file.id} key={file.id}

View file

@ -58,14 +58,15 @@ class SidebarItem extends React.Component {
}} }}
onKeyPress={this.handleKeyPress} onKeyPress={this.handleKeyPress}
/> />
<a <button
className="sidebar__file-item-show-options" className="sidebar__file-item-show-options"
aria-label="view file options"
onClick={() => this.props.showFileOptions(this.props.file.id)} onClick={() => this.props.showFileOptions(this.props.file.id)}
> >
<InlineSVG src={downArrowUrl} /> <InlineSVG src={downArrowUrl} />
</a> </button>
<div ref="fileOptions" className="sidebar__file-item-options"> <div ref="fileOptions" className="sidebar__file-item-options">
<ul> <ul title="file options">
<li> <li>
<a <a
onClick={() => { onClick={() => {

View file

@ -12,6 +12,7 @@ import { connect } from 'react-redux';
import * as FileActions from '../actions/files'; import * as FileActions from '../actions/files';
import * as IDEActions from '../actions/ide'; import * as IDEActions from '../actions/ide';
import * as ProjectActions from '../actions/project'; import * as ProjectActions from '../actions/project';
import * as EditorAccessibilityActions from '../actions/editorAccessibility';
import * as PreferencesActions from '../actions/preferences'; import * as PreferencesActions from '../actions/preferences';
import { getFile, getHTMLFile, getJSFiles, getCSSFiles, setSelectedFile } from '../reducers/files'; import { getFile, getHTMLFile, getJSFiles, getCSSFiles, setSelectedFile } from '../reducers/files';
import SplitPane from 'react-split-pane'; import SplitPane from 'react-split-pane';
@ -130,6 +131,8 @@ class IDEView extends React.Component {
setFontSize={this.props.setFontSize} setFontSize={this.props.setFontSize}
autosave={this.props.preferences.autosave} autosave={this.props.preferences.autosave}
setAutosave={this.props.setAutosave} setAutosave={this.props.setAutosave}
lintWarning={this.props.preferences.lintWarning}
setLintWarning={this.props.setLintWarning}
/> />
<div className="editor-preview-container"> <div className="editor-preview-container">
<SplitPane <SplitPane
@ -170,12 +173,19 @@ class IDEView extends React.Component {
allowResize={this.props.ide.consoleIsExpanded} allowResize={this.props.ide.consoleIsExpanded}
> >
<Editor <Editor
lintWarning={this.props.preferences.lintWarning}
lintMessages={this.props.editorAccessibility.lintMessages}
updateLineNumber={this.props.updateLineNumber}
updateLintMessage={this.props.updateLintMessage}
clearLintMessage={this.props.clearLintMessage}
file={this.props.selectedFile} file={this.props.selectedFile}
updateFileContent={this.props.updateFileContent} updateFileContent={this.props.updateFileContent}
fontSize={this.props.preferences.fontSize} fontSize={this.props.preferences.fontSize}
indentationAmount={this.props.preferences.indentationAmount} indentationAmount={this.props.preferences.indentationAmount}
isTabIndent={this.props.preferences.isTabIndent} isTabIndent={this.props.preferences.isTabIndent}
files={this.props.files} files={this.props.files}
lintMessages={this.props.editorAccessibility.lintMessages}
lineNumber={this.props.editorAccessibility.lineNumber}
/> />
<Console <Console
consoleEvent={this.props.ide.consoleEvent} consoleEvent={this.props.ide.consoleEvent}
@ -252,11 +262,19 @@ IDEView.propTypes = {
}).isRequired, }).isRequired,
setProjectName: PropTypes.func.isRequired, setProjectName: PropTypes.func.isRequired,
openPreferences: PropTypes.func.isRequired, openPreferences: PropTypes.func.isRequired,
editorAccessibility: PropTypes.shape({
lintMessages: PropTypes.array.isRequired,
lineNumber: PropTypes.string.isRequired
}).isRequired,
updateLintMessage: PropTypes.func.isRequired,
clearLintMessage: PropTypes.func.isRequired,
updateLineNumber: PropTypes.func.isRequired,
preferences: PropTypes.shape({ preferences: PropTypes.shape({
fontSize: PropTypes.number.isRequired, fontSize: PropTypes.number.isRequired,
indentationAmount: PropTypes.number.isRequired, indentationAmount: PropTypes.number.isRequired,
isTabIndent: PropTypes.bool.isRequired, isTabIndent: PropTypes.bool.isRequired,
autosave: PropTypes.bool.isRequired autosave: PropTypes.bool.isRequired,
lintWarning: PropTypes.bool.isRequired
}).isRequired, }).isRequired,
closePreferences: PropTypes.func.isRequired, closePreferences: PropTypes.func.isRequired,
setFontSize: PropTypes.func.isRequired, setFontSize: PropTypes.func.isRequired,
@ -264,6 +282,7 @@ IDEView.propTypes = {
indentWithTab: PropTypes.func.isRequired, indentWithTab: PropTypes.func.isRequired,
indentWithSpace: PropTypes.func.isRequired, indentWithSpace: PropTypes.func.isRequired,
setAutosave: PropTypes.func.isRequired, setAutosave: PropTypes.func.isRequired,
setLintWarning: PropTypes.func.isRequired,
files: PropTypes.array.isRequired, files: PropTypes.array.isRequired,
updateFileContent: PropTypes.func.isRequired, updateFileContent: PropTypes.func.isRequired,
selectedFile: PropTypes.shape({ selectedFile: PropTypes.shape({
@ -300,6 +319,7 @@ function mapStateToProps(state) {
cssFiles: getCSSFiles(state.files), cssFiles: getCSSFiles(state.files),
ide: state.ide, ide: state.ide,
preferences: state.preferences, preferences: state.preferences,
editorAccessibility: state.editorAccessibility,
user: state.user, user: state.user,
project: state.project project: state.project
}; };
@ -307,6 +327,7 @@ function mapStateToProps(state) {
function mapDispatchToProps(dispatch) { function mapDispatchToProps(dispatch) {
return bindActionCreators(Object.assign({}, return bindActionCreators(Object.assign({},
EditorAccessibilityActions,
FileActions, FileActions,
ProjectActions, ProjectActions,
IDEActions, IDEActions,

View file

@ -0,0 +1,24 @@
import * as ActionTypes from '../../../constants';
const initialState = {
lineNumber: 'line',
lintMessages: []
};
const editorAccessibility = (state = initialState, action) => {
switch (action.type) {
case ActionTypes.UPDATE_LINT_MESSAGE:
return Object.assign({}, state, {
lintMessages: state.lintMessages.concat(
{ severity: action.severity, line: action.line, message: action.message })
});
case ActionTypes.CLEAR_LINT_MESSAGE:
return Object.assign({}, state, { lintMessages: [] });
case ActionTypes.UPDATE_LINENUMBER:
return Object.assign({}, state, { lineNumber: `line ${action.lineNumber}` });
default:
return state;
}
};
export default editorAccessibility;

View file

@ -4,7 +4,8 @@ const initialState = {
fontSize: 18, fontSize: 18,
indentationAmount: 2, indentationAmount: 2,
isTabIndent: true, isTabIndent: true,
autosave: true autosave: true,
lintWarning: false
}; };
const preferences = (state = initialState, action) => { const preferences = (state = initialState, action) => {
@ -23,6 +24,8 @@ const preferences = (state = initialState, action) => {
}); });
case ActionTypes.SET_AUTOSAVE: case ActionTypes.SET_AUTOSAVE:
return Object.assign({}, state, { autosave: action.value }); return Object.assign({}, state, { autosave: action.value });
case ActionTypes.SET_LINT_WARNING:
return Object.assign({}, state, { lintWarning: action.value });
case ActionTypes.SET_PREFERENCES: case ActionTypes.SET_PREFERENCES:
return action.preferences; return action.preferences;
default: default:

View file

@ -3,6 +3,7 @@ import files from './modules/IDE/reducers/files';
import ide from './modules/IDE/reducers/ide'; import ide from './modules/IDE/reducers/ide';
import preferences from './modules/IDE/reducers/preferences'; import preferences from './modules/IDE/reducers/preferences';
import project from './modules/IDE/reducers/project'; import project from './modules/IDE/reducers/project';
import editorAccessibility from './modules/IDE/reducers/editorAccessibility';
import user from './modules/User/reducers'; import user from './modules/User/reducers';
import sketches from './modules/Sketch/reducers'; import sketches from './modules/Sketch/reducers';
import { reducer as form } from 'redux-form'; import { reducer as form } from 'redux-form';
@ -14,7 +15,8 @@ const rootReducer = combineReducers({
preferences, preferences,
user, user,
project, project,
sketches sketches,
editorAccessibility
}); });
export default rootReducer; export default rootReducer;

View file

@ -71,6 +71,7 @@
color: $light-primary-text-color; color: $light-primary-text-color;
background-color: $light-modal-button-background-color; background-color: $light-modal-button-background-color;
padding: 0; padding: 0;
margin-bottom: #{28 / $base-font-size}rem;
line-height: #{50 / $base-font-size}rem; line-height: #{50 / $base-font-size}rem;
& g { & g {
fill: $light-primary-text-color; fill: $light-primary-text-color;
@ -115,3 +116,12 @@
box-shadow: 0 12px 12px $light-shadow-color; box-shadow: 0 12px 12px $light-shadow-color;
z-index: 20; z-index: 20;
} }
%hidden-element {
position:absolute;
left:-10000px;
top:auto;
width:1px;
height:1px;
overflow:hidden;
}

View file

@ -35,7 +35,7 @@
.preference { .preference {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
padding-bottom: #{40 / $base-font-size}rem; padding-bottom: #{12 / $base-font-size}rem;
& + & { & + & {
border-top: 2px dashed $light-button-border-color; border-top: 2px dashed $light-button-border-color;
} }
@ -89,3 +89,6 @@
width: #{70 / $base-font-size}rem; width: #{70 / $base-font-size}rem;
} }
.preference--hidden {
@extend %hidden-element;
}

View file

@ -29,6 +29,10 @@
position: absolute; position: absolute;
} }
.editor-accessibility {
@extend %hidden-element;
}
.preview-frame { .preview-frame {
height: 100%; height: 100%;
width: 100%; width: 100%;

View file

@ -13,7 +13,8 @@ const userSchema = new Schema({
fontSize: { type: Number, default: 18 }, fontSize: { type: Number, default: 18 },
indentationAmount: { type: Number, default: 2 }, indentationAmount: { type: Number, default: 2 },
isTabIndent: { type: Boolean, default: false }, isTabIndent: { type: Boolean, default: false },
autosave: { type: Boolean, default: true } autosave: { type: Boolean, default: true },
lintWarning: { type: Boolean, default: false }
} }
}, { timestamps: true }); }, { timestamps: true });