diff --git a/.eslintrc b/.eslintrc index 17527587..6be3a63f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -37,7 +37,8 @@ "react/prefer-stateless-function": [2, { "ignorePureComponents": true }], - "class-methods-use-this": 0 + "class-methods-use-this": 0, + "react/jsx-no-bind": [2, {"allowBind": true, "allowArrowFunctions": true}] }, "plugins": [ "react", "jsx-a11y", "import" diff --git a/client/components/Nav.jsx b/client/components/Nav.jsx index 0a861e97..f19c6628 100644 --- a/client/components/Nav.jsx +++ b/client/components/Nav.jsx @@ -1,167 +1,463 @@ import React, { PropTypes } from 'react'; import { Link } from 'react-router'; +import InlineSVG from 'react-inlinesvg'; +import classNames from 'classnames'; + +import { + metaKeyName, +} from '../utils/metaKey'; + +const triangleUrl = require('../images/down-filled-triangle.svg'); +const logoUrl = require('../images/p5js-logo-small.svg'); class Nav extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + dropdownOpen: 'none' + }; + this.handleFocus = this.handleFocus.bind(this); + this.handleBlur = this.handleBlur.bind(this); + this.clearHideTimeout = this.clearHideTimeout.bind(this); + } + + setDropdown(dropdown) { + this.setState({ + dropdownOpen: dropdown + }); + } + + toggleDropdown(dropdown) { + if (this.state.dropdownOpen === 'none') { + this.setState({ + dropdownOpen: dropdown + }); + } else { + this.setState({ + dropdownOpen: 'none' + }); + } + } + + isUserOwner() { + return this.props.project.owner && this.props.project.owner.id === this.props.user.id; + } + + handleFocus(dropdown) { + this.clearHideTimeout(); + this.setDropdown(dropdown); + } + + clearHideTimeout() { + if (this.timer) { + clearTimeout(this.timer); + this.timer = null; + } + } + + handleBlur() { + this.timer = setTimeout(this.setDropdown.bind(this, 'none'), 10); + } + render() { + const navDropdownState = { + file: classNames({ + 'nav__item': true, + 'nav__item--open': this.state.dropdownOpen === 'file' + }), + edit: classNames({ + 'nav__item': true, + 'nav__item--open': this.state.dropdownOpen === 'edit' + }), + sketch: classNames({ + 'nav__item': true, + 'nav__item--open': this.state.dropdownOpen === 'sketch' + }), + help: classNames({ + 'nav__item': true, + 'nav__item--open': this.state.dropdownOpen === 'help' + }), + account: classNames({ + 'nav__item': true, + 'nav__item--open': this.state.dropdownOpen === 'account' + }) + }; return ( ); } @@ -196,14 +493,25 @@ Nav.propTypes = { showShareModal: PropTypes.func.isRequired, showErrorModal: PropTypes.func.isRequired, unsavedChanges: PropTypes.bool.isRequired, - warnIfUnsavedChanges: PropTypes.func.isRequired + warnIfUnsavedChanges: PropTypes.func.isRequired, + showKeyboardShortcutModal: PropTypes.func.isRequired, + cmController: PropTypes.shape({ + tidyCode: PropTypes.func, + showFind: PropTypes.func, + findNext: PropTypes.func, + findPrev: PropTypes.func + }), + startSketch: PropTypes.func.isRequired, + stopSketch: PropTypes.func.isRequired, + setAllAccessibleOutput: PropTypes.func.isRequired }; Nav.defaultProps = { project: { id: undefined, owner: undefined - } + }, + cmController: {} }; export default Nav; diff --git a/client/images/down-filled-triangle.svg b/client/images/down-filled-triangle.svg new file mode 100644 index 00000000..b673aafe --- /dev/null +++ b/client/images/down-filled-triangle.svg @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/client/images/p5js-logo-small.svg b/client/images/p5js-logo-small.svg new file mode 100644 index 00000000..164ef67f --- /dev/null +++ b/client/images/p5js-logo-small.svg @@ -0,0 +1,20 @@ + diff --git a/client/modules/App/components/Overlay.jsx b/client/modules/App/components/Overlay.jsx index 8951df64..aa016950 100644 --- a/client/modules/App/components/Overlay.jsx +++ b/client/modules/App/components/Overlay.jsx @@ -57,14 +57,15 @@ Overlay.propTypes = { closeOverlay: PropTypes.func, title: PropTypes.string, ariaLabel: PropTypes.string, - previousPath: PropTypes.string.isRequired + previousPath: PropTypes.string }; Overlay.defaultProps = { children: null, title: 'Modal', closeOverlay: null, - ariaLabel: 'modal' + ariaLabel: 'modal', + previousPath: '/' }; export default Overlay; diff --git a/client/modules/IDE/actions/ide.js b/client/modules/IDE/actions/ide.js index 0e8fa3ae..a8683d6a 100644 --- a/client/modules/IDE/actions/ide.js +++ b/client/modules/IDE/actions/ide.js @@ -1,12 +1,13 @@ import * as ActionTypes from '../../../constants'; +import { clearConsole } from './console'; -export function startSketch() { +export function startVisualSketch() { return { type: ActionTypes.START_SKETCH }; } -export function stopSketch() { +export function stopVisualSketch() { return { type: ActionTypes.STOP_SKETCH }; @@ -20,7 +21,7 @@ export function startRefreshSketch() { export function startSketchAndRefresh() { return (dispatch) => { - dispatch(startSketch()); + dispatch(startVisualSketch()); dispatch(startRefreshSketch()); }; } @@ -233,3 +234,26 @@ export function hideHelpModal() { type: ActionTypes.HIDE_HELP_MODAL }; } + +export function startSketch() { + return (dispatch) => { + dispatch(clearConsole()); + dispatch(startSketchAndRefresh()); + }; +} + +export function startAccessibleSketch() { + return (dispatch) => { + dispatch(clearConsole()); + dispatch(startAccessibleOutput()); + dispatch(startSketchAndRefresh()); + }; +} + +export function stopSketch() { + return (dispatch) => { + dispatch(stopAccessibleOutput()); + dispatch(stopVisualSketch()); + }; +} + diff --git a/client/modules/IDE/actions/preferences.js b/client/modules/IDE/actions/preferences.js index 4f735280..46182fa5 100644 --- a/client/modules/IDE/actions/preferences.js +++ b/client/modules/IDE/actions/preferences.js @@ -216,3 +216,12 @@ export function setAutorefresh(value) { } }; } + +export function setAllAccessibleOutput(value) { + return (dispatch) => { + dispatch(setTextOutput(value)); + dispatch(setGridOutput(value)); + dispatch(setSoundOutput(value)); + }; +} + diff --git a/client/modules/IDE/components/Editor.jsx b/client/modules/IDE/components/Editor.jsx index 2108acd6..00344e98 100644 --- a/client/modules/IDE/components/Editor.jsx +++ b/client/modules/IDE/components/Editor.jsx @@ -45,7 +45,6 @@ window.CSSLint = CSSLint; window.HTMLHint = HTMLHint; const beepUrl = require('../../../sounds/audioAlert.mp3'); -const downArrowUrl = require('../../../images/down-arrow.svg'); const unsavedChangesDotUrl = require('../../../images/unsaved-changes-dot.svg'); const rightArrowUrl = require('../../../images/right-arrow.svg'); const leftArrowUrl = require('../../../images/left-arrow.svg'); @@ -54,6 +53,9 @@ class Editor extends React.Component { constructor(props) { super(props); this.tidyCode = this.tidyCode.bind(this); + this.showFind = this.showFind.bind(this); + this.findNext = this.findNext.bind(this); + this.findPrev = this.findPrev.bind(this); } componentDidMount() { this.beep = new Audio(beepUrl); @@ -85,7 +87,8 @@ class Editor extends React.Component { options: { 'asi': true, 'eqeqeq': false, - '-W041': false + '-W041': false, + 'esversion': 6 } } }); @@ -126,6 +129,13 @@ class Editor extends React.Component { this._cm.getWrapperElement().style['font-size'] = `${this.props.fontSize}px`; this._cm.setOption('indentWithTabs', this.props.isTabIndent); this._cm.setOption('tabSize', this.props.indentationAmount); + + this.props.provideController({ + tidyCode: this.tidyCode, + showFind: this.showFind, + findNext: this.findNext, + findPrev: this.findPrev + }); } componentWillUpdate(nextProps) { @@ -165,6 +175,7 @@ class Editor extends React.Component { componentWillUnmount() { this._cm = null; + this.props.provideController(null); } getFileMode(fileName) { @@ -208,6 +219,20 @@ class Editor extends React.Component { } } + showFind() { + this._cm.execCommand('findPersistent'); + } + + findNext() { + this._cm.focus(); + this._cm.execCommand('findNext'); + } + + findPrev() { + this._cm.focus(); + this._cm.execCommand('findPrev'); + } + toggleEditorOptions() { if (this.props.editorOptionsVisible) { this.props.closeEditorOptions(); @@ -257,26 +282,6 @@ class Editor extends React.Component { isUserOwner={this.props.isUserOwner} /> - -
Use the checkbox to choose whether this sketch should be loaded using HTTPS or HTTP.
+You should choose HTTPS if you need to:
+Choose HTTP if you need to:
+Use the checkbox to choose whether this sketch should be loaded using HTTPS or HTTP.
-You should choose HTTPS if you need to:
-Choose HTTP if you need to:
-