Merge branch 'master' into zrispo-feature-code-folding

This commit is contained in:
Cassie Tarakajian 2017-10-11 15:55:55 -04:00
commit 1f86eb6711
25 changed files with 871 additions and 347 deletions

View file

@ -37,7 +37,8 @@
"react/prefer-stateless-function": [2, "react/prefer-stateless-function": [2,
{ "ignorePureComponents": true { "ignorePureComponents": true
}], }],
"class-methods-use-this": 0 "class-methods-use-this": 0,
"react/jsx-no-bind": [2, {"allowBind": true, "allowArrowFunctions": true}]
}, },
"plugins": [ "plugins": [
"react", "jsx-a11y", "import" "react", "jsx-a11y", "import"

View file

@ -1,126 +1,388 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { Link } from 'react-router'; 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 { 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() { 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 ( return (
<nav className="nav" role="navigation" title="main-navigation"> <nav className="nav" role="navigation" title="main-navigation">
<ul className="nav__items-left" title="project-menu"> <ul className="nav__items-left" title="project-menu">
<li className="nav__item"> <li className="nav__item-logo">
<InlineSVG src={logoUrl} alt="p5.js logo" />
</li>
<li className={navDropdownState.file}>
<button
onClick={this.toggleDropdown.bind(this, 'file')}
onBlur={this.handleBlur}
onFocus={this.clearHideTimeout}
>
<span className="nav__item-header">File</span>
<InlineSVG className="nav__item-header-triangle" src={triangleUrl} />
</button>
<ul className="nav__dropdown">
<li className="nav__dropdown-heading">
<span>File</span>
<InlineSVG src={triangleUrl} />
</li>
<li className="nav__dropdown-item">
<button <button
className="nav__new"
onClick={() => { onClick={() => {
if (!this.props.unsavedChanges) { if (!this.props.unsavedChanges) {
this.props.newProject(); this.props.newProject();
} else if (this.props.warnIfUnsavedChanges()) { } else if (this.props.warnIfUnsavedChanges()) {
this.props.newProject(); this.props.newProject();
} }
this.setDropdown('none');
}} }}
onFocus={this.handleFocus.bind(this, 'file')}
onBlur={this.handleBlur}
> >
New New
</button> </button>
</li> </li>
{(() => { // eslint-disable-line { (!this.props.project.owner || this.isUserOwner()) &&
if ( <li className="nav__dropdown-item">
!this.props.project.owner ||
(this.props.project.owner && this.props.project.owner.id === this.props.user.id)
) {
return (
<li className="nav__item">
<button <button
className="nav__save"
onClick={() => { onClick={() => {
if (this.props.user.authenticated) { if (this.props.user.authenticated) {
this.props.saveProject(); this.props.saveProject();
} else { } else {
this.props.showErrorModal('forceAuthentication'); this.props.showErrorModal('forceAuthentication');
} }
this.setDropdown('none');
}} }}
onFocus={this.handleFocus.bind(this, 'file')}
onBlur={this.handleBlur}
> >
Save Save
<span className="nav__keyboard-shortcut">{metaKeyName}+s</span>
</button> </button>
</li> </li> }
); { this.props.project.id && this.props.user.authenticated &&
} <li className="nav__dropdown-item">
})()} <button
{(() => { // eslint-disable-line onClick={() => {
if (this.props.project.id && this.props.user.authenticated) { this.props.cloneProject();
return ( this.setDropdown('none');
<li className="nav__item"> }}
<button className="nav__clone" onClick={this.props.cloneProject}> onFocus={this.handleFocus.bind(this, 'file')}
onBlur={this.handleBlur}
>
Duplicate Duplicate
</button> </button>
</li> </li> }
); { this.props.project.id &&
} <li className="nav__dropdown-item">
})()} <button
{(() => { // eslint-disable-line onClick={() => {
if (this.props.project.id) { this.props.showShareModal();
return ( this.setDropdown('none');
<li className="nav__item"> }}
<button className="nav__export" onClick={() => this.props.exportProjectAsZip(this.props.project.id)}> onFocus={this.handleFocus.bind(this, 'file')}
Download onBlur={this.handleBlur}
</button> >
</li>
);
}
})()}
{(() => { // eslint-disable-line
if (this.props.project.id) {
return (
<li className="nav__item">
<button onClick={this.props.showShareModal}>
Share Share
</button> </button>
</li> </li> }
); { this.props.project.id &&
} <li className="nav__dropdown-item">
})()} <button
{(() => { // eslint-disable-line onClick={() => {
if (this.props.user.authenticated) { this.props.exportProjectAsZip(this.props.project.id);
return ( this.setDropdown('none');
<li className="nav__item"> }}
<p className="nav__open"> onFocus={this.handleFocus.bind(this, 'file')}
onBlur={this.handleBlur}
>
Download
</button>
</li> }
{ this.props.user.authenticated &&
<li className="nav__dropdown-item">
<Link <Link
to={`/${this.props.user.username}/sketches`} to={`/${this.props.user.username}/sketches`}
onFocus={this.handleFocus.bind(this, 'file')}
onBlur={this.handleBlur}
onClick={this.setDropdown.bind(this, 'none')}
> >
Open Open
</Link> </Link>
</p> </li> }
</li> <li className="nav__dropdown-item">
);
}
})()}
<li className="nav__item">
<p className="nav__open">
<Link <Link
to="/p5/sketches" to="/p5/sketches"
onFocus={this.handleFocus.bind(this, 'file')}
onBlur={this.handleBlur}
onClick={this.setDropdown.bind(this, 'none')}
> >
Examples Examples
</Link> </Link>
</p>
</li> </li>
<li className="nav__item"> </ul>
<p className="nav__reference"> </li>
<li className={navDropdownState.edit}>
<button
onClick={this.toggleDropdown.bind(this, 'edit')}
onBlur={this.handleBlur}
onFocus={this.clearHideTimeout}
>
<span className="nav__item-header">Edit</span>
<InlineSVG className="nav__item-header-triangle" src={triangleUrl} />
</button>
<ul className="nav__dropdown">
<li className="nav__dropdown-heading">
<span>Edit</span>
<InlineSVG src={triangleUrl} />
</li>
<li className="nav__dropdown-item">
<button
onClick={() => {
this.props.cmController.tidyCode();
this.setDropdown('none');
}}
onFocus={this.handleFocus.bind(this, 'edit')}
onBlur={this.handleBlur}
>
Tidy Code
<span className="nav__keyboard-shortcut">{'\u21E7'}+Tab</span>
</button>
</li>
<li className="nav__dropdown-item">
<button
onClick={() => {
this.props.cmController.showFind();
this.setDropdown('none');
}}
onFocus={this.handleFocus.bind(this, 'edit')}
onBlur={this.handleBlur}
>
Find
<span className="nav__keyboard-shortcut">{metaKeyName}+F</span>
</button>
</li>
<li className="nav__dropdown-item">
<button
onClick={() => {
this.props.cmController.findNext();
this.setDropdown('none');
}}
onFocus={this.handleFocus.bind(this, 'edit')}
onBlur={this.handleBlur}
>
Find Next
<span className="nav__keyboard-shortcut">{metaKeyName}+G</span>
</button>
</li>
<li className="nav__dropdown-item">
<button
onClick={() => {
this.props.cmController.findPrev();
this.setDropdown('none');
}}
onFocus={this.handleFocus.bind(this, 'edit')}
onBlur={this.handleBlur}
>
Find Previous
<span className="nav__keyboard-shortcut">{'\u21E7'}+{metaKeyName}+G</span>
</button>
</li>
</ul>
</li>
<li className={navDropdownState.sketch}>
<button
onClick={this.toggleDropdown.bind(this, 'sketch')}
onBlur={this.handleBlur}
onFocus={this.clearHideTimeout}
>
<span className="nav__item-header">Sketch</span>
<InlineSVG className="nav__item-header-triangle" src={triangleUrl} />
</button>
<ul className="nav__dropdown">
<li className="nav__dropdown-heading">
<span>Sketch</span>
<InlineSVG src={triangleUrl} />
</li>
<li className="nav__dropdown-item">
<button
onClick={() => {
this.props.startSketch();
this.setDropdown('none');
}}
onFocus={this.handleFocus.bind(this, 'sketch')}
onBlur={this.handleBlur}
>
Run
<span className="nav__keyboard-shortcut">{metaKeyName}+Enter</span>
</button>
</li>
<li className="nav__dropdown-item">
<button
onClick={() => {
this.props.stopSketch();
this.setDropdown('none');
}}
onFocus={this.handleFocus.bind(this, 'sketch')}
onBlur={this.handleBlur}
>
Stop
<span className="nav__keyboard-shortcut">{'\u21E7'}+{metaKeyName}+Enter</span>
</button>
</li>
<li className="nav__dropdown-item">
<button
onClick={() => {
this.props.setAllAccessibleOutput(true);
this.setDropdown('none');
}}
onFocus={this.handleFocus.bind(this, 'sketch')}
onBlur={this.handleBlur}
>
Start Accessible
<span className="nav__keyboard-shortcut">{'\u21E7'}+{metaKeyName}+1</span>
</button>
</li>
<li className="nav__dropdown-item">
<button
onClick={() => {
this.props.setAllAccessibleOutput(false);
this.setDropdown('none');
}}
onFocus={this.handleFocus.bind(this, 'sketch')}
onBlur={this.handleBlur}
>
Stop Accessible
<span className="nav__keyboard-shortcut">{'\u21E7'}+{metaKeyName}+2</span>
</button>
</li>
</ul>
</li>
<li className={navDropdownState.help}>
<button
onClick={this.toggleDropdown.bind(this, 'help')}
onBlur={this.handleBlur}
onFocus={this.clearHideTimeout}
>
<span className="nav__item-header">Help</span>
<InlineSVG className="nav__item-header-triangle" src={triangleUrl} />
</button>
<ul className="nav__dropdown">
<li className="nav__dropdown-heading">
<span>Help</span>
<InlineSVG src={triangleUrl} />
</li>
<li className="nav__dropdown-item">
<button
onClick={() => {
this.props.showKeyboardShortcutModal();
this.setDropdown('none');
}}
>
Keyboard Shortcuts
</button>
</li>
<li className="nav__dropdown-item">
<a <a
href="https://p5js.org/reference/" href="https://p5js.org/reference/"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
onFocus={this.handleFocus.bind(this, 'help')}
onBlur={this.handleBlur}
onClick={this.setDropdown.bind(this, 'none')}
>Reference</a> >Reference</a>
</p>
</li> </li>
<li className="nav__item"> <li className="nav__dropdown-item">
<p className="nav__about"> <Link
<Link to="/about"> to="/about"
onFocus={this.handleFocus.bind(this, 'help')}
onBlur={this.handleBlur}
onClick={this.setDropdown.bind(this, 'none')}
>
About About
</Link> </Link>
</p>
</li> </li>
</ul> </ul>
</li>
</ul>
{ !this.props.user.authenticated &&
<ul className="nav__items-right" title="user-menu"> <ul className="nav__items-right" title="user-menu">
{(() => {
if (!this.props.user.authenticated) {
return (
<li className="nav__item"> <li className="nav__item">
<p> <p>
<Link to="/login">Log in</Link> <Link to="/login">Log in</Link>
@ -128,40 +390,74 @@ class Nav extends React.PureComponent {
<Link to="/signup">Sign up</Link> <Link to="/signup">Sign up</Link>
</p> </p>
</li> </li>
); </ul>}
} { this.props.user.authenticated &&
return ( <ul className="nav__items-right" title="user-menu">
<li className="nav__item"> <li className="nav__item">
<a>Hello, {this.props.user.username}!</a> <span>Hello, {this.props.user.username}!</span>
</li>
<span className="nav__item-spacer">|</span>
<li className={navDropdownState.account}>
<button
className="nav__item-header"
onClick={this.toggleDropdown.bind(this, 'account')}
onBlur={this.handleBlur}
onFocus={this.clearHideTimeout}
>
My Account
</button>
<InlineSVG className="nav__item-header-triangle" src={triangleUrl} />
<ul className="nav__dropdown"> <ul className="nav__dropdown">
<li className="nav__dropdown-heading"> <li className="nav__dropdown-heading">
<a>Hello, {this.props.user.username}!</a> <span>My Account</span>
<InlineSVG src={triangleUrl} />
</li> </li>
<li> <li className="nav__dropdown-item">
<Link to={`/${this.props.user.username}/sketches`}> <Link
to={`/${this.props.user.username}/sketches`}
onFocus={this.handleFocus.bind(this, 'account')}
onBlur={this.handleBlur}
onClick={this.setDropdown.bind(this, 'none')}
>
My sketches My sketches
</Link> </Link>
</li> </li>
<li> <li className="nav__dropdown-item">
<Link to={`/${this.props.user.username}/assets`}> <Link
to={`/${this.props.user.username}/assets`}
onFocus={this.handleFocus.bind(this, 'account')}
onBlur={this.handleBlur}
onClick={this.setDropdown.bind(this, 'none')}
>
My assets My assets
</Link> </Link>
</li> </li>
<li> <li className="nav__dropdown-item">
<Link to={`/${this.props.user.username}/account`}> <Link
My account to={`/${this.props.user.username}/account`}
onFocus={this.handleFocus.bind(this, 'account')}
onBlur={this.handleBlur}
onClick={this.setDropdown.bind(this, 'none')}
>
Settings
</Link> </Link>
</li> </li>
<li> <li className="nav__dropdown-item">
<button onClick={this.props.logoutUser} > <button
onClick={() => {
this.props.logoutUser();
this.setDropdown('none');
}}
onFocus={this.handleFocus.bind(this, 'account')}
onBlur={this.handleBlur}
>
Log out Log out
</button> </button>
</li> </li>
</ul> </ul>
</li> </li>
); </ul> }
})()} {/*
</ul>
<div className="nav__announce"> <div className="nav__announce">
This is a preview version of the editor, that has not yet been officially released. This is a preview version of the editor, that has not yet been officially released.
It is in development, you can report bugs <a It is in development, you can report bugs <a
@ -171,6 +467,7 @@ class Nav extends React.PureComponent {
>here</a>. >here</a>.
Please use with caution. Please use with caution.
</div> </div>
*/}
</nav> </nav>
); );
} }
@ -196,14 +493,25 @@ Nav.propTypes = {
showShareModal: PropTypes.func.isRequired, showShareModal: PropTypes.func.isRequired,
showErrorModal: PropTypes.func.isRequired, showErrorModal: PropTypes.func.isRequired,
unsavedChanges: PropTypes.bool.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 = { Nav.defaultProps = {
project: { project: {
id: undefined, id: undefined,
owner: undefined owner: undefined
} },
cmController: {}
}; };
export default Nav; export default Nav;

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="9px" height="6px" viewBox="0 0 9 6" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
<title>Triangle</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<polygon id="Triangle" fill="#B5B5B5" transform="translate(4.500000, 3.000000) rotate(180.000000) translate(-4.500000, -3.000000) " points="4.5 0 9 6 0 6"></polygon>
</g>
</svg>

After

Width:  |  Height:  |  Size: 633 B

View file

@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="56" height="42" viewBox="0 0 56 42">
<defs>
<path id="a" d="M0 0h36v36H0z"/>
<path id="b" d="M0 0h36v36H0z"/>
</defs>
<g fill="none" fill-rule="evenodd">
<path fill="#ED225D" d="M0 0h56v42H0z"/>
<g transform="translate(10)">
<use fill="#FFF" xlink:href="#a"/>
<path stroke="#979797" d="M.5.5h35v35H.5z"/>
</g>
<path fill="#ED225D" d="M21.025 20.617a2.826 2.826 0 0 0-.941-.795c-.38-.201-.824-.302-1.334-.302-.481 0-.912.104-1.29.314-.38.209-.704.478-.974.805-.27.329-.474.702-.612 1.12a3.85 3.85 0 0 0 0 2.496c.138.411.342.78.612 1.109.27.328.594.593.974.794.378.202.809.302 1.29.302.51 0 .955-.104 1.334-.313.38-.209.693-.477.94-.806.248-.328.434-.701.559-1.12a4.283 4.283 0 0 0 0-2.497c-.125-.409-.31-.778-.558-1.107"/>
<g transform="translate(10)">
<mask id="c" fill="#fff">
<use xlink:href="#b"/>
</mask>
<path fill="#ED225D" d="M32.75 16.44l-.989.717-1.026-1.361-1.001 1.326-.963-.734.975-1.337-1.582-.571.374-1.15 1.586.511v-1.644h1.273v1.647l1.574-.476.373 1.15-1.57.536.975 1.385zm-7.045 8.959a5.208 5.208 0 0 1-1.25 1.769c-.524.481-1.139.85-1.844 1.105a6.559 6.559 0 0 1-2.25.383c-1.334 0-2.46-.312-3.374-.936a5.238 5.238 0 0 1-1.533-1.614l1.953-1.835.03-.01a3.14 3.14 0 0 0 1.125 1.52c.525.384 1.147.576 1.867.576.405 0 .787-.068 1.147-.204.36-.134.675-.33.945-.585.27-.256.484-.575.641-.958.158-.383.237-.815.237-1.296 0-.616-.106-1.127-.315-1.533a2.775 2.775 0 0 0-.821-.98 3.075 3.075 0 0 0-1.136-.519 5.634 5.634 0 0 0-1.282-.146c-.3 0-.619.022-.957.067-.337.046-.667.11-.99.192-.322.083-.637.177-.944.281a6.717 6.717 0 0 0-.821.338l.27-8.722h9.065v2.434h-6.501l-.157 3.449c.254-.075.558-.128.91-.158.353-.03.672-.045.957-.045.78 0 1.503.112 2.17.338a5.15 5.15 0 0 1 1.744.98c.495.429.88.955 1.158 1.578.278.624.416 1.341.416 2.153 0 .887-.153 1.679-.46 2.378zm-11.601-.36a5.504 5.504 0 0 1-1.058 1.825 5.08 5.08 0 0 1-1.664 1.24c-.653.307-1.392.462-2.216.462a4.678 4.678 0 0 1-2.103-.474c-.638-.315-1.13-.743-1.474-1.285h-.045v6.853h-2.7V17.43h2.588v1.51h.067c.135-.21.311-.424.529-.642.217-.218.484-.413.799-.587a4.908 4.908 0 0 1 2.407-.597c.764 0 1.469.147 2.114.44.645.293 1.2.695 1.665 1.206a5.465 5.465 0 0 1 1.08 1.803c.254.691.382 1.435.382 2.231 0 .797-.124 1.544-.371 2.243zM0 36.07h36V0H0v36.071z" mask="url(#c)"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -57,14 +57,15 @@ Overlay.propTypes = {
closeOverlay: PropTypes.func, closeOverlay: PropTypes.func,
title: PropTypes.string, title: PropTypes.string,
ariaLabel: PropTypes.string, ariaLabel: PropTypes.string,
previousPath: PropTypes.string.isRequired previousPath: PropTypes.string
}; };
Overlay.defaultProps = { Overlay.defaultProps = {
children: null, children: null,
title: 'Modal', title: 'Modal',
closeOverlay: null, closeOverlay: null,
ariaLabel: 'modal' ariaLabel: 'modal',
previousPath: '/'
}; };
export default Overlay; export default Overlay;

View file

@ -1,12 +1,13 @@
import * as ActionTypes from '../../../constants'; import * as ActionTypes from '../../../constants';
import { clearConsole } from './console';
export function startSketch() { export function startVisualSketch() {
return { return {
type: ActionTypes.START_SKETCH type: ActionTypes.START_SKETCH
}; };
} }
export function stopSketch() { export function stopVisualSketch() {
return { return {
type: ActionTypes.STOP_SKETCH type: ActionTypes.STOP_SKETCH
}; };
@ -20,7 +21,7 @@ export function startRefreshSketch() {
export function startSketchAndRefresh() { export function startSketchAndRefresh() {
return (dispatch) => { return (dispatch) => {
dispatch(startSketch()); dispatch(startVisualSketch());
dispatch(startRefreshSketch()); dispatch(startRefreshSketch());
}; };
} }
@ -233,3 +234,26 @@ export function hideHelpModal() {
type: ActionTypes.HIDE_HELP_MODAL 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());
};
}

View file

@ -216,3 +216,12 @@ export function setAutorefresh(value) {
} }
}; };
} }
export function setAllAccessibleOutput(value) {
return (dispatch) => {
dispatch(setTextOutput(value));
dispatch(setGridOutput(value));
dispatch(setSoundOutput(value));
};
}

View file

@ -45,7 +45,6 @@ window.CSSLint = CSSLint;
window.HTMLHint = HTMLHint; window.HTMLHint = HTMLHint;
const beepUrl = require('../../../sounds/audioAlert.mp3'); const beepUrl = require('../../../sounds/audioAlert.mp3');
const downArrowUrl = require('../../../images/down-arrow.svg');
const unsavedChangesDotUrl = require('../../../images/unsaved-changes-dot.svg'); const unsavedChangesDotUrl = require('../../../images/unsaved-changes-dot.svg');
const rightArrowUrl = require('../../../images/right-arrow.svg'); const rightArrowUrl = require('../../../images/right-arrow.svg');
const leftArrowUrl = require('../../../images/left-arrow.svg'); const leftArrowUrl = require('../../../images/left-arrow.svg');
@ -54,6 +53,9 @@ class Editor extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.tidyCode = this.tidyCode.bind(this); 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() { componentDidMount() {
this.beep = new Audio(beepUrl); this.beep = new Audio(beepUrl);
@ -85,7 +87,8 @@ class Editor extends React.Component {
options: { options: {
'asi': true, 'asi': true,
'eqeqeq': false, '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.getWrapperElement().style['font-size'] = `${this.props.fontSize}px`;
this._cm.setOption('indentWithTabs', this.props.isTabIndent); this._cm.setOption('indentWithTabs', this.props.isTabIndent);
this._cm.setOption('tabSize', this.props.indentationAmount); this._cm.setOption('tabSize', this.props.indentationAmount);
this.props.provideController({
tidyCode: this.tidyCode,
showFind: this.showFind,
findNext: this.findNext,
findPrev: this.findPrev
});
} }
componentWillUpdate(nextProps) { componentWillUpdate(nextProps) {
@ -165,6 +175,7 @@ class Editor extends React.Component {
componentWillUnmount() { componentWillUnmount() {
this._cm = null; this._cm = null;
this.props.provideController(null);
} }
getFileMode(fileName) { 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() { toggleEditorOptions() {
if (this.props.editorOptionsVisible) { if (this.props.editorOptionsVisible) {
this.props.closeEditorOptions(); this.props.closeEditorOptions();
@ -257,26 +282,6 @@ class Editor extends React.Component {
isUserOwner={this.props.isUserOwner} isUserOwner={this.props.isUserOwner}
/> />
</div> </div>
<button
className="editor__options-button"
aria-label="editor options"
tabIndex="0"
ref={(element) => { this.optionsButton = element; }}
onClick={() => {
this.toggleEditorOptions();
}}
onBlur={() => setTimeout(this.props.closeEditorOptions, 200)}
>
<InlineSVG src={downArrowUrl} />
</button>
<ul className="editor__options" title="editor options">
<li>
<button onClick={this.tidyCode}>Tidy</button>
</li>
<li>
<button onClick={this.props.showKeyboardShortcutModal}>Keyboard shortcuts</button>
</li>
</ul>
</header> </header>
<div ref={(element) => { this.codemirrorContainer = element; }} className="editor-holder" tabIndex="0"> <div ref={(element) => { this.codemirrorContainer = element; }} className="editor-holder" tabIndex="0">
</div> </div>
@ -310,7 +315,6 @@ Editor.propTypes = {
editorOptionsVisible: PropTypes.bool.isRequired, editorOptionsVisible: PropTypes.bool.isRequired,
showEditorOptions: PropTypes.func.isRequired, showEditorOptions: PropTypes.func.isRequired,
closeEditorOptions: PropTypes.func.isRequired, closeEditorOptions: PropTypes.func.isRequired,
showKeyboardShortcutModal: PropTypes.func.isRequired,
setUnsavedChanges: PropTypes.func.isRequired, setUnsavedChanges: PropTypes.func.isRequired,
startRefreshSketch: PropTypes.func.isRequired, startRefreshSketch: PropTypes.func.isRequired,
autorefresh: PropTypes.bool.isRequired, autorefresh: PropTypes.bool.isRequired,
@ -327,7 +331,8 @@ Editor.propTypes = {
collapseSidebar: PropTypes.func.isRequired, collapseSidebar: PropTypes.func.isRequired,
expandSidebar: PropTypes.func.isRequired, expandSidebar: PropTypes.func.isRequired,
isUserOwner: PropTypes.bool, isUserOwner: PropTypes.bool,
clearConsole: PropTypes.func.isRequired clearConsole: PropTypes.func.isRequired,
provideController: PropTypes.func.isRequired
}; };
Editor.defaultProps = { Editor.defaultProps = {

View file

@ -10,24 +10,24 @@ class GridOutput extends React.Component {
id="gridOutput-content" id="gridOutput-content"
ref={(element) => { this.GridOutputModal = element; }} ref={(element) => { this.GridOutputModal = element; }}
> >
<h2> Grid Output </h2> <h2> table Output </h2>
<p <p
tabIndex="0" tabIndex="0"
role="main" role="main"
id="gridOutput-content-summary" id="gridOutput-content-summary"
aria-label="grid output summary" aria-label="table output summary"
> >
</p> </p>
<table <table
id="gridOutput-content-table" id="gridOutput-content-table"
summary="grid output details" summary="table output details"
> >
</table> </table>
<div <div
tabIndex="0" tabIndex="0"
role="main" role="main"
id="gridOutput-content-details" id="gridOutput-content-details"
aria-label="grid output details" aria-label="table output details"
> >
</div> </div>
</section> </section>

View file

@ -0,0 +1,24 @@
import React from 'react';
function HTTPSModal() {
return (
<section className="help-modal">
<div className="help-modal__section">
<div>
<p>Use the checkbox to choose whether this sketch should be loaded using HTTPS or HTTP.</p>
<p>You should choose HTTPS if you need to:</p>
<ul>
<li>access a webcam or microphone</li>
<li>access an API served over HTTPS</li>
</ul>
<p>Choose HTTP if you need to:</p>
<ul>
<li>access an API served over HTTP</li>
</ul>
</div>
</div>
</section>
);
}
export default HTTPSModal;

View file

@ -1,61 +0,0 @@
import React, { PropTypes } from 'react';
import InlineSVG from 'react-inlinesvg';
const exitUrl = require('../../../images/exit.svg');
const helpContent = {
serveSecure: {
title: 'Serve over HTTPS',
body: (
<div>
<p>Use the checkbox to choose whether this sketch should be loaded using HTTPS or HTTP.</p>
<p>You should choose HTTPS if you need to:</p>
<ul>
<li>access a webcam or microphone</li>
<li>access an API served over HTTPS</li>
</ul>
<p>Choose HTTP if you need to:</p>
<ul>
<li>access an API served over HTTP</li>
</ul>
</div>
)
}
};
const fallbackContent = {
title: 'No content for this topic',
body: null,
};
class HelpModal extends React.Component {
componentDidMount() {
this.shareModal.focus();
}
render() {
const content = helpContent[this.props.type] == null ?
fallbackContent :
helpContent[this.props.type];
return (
<section className="help-modal" ref={(element) => { this.shareModal = element; }} tabIndex="0">
<header className="help-modal__header">
<h2>{content.title}</h2>
<button className="about__exit-button" onClick={this.props.closeModal}>
<InlineSVG src={exitUrl} alt="Close Help Overlay" />
</button>
</header>
<div className="help-modal__section">
{content.body}
</div>
</section>
);
}
}
HelpModal.propTypes = {
type: PropTypes.string.isRequired,
closeModal: PropTypes.func.isRequired,
};
export default HelpModal;

View file

@ -8,7 +8,7 @@ function KeyboardShortcutModal() {
return ( return (
<ul className="keyboard-shortcuts" title="keyboard shortcuts"> <ul className="keyboard-shortcuts" title="keyboard shortcuts">
<li className="keyboard-shortcut-item"> <li className="keyboard-shortcut-item">
<span className="keyboard-shortcut__command">Shift + Tab</span> <span className="keyboard-shortcut__command">{'\u21E7'} + Tab</span>
<span>Tidy</span> <span>Tidy</span>
</li> </li>
<li className="keyboard-shortcut-item"> <li className="keyboard-shortcut-item">
@ -31,7 +31,7 @@ function KeyboardShortcutModal() {
</li> </li>
<li className="keyboard-shortcut-item"> <li className="keyboard-shortcut-item">
<span className="keyboard-shortcut__command"> <span className="keyboard-shortcut__command">
{metaKeyName} + Shift + G {metaKeyName} + {'\u21E7'} + G
</span> </span>
<span>Find Previous Text Match</span> <span>Find Previous Text Match</span>
</li> </li>
@ -61,21 +61,21 @@ function KeyboardShortcutModal() {
</li> </li>
<li className="keyboard-shortcut-item"> <li className="keyboard-shortcut-item">
<span className="keyboard-shortcut__command"> <span className="keyboard-shortcut__command">
{metaKeyName} + Shift + Enter {metaKeyName} + {'\u21E7'} + Enter
</span> </span>
<span>Stop Sketch</span> <span>Stop Sketch</span>
</li> </li>
<li className="keyboard-shortcut-item"> <li className="keyboard-shortcut-item">
<span className="keyboard-shortcut__command"> <span className="keyboard-shortcut__command">
{metaKeyName} + Shift + 1 {metaKeyName} + {'\u21E7'} + 1
</span> </span>
<span>Toggle Text-based Canvas</span> <span>Turn on Accessible Output</span>
</li> </li>
<li className="keyboard-shortcut-item"> <li className="keyboard-shortcut-item">
<span className="keyboard-shortcut__command"> <span className="keyboard-shortcut__command">
{metaKeyName} + Shift + 2 {metaKeyName} + {'\u21E7'} + 2
</span> </span>
<span>Turn Off Text-based Canvas</span> <span>Turn off Accessible Output</span>
</li> </li>
</ul> </ul>
); );

View file

@ -34,8 +34,8 @@ function getAllScriptOffsets(htmlFile) {
} else { } else {
endFilenameInd = htmlFile.indexOf('.js', ind + startTag.length + 3); endFilenameInd = htmlFile.indexOf('.js', ind + startTag.length + 3);
filename = htmlFile.substring(ind + startTag.length, endFilenameInd); filename = htmlFile.substring(ind + startTag.length, endFilenameInd);
// the length of hijackConsoleErrorsScript is 35 lines, already needed a -1 offset. // the length of hijackConsoleErrorsScript is 31 lines
lineOffset = htmlFile.substring(0, ind).split('\n').length + 34; lineOffset = htmlFile.substring(0, ind).split('\n').length + 31;
offs.push([lineOffset, filename]); offs.push([lineOffset, filename]);
lastInd = ind + 1; lastInd = ind + 1;
} }
@ -88,6 +88,7 @@ class PreviewFrame extends React.Component {
} }
window.addEventListener('message', (messageEvent) => { window.addEventListener('message', (messageEvent) => {
console.log(messageEvent);
messageEvent.data.forEach((message) => { messageEvent.data.forEach((message) => {
const args = message.arguments; const args = message.arguments;
Object.keys(args).forEach((key) => { Object.keys(args).forEach((key) => {
@ -152,6 +153,14 @@ class PreviewFrame extends React.Component {
doc.srcDoc = ''; doc.srcDoc = '';
} }
addLoopProtect(sketchDoc) {
const scriptsInHTML = sketchDoc.getElementsByTagName('script');
const scriptsInHTMLArray = Array.prototype.slice.call(scriptsInHTML);
scriptsInHTMLArray.forEach((script) => {
script.innerHTML = loopProtect(script.innerHTML); // eslint-disable-line
});
}
injectLocalFiles() { injectLocalFiles() {
const htmlFile = this.props.htmlFile.content; const htmlFile = this.props.htmlFile.content;
let scriptOffs = []; let scriptOffs = [];
@ -227,7 +236,7 @@ class PreviewFrame extends React.Component {
scriptOffs = getAllScriptOffsets(sketchDocString); scriptOffs = getAllScriptOffsets(sketchDocString);
const consoleErrorsScript = sketchDoc.createElement('script'); const consoleErrorsScript = sketchDoc.createElement('script');
consoleErrorsScript.innerHTML = hijackConsoleErrorsScript(JSON.stringify(scriptOffs)); consoleErrorsScript.innerHTML = hijackConsoleErrorsScript(JSON.stringify(scriptOffs));
// sketchDoc.head.appendChild(consoleErrorsScript); this.addLoopProtect(sketchDoc);
sketchDoc.head.insertBefore(consoleErrorsScript, sketchDoc.head.firstElement); sketchDoc.head.insertBefore(consoleErrorsScript, sketchDoc.head.firstElement);
return `<!DOCTYPE HTML>\n${sketchDoc.documentElement.outerHTML}`; return `<!DOCTYPE HTML>\n${sketchDoc.documentElement.outerHTML}`;
@ -280,7 +289,10 @@ class PreviewFrame extends React.Component {
} }
} }
}); });
newContent = decomment(newContent, { ignore: /noprotect/g }); newContent = decomment(newContent, {
ignore: /noprotect/g,
space: true
});
newContent = loopProtect(newContent); newContent = loopProtect(newContent);
return newContent; return newContent;
} }

View file

@ -4,7 +4,6 @@ import classNames from 'classnames';
import InlineSVG from 'react-inlinesvg'; import InlineSVG from 'react-inlinesvg';
const playUrl = require('../../../images/play.svg'); const playUrl = require('../../../images/play.svg');
const logoUrl = require('../../../images/p5js-logo.svg');
const stopUrl = require('../../../images/stop.svg'); const stopUrl = require('../../../images/stop.svg');
const preferencesUrl = require('../../../images/preferences.svg'); const preferencesUrl = require('../../../images/preferences.svg');
const editProjectNameUrl = require('../../../images/pencil.svg'); const editProjectNameUrl = require('../../../images/pencil.svg');
@ -59,14 +58,9 @@ class Toolbar extends React.Component {
return ( return (
<div className="toolbar"> <div className="toolbar">
<InlineSVG className="toolbar__logo" src={logoUrl} alt="p5js Logo" />
<button <button
className="toolbar__play-sketch-button" className="toolbar__play-sketch-button"
onClick={() => { onClick={this.props.startAccessibleSketch}
this.props.clearConsole();
this.props.startAccessibleOutput();
this.props.startSketchAndRefresh();
}}
aria-label="play sketch" aria-label="play sketch"
disabled={this.props.infiniteLoop} disabled={this.props.infiniteLoop}
> >
@ -74,10 +68,7 @@ class Toolbar extends React.Component {
</button> </button>
<button <button
className={playButtonClass} className={playButtonClass}
onClick={() => { onClick={this.props.startSketch}
this.props.clearConsole();
this.props.startSketchAndRefresh();
}}
aria-label="play only visual sketch" aria-label="play only visual sketch"
disabled={this.props.infiniteLoop} disabled={this.props.infiniteLoop}
> >
@ -85,7 +76,7 @@ class Toolbar extends React.Component {
</button> </button>
<button <button
className={stopButtonClass} className={stopButtonClass}
onClick={() => { this.props.stopAccessibleOutput(); this.props.stopSketch(); }} onClick={this.props.stopSketch}
aria-label="stop sketch" aria-label="stop sketch"
> >
<InlineSVG src={stopUrl} alt="Stop Sketch" /> <InlineSVG src={stopUrl} alt="Stop Sketch" />
@ -187,8 +178,6 @@ Toolbar.propTypes = {
isPlaying: PropTypes.bool.isRequired, isPlaying: PropTypes.bool.isRequired,
preferencesIsVisible: PropTypes.bool.isRequired, preferencesIsVisible: PropTypes.bool.isRequired,
stopSketch: PropTypes.func.isRequired, stopSketch: PropTypes.func.isRequired,
startAccessibleOutput: PropTypes.func.isRequired,
stopAccessibleOutput: PropTypes.func.isRequired,
setProjectName: PropTypes.func.isRequired, setProjectName: PropTypes.func.isRequired,
openPreferences: PropTypes.func.isRequired, openPreferences: PropTypes.func.isRequired,
owner: PropTypes.shape({ owner: PropTypes.shape({
@ -207,10 +196,10 @@ Toolbar.propTypes = {
autorefresh: PropTypes.bool.isRequired, autorefresh: PropTypes.bool.isRequired,
setAutorefresh: PropTypes.func.isRequired, setAutorefresh: PropTypes.func.isRequired,
setServeSecure: PropTypes.func.isRequired, setServeSecure: PropTypes.func.isRequired,
startSketchAndRefresh: PropTypes.func.isRequired, startSketch: PropTypes.func.isRequired,
startAccessibleSketch: PropTypes.func.isRequired,
saveProject: PropTypes.func.isRequired, saveProject: PropTypes.func.isRequired,
currentUser: PropTypes.string, currentUser: PropTypes.string
clearConsole: PropTypes.func.isRequired
}; };
Toolbar.defaultProps = { Toolbar.defaultProps = {

View file

@ -15,7 +15,7 @@ import NewFolderModal from '../components/NewFolderModal';
import ShareModal from '../components/ShareModal'; import ShareModal from '../components/ShareModal';
import KeyboardShortcutModal from '../components/KeyboardShortcutModal'; import KeyboardShortcutModal from '../components/KeyboardShortcutModal';
import ErrorModal from '../components/ErrorModal'; import ErrorModal from '../components/ErrorModal';
import HelpModal from '../components/HelpModal'; import HTTPSModal from '../components/HTTPSModal';
import Nav from '../../../components/Nav'; import Nav from '../../../components/Nav';
import Console from '../components/Console'; import Console from '../components/Console';
import Toast from '../components/Toast'; import Toast from '../components/Toast';
@ -165,18 +165,15 @@ class IDEView extends React.Component {
} else if (e.keyCode === 13 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac))) { } else if (e.keyCode === 13 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac))) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
this.props.clearConsole(); this.props.startSketch();
this.props.startSketchAndRefresh(); // 50 === 2
} else if (e.keyCode === 50 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac)) && e.shiftKey) { } else if (e.keyCode === 50 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac)) && e.shiftKey) {
e.preventDefault(); e.preventDefault();
this.props.setTextOutput(false); this.props.setAllAccessibleOutput(false);
this.props.setGridOutput(false); // 49 === 1
this.props.setSoundOutput(false);
} else if (e.keyCode === 49 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac)) && e.shiftKey) { } else if (e.keyCode === 49 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac)) && e.shiftKey) {
e.preventDefault(); e.preventDefault();
this.props.setTextOutput(true); this.props.setAllAccessibleOutput(true);
this.props.setGridOutput(true);
this.props.setSoundOutput(true);
} }
} }
@ -213,18 +210,20 @@ class IDEView extends React.Component {
cloneProject={this.props.cloneProject} cloneProject={this.props.cloneProject}
project={this.props.project} project={this.props.project}
logoutUser={this.props.logoutUser} logoutUser={this.props.logoutUser}
startSketch={this.props.startSketch}
stopSketch={this.props.stopSketch} stopSketch={this.props.stopSketch}
showShareModal={this.props.showShareModal} showShareModal={this.props.showShareModal}
showErrorModal={this.props.showErrorModal} showErrorModal={this.props.showErrorModal}
unsavedChanges={this.props.ide.unsavedChanges} unsavedChanges={this.props.ide.unsavedChanges}
warnIfUnsavedChanges={this.warnIfUnsavedChanges} warnIfUnsavedChanges={this.warnIfUnsavedChanges}
showKeyboardShortcutModal={this.props.showKeyboardShortcutModal}
cmController={this.cmController}
setAllAccessibleOutput={this.props.setAllAccessibleOutput}
/> />
<Toolbar <Toolbar
className="Toolbar" className="Toolbar"
isPlaying={this.props.ide.isPlaying} isPlaying={this.props.ide.isPlaying}
stopSketch={this.props.stopSketch} stopSketch={this.props.stopSketch}
startAccessibleOutput={this.props.startAccessibleOutput}
stopAccessibleOutput={this.props.stopAccessibleOutput}
projectName={this.props.project.name} projectName={this.props.project.name}
setProjectName={this.props.setProjectName} setProjectName={this.props.setProjectName}
showEditProjectName={this.props.showEditProjectName} showEditProjectName={this.props.showEditProjectName}
@ -241,10 +240,10 @@ class IDEView extends React.Component {
infiniteLoop={this.props.ide.infiniteLoop} infiniteLoop={this.props.ide.infiniteLoop}
autorefresh={this.props.preferences.autorefresh} autorefresh={this.props.preferences.autorefresh}
setAutorefresh={this.props.setAutorefresh} setAutorefresh={this.props.setAutorefresh}
startSketchAndRefresh={this.props.startSketchAndRefresh} startSketch={this.props.startSketch}
startAccessibleSketch={this.props.startAccessibleSketch}
saveProject={this.props.saveProject} saveProject={this.props.saveProject}
currentUser={this.props.user.username} currentUser={this.props.user.username}
clearConsole={this.props.clearConsole}
showHelpModal={this.props.showHelpModal} showHelpModal={this.props.showHelpModal}
/> />
<Preferences <Preferences
@ -300,8 +299,8 @@ class IDEView extends React.Component {
<SplitPane <SplitPane
split="vertical" split="vertical"
defaultSize={'50%'} defaultSize={'50%'}
onChange={() => (this.overlay.style.display = 'block')} onChange={() => { this.overlay.style.display = 'block'; }}
onDragFinished={() => (this.overlay.style.display = 'none')} onDragFinished={() => { this.overlay.style.display = 'none'; }}
> >
<SplitPane <SplitPane
split="horizontal" split="horizontal"
@ -341,6 +340,7 @@ class IDEView extends React.Component {
collapseSidebar={this.props.collapseSidebar} collapseSidebar={this.props.collapseSidebar}
isUserOwner={this.isUserOwner()} isUserOwner={this.isUserOwner()}
clearConsole={this.props.clearConsole} clearConsole={this.props.clearConsole}
provideController={(ctl) => { this.cmController = ctl; }}
/> />
<Console <Console
consoleEvents={this.props.console} consoleEvents={this.props.console}
@ -516,17 +516,16 @@ class IDEView extends React.Component {
{(() => { // eslint-disable-line {(() => { // eslint-disable-line
if (this.props.ide.helpType) { if (this.props.ide.helpType) {
return ( return (
<Overlay> <Overlay
<HelpModal title="Serve over HTTPS"
type={this.props.ide.helpType} closeOverlay={this.props.hideHelpModal}
closeModal={this.props.hideHelpModal} >
/> <HTTPSModal />
</Overlay> </Overlay>
); );
} }
})()} })()}
</div> </div>
); );
} }
} }
@ -572,8 +571,6 @@ IDEView.propTypes = {
helpType: PropTypes.string helpType: PropTypes.string
}).isRequired, }).isRequired,
stopSketch: PropTypes.func.isRequired, stopSketch: PropTypes.func.isRequired,
startAccessibleOutput: PropTypes.func.isRequired,
stopAccessibleOutput: PropTypes.func.isRequired,
project: PropTypes.shape({ project: PropTypes.shape({
id: PropTypes.string, id: PropTypes.string,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
@ -614,6 +611,7 @@ IDEView.propTypes = {
setTextOutput: PropTypes.func.isRequired, setTextOutput: PropTypes.func.isRequired,
setGridOutput: PropTypes.func.isRequired, setGridOutput: PropTypes.func.isRequired,
setSoundOutput: PropTypes.func.isRequired, setSoundOutput: PropTypes.func.isRequired,
setAllAccessibleOutput: PropTypes.func.isRequired,
files: PropTypes.arrayOf(PropTypes.shape({ files: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
@ -672,7 +670,6 @@ IDEView.propTypes = {
setUnsavedChanges: PropTypes.func.isRequired, setUnsavedChanges: PropTypes.func.isRequired,
setTheme: PropTypes.func.isRequired, setTheme: PropTypes.func.isRequired,
setAutorefresh: PropTypes.func.isRequired, setAutorefresh: PropTypes.func.isRequired,
startSketchAndRefresh: PropTypes.func.isRequired,
endSketchRefresh: PropTypes.func.isRequired, endSketchRefresh: PropTypes.func.isRequired,
startRefreshSketch: PropTypes.func.isRequired, startRefreshSketch: PropTypes.func.isRequired,
setBlobUrl: PropTypes.func.isRequired, setBlobUrl: PropTypes.func.isRequired,
@ -687,7 +684,9 @@ IDEView.propTypes = {
clearPersistedState: PropTypes.func.isRequired, clearPersistedState: PropTypes.func.isRequired,
persistState: PropTypes.func.isRequired, persistState: PropTypes.func.isRequired,
showHelpModal: PropTypes.func.isRequired, showHelpModal: PropTypes.func.isRequired,
hideHelpModal: PropTypes.func.isRequired hideHelpModal: PropTypes.func.isRequired,
startSketch: PropTypes.func.isRequired,
startAccessibleSketch: PropTypes.func.isRequired
}; };
function mapStateToProps(state) { function mapStateToProps(state) {

View file

@ -27,6 +27,10 @@ class LoginView extends React.Component {
} }
render() { render() {
if (this.props.user.authenticated) {
this.gotoHomePage();
return null;
}
return ( return (
<div className="form-container"> <div className="form-container">
<div className="form-container__header"> <div className="form-container__header">
@ -70,7 +74,16 @@ function mapDispatchToProps() {
} }
LoginView.propTypes = { LoginView.propTypes = {
previousPath: PropTypes.string.isRequired previousPath: PropTypes.string.isRequired,
user: {
authenticated: PropTypes.bool
}
};
LoginView.defaultProps = {
user: {
authenticated: false
}
}; };
export default reduxForm({ export default reduxForm({

View file

@ -27,6 +27,10 @@ class SignupView extends React.Component {
} }
render() { render() {
if (this.props.user.authenticated) {
this.gotoHomePage();
return null;
}
return ( return (
<div className="form-container"> <div className="form-container">
<div className="form-container__header"> <div className="form-container__header">
@ -84,7 +88,16 @@ function onSubmitFail(errors) {
} }
SignupView.propTypes = { SignupView.propTypes = {
previousPath: PropTypes.string.isRequired previousPath: PropTypes.string.isRequired,
user: {
authenticated: PropTypes.bool
}
};
SignupView.defaultProps = {
user: {
authenticated: false
}
}; };
export default reduxForm({ export default reduxForm({

View file

@ -15,7 +15,7 @@ $themes: (
primary-text-color: #333, primary-text-color: #333,
modal-button-color: #333, modal-button-color: #333,
heading-text-color: #333, heading-text-color: #333,
secondary-text-color: #6b6b6b, secondary-text-color: #a8a8a8,
inactive-text-color: #b5b5b5, inactive-text-color: #b5b5b5,
background-color: #fbfbfb, background-color: #fbfbfb,
preview-placeholder-color: #dcdcdc, preview-placeholder-color: #dcdcdc,
@ -30,7 +30,8 @@ $themes: (
button-active-color: $white, button-active-color: $white,
modal-background-color: #f4f4f4, modal-background-color: #f4f4f4,
modal-button-background-color: #e6e6e6, modal-button-background-color: #e6e6e6,
modal-border-color: #B9D0E1, modal-border-color: rgba(17, 17, 17, 0.3),
modal-boder-selected-color: #B9D0E1,
icon-color: $icon-color, icon-color: $icon-color,
icon-hover-color: $icon-hover-color, icon-hover-color: $icon-hover-color,
icon-toast-hover-color: $white, icon-toast-hover-color: $white,
@ -44,14 +45,17 @@ $themes: (
input-text-color: #333, input-text-color: #333,
input-border-color: #b5b5b5, input-border-color: #b5b5b5,
about-list-text-color: #4a4a4a, about-list-text-color: #4a4a4a,
search-background-color: #ebebeb search-background-color: #ebebeb,
dropdown-color: #414141,
keyboard-shortcut-color: #757575,
nav-hover-color: $p5js-pink,
), ),
dark: ( dark: (
logo-color: $p5js-pink, logo-color: $p5js-pink,
primary-text-color: $white, primary-text-color: $white,
modal-button-color: $white, modal-button-color: $white,
heading-text-color: $white, heading-text-color: $white,
secondary-text-color: #c2c2c2, secondary-text-color: #DADADA,
inactive-text-color: #b5b5b5, inactive-text-color: #b5b5b5,
background-color: #333, background-color: #333,
preview-placeholder-color: #dcdcdc, preview-placeholder-color: #dcdcdc,
@ -80,7 +84,10 @@ $themes: (
input-text-color: #333, input-text-color: #333,
input-border-color: #b5b5b5, input-border-color: #b5b5b5,
about-list-text-color: #f4f4f4, about-list-text-color: #f4f4f4,
search-background-color: #ebebeb search-background-color: #ebebeb,
dropdown-color: #dadada,
keyboard-shortcut-color: #B5B5B5,
nav-hover-color: $p5js-pink,
), ),
contrast: ( contrast: (
logo-color: $yellow, logo-color: $yellow,
@ -115,7 +122,10 @@ $themes: (
input-text-color: #333, input-text-color: #333,
input-border-color: #b5b5b5, input-border-color: #b5b5b5,
about-list-text-color: #f4f4f4, about-list-text-color: #f4f4f4,
search-background-color: $white search-background-color: $white,
dropdown-color: #e1e1e1,
keyboard-shortcut-color: #e1e1e1,
nav-hover-color: $yellow
) )
); );

View file

@ -1,34 +1,41 @@
.nav { .nav {
width: 100%; width: calc(100% - #{10 / $base-font-size}rem);
padding: #{10 / $base-font-size}rem #{32 / $base-font-size}rem 0 #{32 / $base-font-size}rem; height: #{42 / $base-font-size}rem;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
}
.nav__items-left, .nav__items-right {
@include themify() { @include themify() {
border-bottom: 1px dashed map-get($theme-map, 'inactive-text-color'); border-bottom: 1px dashed map-get($theme-map, 'inactive-text-color');
} }
& button {
padding: 0;
}
}
.nav__items-left, .nav__items-right {
list-style: none; list-style: none;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: flex-end; justify-content: flex-end;
padding: #{3 / $base-font-size}rem 0; height: 100%;
align-items: center;
} }
.nav__items-left { .nav__items-left {
& button { & button {
@include themify() { @include themify() {
color: getThemifyVariable('inactive-text-color'); color: getThemifyVariable('secondary-text-color');
} }
} }
} }
.nav__item { .nav__item {
position: relative; position: relative;
padding: 0 #{24 / $base-font-size}rem; padding: 0 #{10 / $base-font-size}rem;
text-align: center; display: flex;
align-items: center;
justify-content: center;
height: 100%;
} }
.nav__item:first-child { .nav__item:first-child {
@ -39,38 +46,57 @@
padding-right: #{15 / $base-font-size}rem; padding-right: #{15 / $base-font-size}rem;
} }
.nav__item-header {
margin-right: #{5 / $base-font-size}rem;
}
.nav__item:hover {
.nav__item-header {
@include themify() {
color: getThemifyVariable('nav-hover-color');
}
}
.nav__item-header-triangle polygon {
@include themify() {
fill: getThemifyVariable('nav-hover-color');
}
}
}
.nav__dropdown { .nav__dropdown {
@include themify() { @include themify() {
background-color: map-get($theme-map, 'modal-background-color'); background-color: map-get($theme-map, 'modal-background-color');
border: 1px solid map-get($theme-map, 'modal-border-color'); border: 1px solid map-get($theme-map, 'modal-border-color');
box-shadow: 0 0 18px getThemifyVariable('shadow-color'); box-shadow: 0 0 18px 0 getThemifyVariable('shadow-color');
color: getThemifyVariable('dropdown-color');
} }
@extend %hidden-element; display: none;
text-align: left; text-align: left;
width: #{140 / $base-font-size}rem; width: #{180 / $base-font-size}rem;
.nav__item:hover & { .nav__item--open & {
display: flex; display: flex;
position: absolute; position: absolute;
flex-direction: column; flex-direction: column;
top: #{-8 / $base-font-size}rem;; top: 4px;
left: #{-11 / $base-font-size}rem; left: 0;
height: auto; height: auto;
} }
padding-bottom: #{8 / $base-font-size}rem; z-index: 9999;
z-index: 1; border-radius: #{6 / $base-font-size}rem;
}
.nav__items-right {
padding-right: #{20 / $base-font-size}rem;
& .nav__dropdown {
width: #{121 / $base-font-size}rem;
}
} }
.nav__item-spacer { .nav__item-spacer {
@include themify() { @include themify() {
color: map-get($theme-map, 'inactive-text-color'); color: map-get($theme-map, 'inactive-text-color');
margin: 0 #{8 / $base-font-size}rem;
} }
padding: 0 #{15 / $base-font-size}rem;
}
.nav__dropdown li {
padding: #{4 / $base-font-size}rem #{16 / $base-font-size}rem;
width: 100%;
} }
.nav__dropdown a, button { .nav__dropdown a, button {
@ -93,9 +119,24 @@
@include themify() { @include themify() {
border-bottom: 1px dashed map-get($theme-map, 'inactive-text-color'); border-bottom: 1px dashed map-get($theme-map, 'inactive-text-color');
} }
margin-top: #{3 / $base-font-size}rem; height: #{(42 - 5) / $base-font-size}rem;
text-align: center; display: flex;
margin-bottom: #{4 / $base-font-size}rem; align-items: center;
justify-content: space-between;
margin: 0 #{16 / $base-font-size}rem;
cursor: pointer;
&:hover {
span {
@include themify() {
color: getThemifyVariable('nav-hover-color');
}
}
polygon {
@include themify() {
fill: getThemifyVariable('nav-hover-color');
}
}
}
} }
.nav__dropdown-heading a, .nav__dropdown-heading a:hover { .nav__dropdown-heading a, .nav__dropdown-heading a:hover {
@ -106,6 +147,48 @@
width: 100%; width: 100%;
} }
.nav__dropdown-heading svg {
transform-origin: 50% 50%;
transform: rotate(180deg);
}
.nav__dropdown-item {
height: #{32 / $base-font-size}rem;
width: 100%;
display: flex;
align-items: center;
padding: 0 #{16 / $base-font-size}rem;
cursor: pointer;
& button, & a {
@include themify() {
color: getThemifyVariable('dropdown-color');
}
}
&:hover {
@include themify() {
background-color: getThemifyVariable('button-background-hover-color');
color: getThemifyVariable('button-hover-color')
}
& button, & a {
@include themify() {
color: getThemifyVariable('button-hover-color');
}
}
}
& button, & a {
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
}
}
.nav__dropdown-item:last-child {
border-radius: 0 0 #{6 / $base-font-size}rem #{6 / $base-font-size}rem;
}
.nav__announce { .nav__announce {
position: absolute; position: absolute;
top: #{40 / $base-font-size}rem; top: #{40 / $base-font-size}rem;
@ -121,3 +204,25 @@
z-index: 0; z-index: 0;
} }
.nav__item-logo {
position: relative;
height: #{42 / $base-font-size}rem;
width: #{56 / $base-font-size}rem;
& span {
position: absolute;
}
}
.nav__keyboard-shortcut {
font-size: #{12 / $base-font-size}rem;
font-family: Inconsololata, monospace;
@include themify() {
color: getThemifyVariable('keyboard-shortcut-color');
}
.nav__dropdown-item:hover & {
@include themify() {
color: getThemifyVariable('button-hover-color');
}
}
}

View file

@ -21,6 +21,8 @@ $p5-dark-white: #FDFDFD;
$p5-dark-orange: #EE9900; $p5-dark-orange: #EE9900;
$p5-dark-lightgray: #E0D7D1; $p5-dark-lightgray: #E0D7D1;
$p5-dark-darkgray: #666666; $p5-dark-darkgray: #666666;
$p5-dark-green: #58a10b;
$p5-dark-goldbrown: #b58317;
$p5-dark-gutter: #f4f4f4; $p5-dark-gutter: #f4f4f4;
$p5-dark-number: #b5b5b5; $p5-dark-number: #b5b5b5;
@ -41,7 +43,7 @@ $p5-dark-activeline: rgb(207, 207, 207);
} }
.cm-s-p5-dark .cm-string { .cm-s-p5-dark .cm-string {
color: $p5-dark-lightblue; color: $p5-dark-green;
} }
.cm-s-p5-dark .cm-string-2 { .cm-s-p5-dark .cm-string-2 {
@ -49,11 +51,11 @@ $p5-dark-activeline: rgb(207, 207, 207);
} }
.cm-s-p5-dark .cm-number { .cm-s-p5-dark .cm-number {
color: $p5-dark-pink; color: $p5-dark-white;
} }
.cm-s-p5-dark .cm-keyword { .cm-s-p5-dark .cm-keyword {
color: $p5-light-green; color: $p5-dark-goldbrown;
} }
.cm-s-p5-dark .cm-variable { .cm-s-p5-dark .cm-variable {

View file

@ -12,7 +12,7 @@
$p5-light-lightbrown: #A67F59; $p5-light-lightbrown: #A67F59;
$p5-light-brown: #704F21; $p5-light-brown: #704F21;
$p5-light-black: #333; $p5-light-black: #333333;
$p5-light-pink: #D9328F; $p5-light-pink: #D9328F;
$p5-light-gray: #A0A0A0; $p5-light-gray: #A0A0A0;
$p5-light-lightblue: #00A1D3; $p5-light-lightblue: #00A1D3;
@ -21,6 +21,7 @@ $p5-light-white: #FDFDFD;
$p5-light-orange: #EE9900; $p5-light-orange: #EE9900;
$p5-light-lightgray: #E0D7D1; $p5-light-lightgray: #E0D7D1;
$p5-light-darkgray: #666666; $p5-light-darkgray: #666666;
$p5-light-green: #58a10b;
$p5-light-gutter: #f4f4f4; $p5-light-gutter: #f4f4f4;
$p5-light-number: #b5b5b5; $p5-light-number: #b5b5b5;
@ -37,11 +38,11 @@ $p5-light-activeline: rgb(207, 207, 207);
} }
.cm-s-p5-light .cm-def { .cm-s-p5-light .cm-def {
color: $p5-light-darkblue; color: $p5-light-lightblue;
} }
.cm-s-p5-light .cm-string { .cm-s-p5-light .cm-string {
color: $p5-light-lightblue; color: $p5-light-green;
} }
.cm-s-p5-light .cm-string-2 { .cm-s-p5-light .cm-string-2 {
@ -49,7 +50,7 @@ $p5-light-activeline: rgb(207, 207, 207);
} }
.cm-s-p5-light .cm-number { .cm-s-p5-light .cm-number {
color: $p5-light-pink; color: $p5-light-black;
} }
.cm-s-p5-light .cm-keyword { .cm-s-p5-light .cm-keyword {
@ -60,7 +61,7 @@ $p5-light-activeline: rgb(207, 207, 207);
color: $p5-light-lightblue; color: $p5-light-lightblue;
} }
.cm-s-p5-light .cm-variable-2 { .cm-s-p5-light .cm-variable2 {
color: $p5-light-black; color: $p5-light-black;
} }
@ -114,14 +115,14 @@ $p5-light-activeline: rgb(207, 207, 207);
} }
.cm-s-p5-light .cm-attribute { .cm-s-p5-light .cm-attribute {
color: $p5-light-lightblue; color: $p5-light-black;
} }
.cm-s-p5-light .cm-p5-function { .cm-s-p5-light .cm-p5-function {
color: $p5-light-darkblue; color: $p5-light-darkblue;
font-weight: bold;
} }
.cm-s-p5-light .cm-p5-variable { .cm-s-p5-light .cm-p5-variable {
color: $p5-light-pink; color: $p5-light-pink;
font-weight: bold;
} }

View file

@ -8,7 +8,7 @@ const metaKey = (() => {
return 'Ctrl'; return 'Ctrl';
})(); })();
const metaKeyName = metaKey === 'Cmd' ? 'Command' : 'Control'; const metaKeyName = metaKey === 'Cmd' ? '\u2318' : 'Ctrl';
export { export {
metaKey, metaKey,

View file

@ -136,6 +136,34 @@ export function getProjectsForUserId(userId) {
}); });
} }
export function getProjectAsset(req, res) {
Project.findById(req.params.project_id)
.populate('user', 'username')
.exec((err, project) => {
if (err) {
return res.status(404).send({ message: 'Project with that id does not exist' });
}
var assetURL = null;
var seekPath = req.params[0]; // req.params.asset_path;
var seekPathSplit = seekPath.split('/');
var seekFilename = seekPathSplit[seekPathSplit.length-1];
project.files.forEach((file) => {
if(file.name === seekFilename) {
assetURL = file.url;
}
});
if(!assetURL) {
return res.status(404).send({ message: 'Asset does not exist' });
} else {
request({ method: 'GET', url: assetURL, encoding: null }, (err, response, body) => {
res.send(body);
});
}
});
}
export function getProjectsForUserName(username) { export function getProjectsForUserName(username) {
} }

View file

@ -2,6 +2,7 @@ import { Router } from 'express';
import { renderIndex } from '../views/index'; import { renderIndex } from '../views/index';
import { get404Sketch } from '../views/404Page'; import { get404Sketch } from '../views/404Page';
import { userExists } from '../controllers/user.controller'; import { userExists } from '../controllers/user.controller';
import { getProjectAsset } from '../controllers/project.controller';
const router = new Router(); const router = new Router();
@ -13,6 +14,9 @@ router.route('/').get((req, res) => {
}); });
router.route('/signup').get((req, res) => { router.route('/signup').get((req, res) => {
if (req.user) {
return res.redirect('/');
}
res.send(renderIndex()); res.send(renderIndex());
}); });
@ -24,11 +28,18 @@ router.route('/:username/sketches/:project_id').get((req, res) => {
res.send(renderIndex()); res.send(renderIndex());
}); });
router.route('/:username/sketches/:project_id/*').get((req, res) => {
getProjectAsset(req,res);
});
// router.route('/full/:project_id').get((req, res) => { // router.route('/full/:project_id').get((req, res) => {
// res.send(renderIndex()); // res.send(renderIndex());
// }); // });
router.route('/login').get((req, res) => { router.route('/login').get((req, res) => {
if (req.user) {
return res.redirect('/');
}
res.send(renderIndex()); res.send(renderIndex());
}); });

@ -1 +1 @@
Subproject commit 0958be54482722821159cd3e07777988ee349f37 Subproject commit 51087283c090ab1f1f0f733a01fdf46ef1382544