Merge pull request #48 from MathuraMG/editor
additions to make editor accessible
This commit is contained in:
commit
4171d27922
22 changed files with 277 additions and 50 deletions
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
23
client/modules/IDE/actions/editorAccessibility.js
Normal file
23
client/modules/IDE/actions/editorAccessibility.js
Normal 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
|
||||||
|
};
|
||||||
|
}
|
|
@ -79,4 +79,3 @@ export function closePreferences() {
|
||||||
type: ActionTypes.CLOSE_PREFERENCES
|
type: ActionTypes.CLOSE_PREFERENCES
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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,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qJrx4KOQjahZxWbcZgztj2c49nKmkId44S71j0c8eV9yDK6uPRzx5X18eDvjvQ6yKo9ZSS6l//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,
|
||||||
|
|
42
client/modules/IDE/components/EditorAccessibility.js
Normal file
42
client/modules/IDE/components/EditorAccessibility.js
Normal 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;
|
|
@ -12,36 +12,40 @@ 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 {
|
||||||
const modalClass = classNames({
|
componentDidMount() {
|
||||||
modal: true,
|
document.getElementById('name').focus();
|
||||||
'modal--reduced': !props.canUploadMedia
|
}
|
||||||
});
|
render() {
|
||||||
|
const modalClass = classNames({
|
||||||
return (
|
modal: true,
|
||||||
<section className={modalClass}>
|
'modal--reduced': !this.props.canUploadMedia
|
||||||
<div className="modal-content">
|
});
|
||||||
<div className="modal__header">
|
return (
|
||||||
<h2 className="modal__title">Add File</h2>
|
<section className={modalClass}>
|
||||||
<button className="modal__exit-button" onClick={props.closeModal}>
|
<div className="modal-content">
|
||||||
<InlineSVG src={exitUrl} alt="Close New File Modal" />
|
<div className="modal__header">
|
||||||
</button>
|
<h2 className="modal__title">Add File</h2>
|
||||||
|
<button className="modal__exit-button" onClick={this.props.closeModal}>
|
||||||
|
<InlineSVG src={exitUrl} alt="Close New File Modal" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<NewFileForm {...this.props} />
|
||||||
|
{(() => {
|
||||||
|
if (this.props.canUploadMedia) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p className="modal__divider">OR</p>
|
||||||
|
<FileUploader />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
<NewFileForm {...props} />
|
</section>
|
||||||
{(() => {
|
);
|
||||||
if (props.canUploadMedia) {
|
}
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<p className="modal__divider">OR</p>
|
|
||||||
<FileUploader />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
})()}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NewFileModal.propTypes = {
|
NewFileModal.propTypes = {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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={() => {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
24
client/modules/IDE/reducers/editorAccessibility.js
Normal file
24
client/modules/IDE/reducers/editorAccessibility.js
Normal 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;
|
|
@ -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:
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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%;
|
||||||
|
|
|
@ -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 });
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue