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
|
||||
</a>
|
||||
</li>
|
||||
<li className="nav__item" onClick={props.cloneProject}>
|
||||
<a className="nav__clone">
|
||||
<li className="nav__item">
|
||||
<a className="nav__clone" onClick={props.cloneProject}>
|
||||
Duplicate
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -44,6 +44,10 @@ export const CONSOLE_EVENT = 'CONSOLE_EVENT';
|
|||
export const EXPAND_CONSOLE = 'EXPAND_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 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 SET_AUTOSAVE = 'SET_AUTOSAVE';
|
||||
export const SET_LINT_WARNING = 'SET_LINT_WARNING';
|
||||
export const SET_PREFERENCES = 'SET_PREFERENCES';
|
||||
|
||||
// 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
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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 (
|
||||
<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">
|
||||
<h2 className="preview-console__header-title">console</h2>
|
||||
<button className="preview-console__collapse" onClick={this.props.collapseConsole} aria-label="collapse console">
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import EditorAccessibility from '../components/EditorAccessibility';
|
||||
import CodeMirror from 'codemirror';
|
||||
import 'codemirror/mode/javascript/javascript';
|
||||
import 'codemirror/mode/css/css';
|
||||
|
@ -10,6 +11,7 @@ import 'codemirror/addon/lint/css-lint';
|
|||
import 'codemirror/addon/lint/html-lint';
|
||||
import 'codemirror/addon/comment/comment';
|
||||
import 'codemirror/keymap/sublime';
|
||||
import 'codemirror/addon/search/jump-to-line';
|
||||
import { JSHINT } from 'jshint';
|
||||
window.JSHINT = JSHINT;
|
||||
import { CSSLint } from 'csslint';
|
||||
|
@ -22,6 +24,8 @@ import { debounce } from 'throttle-debounce';
|
|||
class Editor extends React.Component {
|
||||
|
||||
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
|
||||
theme: 'p5-widget',
|
||||
value: this.props.file.content,
|
||||
|
@ -31,12 +35,27 @@ class Editor extends React.Component {
|
|||
mode: 'javascript',
|
||||
lineWrapping: true,
|
||||
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.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.props.updateFileContent('sketch.js', this._cm.getValue());
|
||||
// throttle(1000, () => console.log('debounce is working!'));
|
||||
|
@ -81,11 +100,26 @@ class Editor extends React.Component {
|
|||
_cm: CodeMirror.Editor
|
||||
|
||||
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 = {
|
||||
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,
|
||||
isTabIndent: PropTypes.bool.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,24 +12,27 @@ import FileUploader from './FileUploader';
|
|||
// At some point this will probably be generalized to a generic modal
|
||||
// in which you can insert different content
|
||||
// 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({
|
||||
modal: true,
|
||||
'modal--reduced': !props.canUploadMedia
|
||||
'modal--reduced': !this.props.canUploadMedia
|
||||
});
|
||||
|
||||
return (
|
||||
<section className={modalClass}>
|
||||
<div className="modal-content">
|
||||
<div className="modal__header">
|
||||
<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" />
|
||||
</button>
|
||||
</div>
|
||||
<NewFileForm {...props} />
|
||||
<NewFileForm {...this.props} />
|
||||
{(() => {
|
||||
if (props.canUploadMedia) {
|
||||
if (this.props.canUploadMedia) {
|
||||
return (
|
||||
<div>
|
||||
<p className="modal__divider">OR</p>
|
||||
|
@ -43,6 +46,7 @@ function NewFileModal(props) {
|
|||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NewFileModal.propTypes = {
|
||||
closeModal: PropTypes.func.isRequired,
|
||||
|
|
|
@ -28,6 +28,11 @@ class Preferences extends React.Component {
|
|||
this.props.setAutosave(value);
|
||||
}
|
||||
|
||||
handleLintWarning(event) {
|
||||
const value = event.target.value === 'true';
|
||||
this.props.setLintWarning(value);
|
||||
}
|
||||
|
||||
render() {
|
||||
const preferencesContainerClass = classNames({
|
||||
preferences: true,
|
||||
|
@ -49,6 +54,14 @@ class Preferences extends React.Component {
|
|||
preference__option: true,
|
||||
'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 (
|
||||
<section className={preferencesContainerClass} tabIndex="0" title="preference-menu">
|
||||
<div className="preferences__heading">
|
||||
|
@ -77,6 +90,7 @@ class Preferences extends React.Component {
|
|||
className="preference__value"
|
||||
aria-live="status"
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
role="status"
|
||||
value={this.props.fontSize}
|
||||
onChange={this.handleUpdateFont}
|
||||
|
@ -106,6 +120,7 @@ class Preferences extends React.Component {
|
|||
className="preference__value"
|
||||
aria-live="status"
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
role="status"
|
||||
value={this.props.indentationAmount}
|
||||
onChange={this.handleUpdateIndentation}
|
||||
|
@ -139,6 +154,21 @@ class Preferences extends React.Component {
|
|||
>Off</button>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
@ -155,7 +185,9 @@ Preferences.propTypes = {
|
|||
isTabIndent: PropTypes.bool.isRequired,
|
||||
setFontSize: PropTypes.func.isRequired,
|
||||
autosave: PropTypes.bool.isRequired,
|
||||
setAutosave: PropTypes.func.isRequired
|
||||
setAutosave: PropTypes.func.isRequired,
|
||||
lintWarning: PropTypes.bool.isRequired,
|
||||
setLintWarning: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default Preferences;
|
||||
|
|
|
@ -33,7 +33,7 @@ const hijackConsoleScript = `<script>
|
|||
});
|
||||
|
||||
// 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 substring = "script error";
|
||||
var data = {};
|
||||
|
@ -41,7 +41,7 @@ const hijackConsoleScript = `<script>
|
|||
if (string.indexOf(substring) > -1){
|
||||
data = 'Script Error: See Browser Console for Detail';
|
||||
} else {
|
||||
data = msg + ' Line: ' + lineNo + 'column: ' + columnNo;
|
||||
data = msg + ' Line: ' + lineNumber + 'column: ' + columnNo;
|
||||
}
|
||||
window.parent.postMessage({
|
||||
method: 'error',
|
||||
|
@ -161,7 +161,8 @@ class PreviewFrame extends React.Component {
|
|||
return (
|
||||
<iframe
|
||||
className="preview-frame"
|
||||
role="region"
|
||||
aria-label="sketch output"
|
||||
role="main"
|
||||
tabIndex="0"
|
||||
frameBorder="0"
|
||||
title="sketch output"
|
||||
|
|
|
@ -49,7 +49,7 @@ class Sidebar extends React.Component {
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="sidebar__file-list">
|
||||
<ul className="sidebar__file-list" title="project files">
|
||||
{this.props.files.map((file, fileIndex) =>
|
||||
<SidebarItem
|
||||
key={file.id}
|
||||
|
|
|
@ -58,14 +58,15 @@ class SidebarItem extends React.Component {
|
|||
}}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
/>
|
||||
<a
|
||||
<button
|
||||
className="sidebar__file-item-show-options"
|
||||
aria-label="view file options"
|
||||
onClick={() => this.props.showFileOptions(this.props.file.id)}
|
||||
>
|
||||
<InlineSVG src={downArrowUrl} />
|
||||
</a>
|
||||
</button>
|
||||
<div ref="fileOptions" className="sidebar__file-item-options">
|
||||
<ul>
|
||||
<ul title="file options">
|
||||
<li>
|
||||
<a
|
||||
onClick={() => {
|
||||
|
|
|
@ -12,6 +12,7 @@ import { connect } from 'react-redux';
|
|||
import * as FileActions from '../actions/files';
|
||||
import * as IDEActions from '../actions/ide';
|
||||
import * as ProjectActions from '../actions/project';
|
||||
import * as EditorAccessibilityActions from '../actions/editorAccessibility';
|
||||
import * as PreferencesActions from '../actions/preferences';
|
||||
import { getFile, getHTMLFile, getJSFiles, getCSSFiles, setSelectedFile } from '../reducers/files';
|
||||
import SplitPane from 'react-split-pane';
|
||||
|
@ -130,6 +131,8 @@ class IDEView extends React.Component {
|
|||
setFontSize={this.props.setFontSize}
|
||||
autosave={this.props.preferences.autosave}
|
||||
setAutosave={this.props.setAutosave}
|
||||
lintWarning={this.props.preferences.lintWarning}
|
||||
setLintWarning={this.props.setLintWarning}
|
||||
/>
|
||||
<div className="editor-preview-container">
|
||||
<SplitPane
|
||||
|
@ -170,12 +173,19 @@ class IDEView extends React.Component {
|
|||
allowResize={this.props.ide.consoleIsExpanded}
|
||||
>
|
||||
<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}
|
||||
updateFileContent={this.props.updateFileContent}
|
||||
fontSize={this.props.preferences.fontSize}
|
||||
indentationAmount={this.props.preferences.indentationAmount}
|
||||
isTabIndent={this.props.preferences.isTabIndent}
|
||||
files={this.props.files}
|
||||
lintMessages={this.props.editorAccessibility.lintMessages}
|
||||
lineNumber={this.props.editorAccessibility.lineNumber}
|
||||
/>
|
||||
<Console
|
||||
consoleEvent={this.props.ide.consoleEvent}
|
||||
|
@ -252,11 +262,19 @@ IDEView.propTypes = {
|
|||
}).isRequired,
|
||||
setProjectName: 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({
|
||||
fontSize: PropTypes.number.isRequired,
|
||||
indentationAmount: PropTypes.number.isRequired,
|
||||
isTabIndent: PropTypes.bool.isRequired,
|
||||
autosave: PropTypes.bool.isRequired
|
||||
autosave: PropTypes.bool.isRequired,
|
||||
lintWarning: PropTypes.bool.isRequired
|
||||
}).isRequired,
|
||||
closePreferences: PropTypes.func.isRequired,
|
||||
setFontSize: PropTypes.func.isRequired,
|
||||
|
@ -264,6 +282,7 @@ IDEView.propTypes = {
|
|||
indentWithTab: PropTypes.func.isRequired,
|
||||
indentWithSpace: PropTypes.func.isRequired,
|
||||
setAutosave: PropTypes.func.isRequired,
|
||||
setLintWarning: PropTypes.func.isRequired,
|
||||
files: PropTypes.array.isRequired,
|
||||
updateFileContent: PropTypes.func.isRequired,
|
||||
selectedFile: PropTypes.shape({
|
||||
|
@ -300,6 +319,7 @@ function mapStateToProps(state) {
|
|||
cssFiles: getCSSFiles(state.files),
|
||||
ide: state.ide,
|
||||
preferences: state.preferences,
|
||||
editorAccessibility: state.editorAccessibility,
|
||||
user: state.user,
|
||||
project: state.project
|
||||
};
|
||||
|
@ -307,6 +327,7 @@ function mapStateToProps(state) {
|
|||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators(Object.assign({},
|
||||
EditorAccessibilityActions,
|
||||
FileActions,
|
||||
ProjectActions,
|
||||
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,
|
||||
indentationAmount: 2,
|
||||
isTabIndent: true,
|
||||
autosave: true
|
||||
autosave: true,
|
||||
lintWarning: false
|
||||
};
|
||||
|
||||
const preferences = (state = initialState, action) => {
|
||||
|
@ -23,6 +24,8 @@ const preferences = (state = initialState, action) => {
|
|||
});
|
||||
case ActionTypes.SET_AUTOSAVE:
|
||||
return Object.assign({}, state, { autosave: action.value });
|
||||
case ActionTypes.SET_LINT_WARNING:
|
||||
return Object.assign({}, state, { lintWarning: action.value });
|
||||
case ActionTypes.SET_PREFERENCES:
|
||||
return action.preferences;
|
||||
default:
|
||||
|
|
|
@ -3,6 +3,7 @@ import files from './modules/IDE/reducers/files';
|
|||
import ide from './modules/IDE/reducers/ide';
|
||||
import preferences from './modules/IDE/reducers/preferences';
|
||||
import project from './modules/IDE/reducers/project';
|
||||
import editorAccessibility from './modules/IDE/reducers/editorAccessibility';
|
||||
import user from './modules/User/reducers';
|
||||
import sketches from './modules/Sketch/reducers';
|
||||
import { reducer as form } from 'redux-form';
|
||||
|
@ -14,7 +15,8 @@ const rootReducer = combineReducers({
|
|||
preferences,
|
||||
user,
|
||||
project,
|
||||
sketches
|
||||
sketches,
|
||||
editorAccessibility
|
||||
});
|
||||
|
||||
export default rootReducer;
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
color: $light-primary-text-color;
|
||||
background-color: $light-modal-button-background-color;
|
||||
padding: 0;
|
||||
margin-bottom: #{28 / $base-font-size}rem;
|
||||
line-height: #{50 / $base-font-size}rem;
|
||||
& g {
|
||||
fill: $light-primary-text-color;
|
||||
|
@ -115,3 +116,12 @@
|
|||
box-shadow: 0 12px 12px $light-shadow-color;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
%hidden-element {
|
||||
position:absolute;
|
||||
left:-10000px;
|
||||
top:auto;
|
||||
width:1px;
|
||||
height:1px;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
.preference {
|
||||
display: flex;
|
||||
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;
|
||||
}
|
||||
|
@ -89,3 +89,6 @@
|
|||
width: #{70 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.preference--hidden {
|
||||
@extend %hidden-element;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,10 @@
|
|||
position: absolute;
|
||||
}
|
||||
|
||||
.editor-accessibility {
|
||||
@extend %hidden-element;
|
||||
}
|
||||
|
||||
.preview-frame {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
|
|
@ -13,7 +13,8 @@ const userSchema = new Schema({
|
|||
fontSize: { type: Number, default: 18 },
|
||||
indentationAmount: { type: Number, default: 2 },
|
||||
isTabIndent: { type: Boolean, default: false },
|
||||
autosave: { type: Boolean, default: true }
|
||||
autosave: { type: Boolean, default: true },
|
||||
lintWarning: { type: Boolean, default: false }
|
||||
}
|
||||
}, { timestamps: true });
|
||||
|
||||
|
|
Loading…
Reference in a new issue