fix a ton of eslint errors

This commit is contained in:
catarak 2016-06-23 18:29:55 -04:00
parent 77cc7b2a8c
commit 3d42da18a8
51 changed files with 1026 additions and 937 deletions

View file

@ -12,6 +12,34 @@
"classes": true "classes": true
}, },
"rules": { "rules": {
"react/no-multi-comp": 0,
"import/default": 0,
"import/no-duplicates": 0,
"import/named": 0,
"import/namespace": 0,
"import/no-unresolved": 0,
"import/no-named-as-default": 2,
"comma-dangle": 0, // not sure why airbnb turned this on. gross!
"indent": [2, 2, {"SwitchCase": 1}],
"no-console": 0,
"no-alert": 0,
"no-underscore-dangle": 0,
"max-len": [1, 120, 4],
},
"plugins": [
"react", "import"
],
"settings": {
"import/parser": "babel-eslint",
"import/resolve": {
"moduleDirectory": ["node_modules"]
}
},
"globals": {
"__DEVELOPMENT__": true,
"__CLIENT__": true,
"__SERVER__": true,
"__DISABLE_SSR__": true,
"__DEVTOOLS__": true
} }
} }

37
client/components/Nav.js Normal file
View file

@ -0,0 +1,37 @@
import React from 'react';
import { Link } from 'react-router';
class Nav extends React.Component {
render() {
return (
<nav className="nav">
<ul className="nav__items-left">
<li className="nav__item">
<p
className="nav__new"
onClick={this.props.createProject}
>
New
</p>
</li>
<li className="nav__item">
<p
className="nav__save"
onClick={this.props.saveProject}
>
Save
</p>
</li>
</ul>
<ul className="nav__items-right">
<li className="nav__item">
{this.props.user.authenticated && <p>Hello, {this.props.user.username}!</p>}
{!this.props.user.authenticated && <p><Link to='/login'>Login</Link> or <Link to='/signup'>Sign Up</Link></p>}
</li>
</ul>
</nav>
);
}
}
export default Nav;

View file

@ -1,33 +0,0 @@
import React from 'react'
import { Link } from 'react-router'
class Nav extends React.Component {
render() {
return (
<nav className="nav">
<ul className="nav__items-left">
<li className="nav__item">
<p className="nav__new"
onClick={this.props.createProject}>
New
</p>
</li>
<li className="nav__item">
<p className="nav__save"
onClick={this.props.saveProject}>
Save
</p>
</li>
</ul>
<ul className="nav__items-right">
<li className="nav__item">
{this.props.user.authenticated && <p>Hello, {this.props.user.username}!</p>}
{!this.props.user.authenticated && <p><Link to={`/login`}>Login</Link> or <Link to={`/signup`}>Sign Up</Link></p>}
</li>
</ul>
</nav>
);
}
}
export default Nav;

View file

@ -22,5 +22,5 @@ export const NEW_PROJECT = 'NEW_PROJECT';
export const SET_PROJECT = 'SET_PROJECT'; export const SET_PROJECT = 'SET_PROJECT';
//eventually, handle errors more specifically and better // eventually, handle errors more specifically and better
export const ERROR = 'ERROR'; export const ERROR = 'ERROR';

View file

@ -5,19 +5,18 @@ import DevTools from './components/DevTools';
class App extends React.Component { class App extends React.Component {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
this.state = {isMounted: false}; this.state = { isMounted: false };
} }
componentDidMount() { componentDidMount() {
this.setState({isMounted: true}); this.setState({ isMounted: true });
} }
render() { render() {
debugger;
return ( return (
<div className="app"> <div className="app">
{this.state.isMounted && !window.devToolsExtension && process.env.NODE_ENV === 'development' && <DevTools />} {this.state.isMounted && !window.devToolsExtension && process.env.NODE_ENV === 'development' && <DevTools />}
{ this.props.children } {this.props.children}
</div> </div>
); );
} }

View file

@ -1,9 +1,9 @@
import * as ActionTypes from '../../../constants'; import * as ActionTypes from '../../../constants';
export function updateFile(name, content) { export function updateFile(name, content) {
return { return {
type: ActionTypes.CHANGE_SELECTED_FILE, type: ActionTypes.CHANGE_SELECTED_FILE,
name: name, name,
content: content content
} };
} }

View file

@ -1,19 +1,19 @@
import * as ActionTypes from '../../../constants'; import * as ActionTypes from '../../../constants';
export function toggleSketch() { export function toggleSketch() {
return { return {
type: ActionTypes.TOGGLE_SKETCH type: ActionTypes.TOGGLE_SKETCH
} };
} }
export function startSketch() { export function startSketch() {
return { return {
type: ActionTypes.START_SKETCH type: ActionTypes.START_SKETCH
} };
} }
export function stopSketch() { export function stopSketch() {
return { return {
type: ActionTypes.STOP_SKETCH type: ActionTypes.STOP_SKETCH
} };
} }

View file

@ -1,25 +1,25 @@
import * as ActionTypes from '../../../constants'; import * as ActionTypes from '../../../constants';
export function openPreferences() { export function openPreferences() {
return { return {
type: ActionTypes.OPEN_PREFERENCES type: ActionTypes.OPEN_PREFERENCES
} };
} }
export function closePreferences() { export function closePreferences() {
return { return {
type: ActionTypes.CLOSE_PREFERENCES type: ActionTypes.CLOSE_PREFERENCES
} };
} }
export function increaseFont() { export function increaseFont() {
return { return {
type: ActionTypes.INCREASE_FONTSIZE type: ActionTypes.INCREASE_FONTSIZE
} };
} }
export function decreaseFont() { export function decreaseFont() {
return { return {
type: ActionTypes.DECREASE_FONTSIZE type: ActionTypes.DECREASE_FONTSIZE
} };
} }

View file

@ -1,89 +1,91 @@
import * as ActionTypes from '../../../constants'; import * as ActionTypes from '../../../constants';
import { browserHistory } from 'react-router' import { browserHistory } from 'react-router';
import axios from 'axios' import axios from 'axios';
const ROOT_URL = location.href.indexOf('localhost') > 0 ? 'http://localhost:8000/api' : '/api'; const ROOT_URL = location.href.indexOf('localhost') > 0 ? 'http://localhost:8000/api' : '/api';
export function getProject(id) { export function getProject(id) {
return function(dispatch) { return (dispatch) => {
axios.get(`${ROOT_URL}/projects/${id}`, {withCredentials: true}) axios.get(`${ROOT_URL}/projects/${id}`, { withCredentials: true })
.then(response => { .then(response => {
browserHistory.push(`/projects/${id}`); browserHistory.push(`/projects/${id}`);
dispatch({ dispatch({
type: ActionTypes.SET_PROJECT_NAME, type: ActionTypes.SET_PROJECT_NAME,
project: response.data project: response.data
}) });
}) })
.catch(response => dispatch({ .catch(response => dispatch({
type: ActionTypes.ERROR type: ActionTypes.ERROR,
})); error: response.data
} }));
};
} }
export function setProjectName(event) { export function setProjectName(event) {
var name = event.target.textContent; const name = event.target.textContent;
return { return {
type: ActionTypes.SET_PROJECT_NAME, type: ActionTypes.SET_PROJECT_NAME,
name: name name
} };
} }
export function saveProject() { export function saveProject() {
return function(dispatch, getState) { return (dispatch, getState) => {
var state = getState(); const state = getState();
var formParams = Object.assign({}, state.project); const formParams = Object.assign({}, state.project);
formParams.file = state.file; formParams.file = state.file;
debugger; if (state.id) {
if (state.id) { axios.put(`${ROOT_URL}/projects/${state.id}`, formParams, { withCredentials: true })
axios.put(`${ROOT_URL}/projects/${state.id}`, formParams, {withCredentials: true}) .then(() => {
.then(response => { dispatch({
dispatch({ type: ActionTypes.PROJECT_SAVE_SUCCESS
type: ActionTypes.PROJECT_SAVE_SUCCESS })
}) .catch((response) => dispatch({
.catch(response => dispatch({ type: ActionTypes.PROJECT_SAVE_FAIL,
type: ActionTypes.PROJECT_SAVE_FAIL error: response.data
})); }));
}) });
} } else {
else { axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true })
axios.post(`${ROOT_URL}/projects`, formParams, {withCredentials: true}) .then(response => {
.then(response => { browserHistory.push(`/projects/${response.data.id}`);
browserHistory.push('/projects/' + response.data.id); dispatch({
dispatch({ type: ActionTypes.NEW_PROJECT,
type: ActionTypes.NEW_PROJECT, name: response.data.name,
name: response.data.name, id: response.data.id,
id: response.data.id, file: {
file: { name: response.data.file.name,
name: response.data.file.name, content: response.data.file.content
content: response.data.file.content }
} });
}); })
}) .catch(response => dispatch({
.catch(response => dispatch({ type: ActionTypes.PROJECT_SAVE_FAIL,
type: ActionTypes.PROJECT_SAVE_FAIL error: response.data
})); }));
} }
} };
} }
export function createProject() { export function createProject() {
return function(dispatch) { return (dispatch) => {
axios.post(`${ROOT_URL}/projects`, {}, {withCredentials: true}) axios.post(`${ROOT_URL}/projects`, {}, { withCredentials: true })
.then(response => { .then(response => {
browserHistory.push('/projects/' + response.data.id); browserHistory.push(`/projects/${response.data.id}`);
dispatch({ dispatch({
type: ActionTypes.NEW_PROJECT, type: ActionTypes.NEW_PROJECT,
name: response.data.name, name: response.data.name,
id: response.data.id, id: response.data.id,
file: { file: {
name: response.data.file.name, name: response.data.file.name,
content: response.data.file.content content: response.data.file.content
} }
}); });
}) })
.catch(response => dispatch({ .catch(response => dispatch({
type: ActionTypes.PROJECT_SAVE_FAIL type: ActionTypes.PROJECT_SAVE_FAIL,
})); error: response.data
} }));
};
} }

View file

@ -0,0 +1,43 @@
import React from 'react';
import CodeMirror from 'codemirror';
import 'codemirror/mode/javascript/javascript';
import 'codemirror/addon/selection/active-line';
class Editor extends React.Component {
componentDidMount() {
this._cm = CodeMirror(this.refs.container, { // eslint-disable-line
theme: 'p5-widget',
value: this.props.content,
lineNumbers: true,
styleActiveLine: true,
mode: 'javascript'
});
this._cm.on('change', () => { // eslint-disable-line
this.props.updateFile('sketch.js', this._cm.getValue());
});
this._cm.getWrapperElement().style['font-size'] = `${this.props.fontSize}px`;
}
componentDidUpdate(prevProps) {
if (this.props.content !== prevProps.content &&
this.props.content !== this._cm.getValue()) {
this._cm.setValue(this.props.content); // eslint-disable-line no-underscore-dangle
}
if (this.props.fontSize !== prevProps.fontSize) {
this._cm.getWrapperElement().style['font-size'] = `${this.props.fontSize}px`;
}
}
componentWillUnmount() {
this._cm = null;
}
_cm: CodeMirror.Editor
render() {
return <div ref="container" className="editor-holder"></div>;
}
}
export default Editor;

View file

@ -1,42 +0,0 @@
import React from 'react';
import CodeMirror from 'codemirror';
import 'codemirror/mode/javascript/javascript';
import 'codemirror/addon/selection/active-line'
class Editor extends React.Component {
_cm: CodeMirror.Editor
componentDidMount() {
this._cm = CodeMirror(this.refs.container, {
theme: 'p5-widget',
value: this.props.content,
lineNumbers: true,
styleActiveLine: true,
mode: 'javascript'
});
this._cm.on('change', () => {
this.props.updateFile("sketch.js", this._cm.getValue());
});
this._cm.getWrapperElement().style['font-size'] = this.props.fontSize+'px';
}
componentDidUpdate(prevProps) {
if (this.props.content !== prevProps.content &&
this.props.content !== this._cm.getValue()) {
this._cm.setValue(this.props.content);
}
if (this.props.fontSize !== prevProps.fontSize) {
this._cm.getWrapperElement().style['font-size'] = this.props.fontSize+'px';
}
}
componentWillUnmount() {
this._cm = null;
}
render() {
return <div ref="container" className="editor-holder"></div>;
}
}
export default Editor;

View file

@ -0,0 +1,38 @@
import React from 'react';
const Isvg = require('react-inlinesvg');
const exitUrl = require('../../../images/exit.svg');
const plusUrl = require('../../../images/plus.svg');
const minusUrl = require('../../../images/minus.svg');
const classNames = require('classnames');
class Preferences extends React.Component {
render() {
const preferencesContainerClass = classNames({
preferences: true,
'preferences--selected': this.props.isVisible
});
return (
<div className={preferencesContainerClass} tabIndex="0">
<div className="preferences__heading">
<h2 className="preferences__title">Preferences</h2>
<button className="preferences__exit-button" onClick={this.props.closePreferences}>
<Isvg src={exitUrl} alt="Exit Preferences" />
</button>
</div>
<div className="preference">
<h3 className="preference__title">Text Size</h3>
<button className="preference__plus-button" onClick={this.props.decreaseFont}>
<Isvg src={minusUrl} alt="Decrease Font Size" />
</button>
<p className="preference__value">{this.props.fontSize}</p>
<button className="preference__minus-button" onClick={this.props.increaseFont}>
<Isvg src={plusUrl} alt="Increase Font Size" />
</button>
</div>
</div>
);
}
}
export default Preferences;

View file

@ -1,38 +0,0 @@
import React from 'react';
var Isvg = require('react-inlinesvg');
var exitUrl = require('../../../images/exit.svg');
var plusUrl = require('../../../images/plus.svg');
var minusUrl = require('../../../images/minus.svg');
var classNames = require('classnames');
class Preferences extends React.Component {
render() {
let preferencesContainerClass = classNames({
"preferences": true,
"preferences--selected": this.props.isVisible
});
return (
<div className={preferencesContainerClass} tabindex="0">
<div className="preferences__heading">
<h2 className="preferences__title">Preferences</h2>
<button className="preferences__exit-button" onClick={this.props.closePreferences}>
<Isvg src={exitUrl} alt="Exit Preferences" />
</button>
</div>
<div className="preference">
<h3 className="preference__title">Text Size</h3>
<button className="preference__plus-button" onClick={this.props.decreaseFont}>
<Isvg src={minusUrl} alt="Decrease Font Size" />
</button>
<p className="preference__value">{this.props.fontSize}</p>
<button className="preference__minus-button" onClick={this.props.increaseFont}>
<Isvg src={plusUrl} alt="Increase Font Size" />
</button>
</div>
</div>
);
}
}
export default Preferences;

View file

@ -0,0 +1,20 @@
import React from 'react';
class Preview extends React.Component {
componentDidMount() {
}
render() {
return (
<div>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.0/p5.min.js"></script>
<script type="text/javascript">
{this.props.content}
</script>
</div>
);
}
}
export default Preview;

View file

@ -1,21 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
class Preview extends React.Component {
componentDidMount() {
console.log(ReactDOM.findDOMNode(this));
}
render() {
return (
<div>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.0/p5.min.js"></script>
<script type="text/javascript">
{this.props.content}
</script>
</div>
);
}
}
export default Preview;

View file

@ -0,0 +1,67 @@
import React from 'react';
import ReactDOM from 'react-dom';
class PreviewFrame extends React.Component {
componentDidMount() {
if (this.props.isPlaying) {
this.renderFrameContents();
}
}
clearPreview() {
const doc = ReactDOM.findDOMNode(this).contentDocument;
doc.write('');
doc.close();
}
renderFrameContents() {
const doc = ReactDOM.findDOMNode(this).contentDocument;
if (doc.readyState === 'complete') {
renderSketch();
} else {
setTimeout(this.renderFrameContents, 0);
}
}
renderSketch() {
const doc = ReactDOM.findDOMNode(this).contentDocument;
this.clearPreview();
ReactDOM.render(this.props.head, doc.head);
const p5Script = doc.createElement('script');
p5Script.setAttribute('src', 'https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.0/p5.min.js');
doc.body.appendChild(p5Script);
const sketchScript = doc.createElement('script');
sketchScript.textContent = this.props.content;
doc.body.appendChild(sketchScript);
}
componentDidUpdate(prevProps, prevState) {
if (this.props.isPlaying !== prevProps.isPlaying) {
if (this.props.isPlaying) {
this.renderSketch();
} else {
this.clearPreview();
}
}
if (this.props.isPlaying && this.props.content !== prevProps.content) {
this.renderSketch();
}
}
componentWillUnmount() {
ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this).contentDocument.body);
}
render() {
return <iframe
className="preview-frame"
frameBorder="0"
sandbox="allow-scripts allow-pointer-lock allow-same-origin allow-popups allow-modals allow-forms"
title="sketch output"></iframe>;
}
}
export default PreviewFrame;

View file

@ -1,64 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
class PreviewFrame extends React.Component {
componentDidMount() {
if (this.props.isPlaying) {
this.renderFrameContents();
}
}
renderFrameContents() {
let doc = ReactDOM.findDOMNode(this).contentDocument;
if(doc.readyState === 'complete') {
renderSketch();
} else {
setTimeout(this.renderFrameContents, 0);
}
}
renderSketch() {
let doc = ReactDOM.findDOMNode(this).contentDocument;
this.clearPreview();
ReactDOM.render(this.props.head, doc.head);
let p5Script = doc.createElement('script');
p5Script.setAttribute('src', 'https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.0/p5.min.js');
doc.body.appendChild(p5Script);
let sketchScript = doc.createElement('script');
sketchScript.textContent = this.props.content;
doc.body.appendChild(sketchScript);
}
clearPreview() {
let doc = ReactDOM.findDOMNode(this).contentDocument;
doc.write('');
doc.close();
}
componentDidUpdate(prevProps, prevState) {
if (this.props.isPlaying != prevProps.isPlaying) {
if (this.props.isPlaying) {
this.renderSketch();
}
else {
this.clearPreview();
}
}
if (this.props.isPlaying && this.props.content != prevProps.content) {
this.renderSketch();
}
}
componentWillUnmount() {
ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this).contentDocument.body);
}
render() {
return <iframe className="preview-frame" frameBorder="0" sandbox="allow-scripts allow-pointer-lock allow-same-origin allow-popups allow-modals allow-forms" title="sketch output"></iframe>;
}
}
export default PreviewFrame;

View file

@ -0,0 +1,52 @@
import React from 'react';
const Isvg = require('react-inlinesvg');
const playUrl = require('../../../images/play.svg');
const logoUrl = require('../../../images/p5js-logo.svg');
const stopUrl = require('../../../images/stop.svg');
const preferencesUrl = require('../../../images/preferences.svg');
const classNames = require('classnames');
class Toolbar extends React.Component {
render() {
let playButtonClass = classNames({
'toolbar__play-button': true,
'toolbar__play-button--selected': this.props.isPlaying
});
let stopButtonClass = classNames({
'toolbar__stop-button': true,
'toolbar__stop-button--selected': !this.props.isPlaying
});
let preferencesButtonClass = classNames({
'toolbar__preferences-button': true,
'toolbar__preferences-button--selected': this.props.isPreferencesVisible
});
return (
<div className="toolbar">
<img className="toolbar__logo" src={logoUrl} alt="p5js Logo" />
<button className={playButtonClass} onClick={this.props.startSketch}>
<Isvg src={playUrl} alt="Play Sketch" />
</button>
<button className={stopButtonClass} onClick={this.props.stopSketch}>
<Isvg src={stopUrl} alt="Stop Sketch" />
</button>
<div className="toolbar__project-name-container">
<span
className="toolbar__project-name"
onBlur={this.props.setProjectName.bind(this)}
contentEditable
suppressContentEditableWarning
>
{this.props.projectName}
</span>
</div>
<button className={preferencesButtonClass} onClick={this.props.openPreferences}>
<Isvg src={preferencesUrl} alt="Show Preferences" />
</button>
</div>
);
}
}
export default Toolbar;

View file

@ -1,50 +0,0 @@
import React from 'react';
var Isvg = require('react-inlinesvg');
var playUrl = require('../../../images/play.svg');
var logoUrl = require('../../../images/p5js-logo.svg');
var stopUrl = require('../../../images/stop.svg');
var preferencesUrl = require('../../../images/preferences.svg');
var classNames = require('classnames');
class Toolbar extends React.Component {
render() {
let playButtonClass = classNames({
"toolbar__play-button": true,
"toolbar__play-button--selected": this.props.isPlaying
});
let stopButtonClass = classNames({
"toolbar__stop-button": true,
"toolbar__stop-button--selected": !this.props.isPlaying
});
let preferencesButtonClass = classNames({
"toolbar__preferences-button": true,
"toolbar__preferences-button--selected": this.props.isPreferencesVisible
});
return (
<div className="toolbar">
<img className="toolbar__logo" src={logoUrl} alt="p5js Logo"/>
<button className={playButtonClass} onClick={this.props.startSketch}>
<Isvg src={playUrl} alt="Play Sketch" />
</button>
<button className={stopButtonClass} onClick={this.props.stopSketch}>
<Isvg src={stopUrl} alt="Stop Sketch" />
</button>
<div className="toolbar__project-name-container">
<span className="toolbar__project-name"
onBlur={this.props.setProjectName.bind(this)}
contentEditable={true}
suppressContentEditableWarning={true}>
{this.props.projectName}
</span>
</div>
<button className={preferencesButtonClass} onClick={this.props.openPreferences}>
<Isvg src={preferencesUrl} alt="Show Preferences" />
</button>
</div>
);
}
}
export default Toolbar;

View file

@ -0,0 +1,83 @@
import React from 'react';
import Editor from '../components/Editor';
import PreviewFrame from '../components/PreviewFrame';
import Toolbar from '../components/Toolbar';
import Preferences from '../components/Preferences';
import Nav from '../../../components/Nav';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as FileActions from '../actions/files';
import * as IDEActions from '../actions/ide';
import * as PreferencesActions from '../actions/preferences';
import * as ProjectActions from '../actions/project';
class IDEView extends React.Component {
componentDidMount() {
if (this.props.params.project_id) {
const id = this.props.params.project_id;
this.props.getProject(id);
}
}
render() {
return (
<div className="ide">
<Nav
user={this.props.user}
createProject={this.props.createProject}
saveProject={this.props.saveProject}
/>
<Toolbar
className="Toolbar"
isPlaying={this.props.ide.isPlaying}
startSketch={this.props.startSketch}
stopSketch={this.props.stopSketch}
projectName={this.props.project.name}
setProjectName={this.props.setProjectName}
openPreferences={this.props.openPreferences}
isPreferencesVisible={this.props.preferences.isVisible}
/>
<Preferences
isVisible={this.props.preferences.isVisible}
closePreferences={this.props.closePreferences}
increaseFont={this.props.increaseFont}
decreaseFont={this.props.decreaseFont}
fontSize={this.props.preferences.fontSize}
/>
<Editor
content={this.props.file.content}
updateFile={this.props.updateFile}
fontSize={this.props.preferences.fontSize}
/>
<PreviewFrame
content={this.props.file.content}
head={
<link type="text/css" rel="stylesheet" href="/preview-styles.css" />
}
isPlaying={this.props.ide.isPlaying}
/>
</div>
);
}
}
function mapStateToProps(state) {
return {
file: state.file,
ide: state.ide,
preferences: state.preferences,
user: state.user,
project: state.project
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(Object.assign({},
FileActions,
ProjectActions,
IDEActions,
PreferencesActions),
dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(IDEView);

View file

@ -1,72 +0,0 @@
import React from 'react'
import Editor from '../components/Editor'
import PreviewFrame from '../components/PreviewFrame'
import Toolbar from '../components/Toolbar'
import Preferences from '../components/Preferences'
import Nav from '../../../components/Nav'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import * as FileActions from '../actions/files'
import * as IDEActions from '../actions/ide'
import * as PreferencesActions from '../actions/preferences'
import * as ProjectActions from '../actions/project'
class IDEView extends React.Component {
componentDidMount() {
if (this.props.params.project_id) {
const id = this.props.params.project_id
this.props.getProject(id);
}
}
render() {
return (
<div className="ide">
<Nav user={this.props.user}
createProject={this.props.createProject}
saveProject={this.props.saveProject}/>
<Toolbar
className="Toolbar"
isPlaying={this.props.ide.isPlaying}
startSketch={this.props.startSketch}
stopSketch={this.props.stopSketch}
projectName={this.props.project.name}
setProjectName={this.props.setProjectName}
openPreferences={this.props.openPreferences}
isPreferencesVisible={this.props.preferences.isVisible}/>
<Preferences
isVisible={this.props.preferences.isVisible}
closePreferences={this.props.closePreferences}
increaseFont={this.props.increaseFont}
decreaseFont={this.props.decreaseFont}
fontSize={this.props.preferences.fontSize}/>
<Editor
content={this.props.file.content}
updateFile={this.props.updateFile}
fontSize={this.props.preferences.fontSize} />
<PreviewFrame
content={this.props.file.content}
head={
<link type='text/css' rel='stylesheet' href='/preview-styles.css' />
}
isPlaying={this.props.ide.isPlaying}/>
</div>
)
}
}
function mapStateToProps(state) {
return {
file: state.file,
ide: state.ide,
preferences: state.preferences,
user: state.user,
project: state.project
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(Object.assign({}, FileActions, ProjectActions, IDEActions, PreferencesActions), dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(IDEView);

View file

@ -1,45 +1,45 @@
import * as ActionTypes from '../../../constants'; import * as ActionTypes from '../../../constants';
const initialState = { const initialState = {
name: "sketch.js", name: 'sketch.js',
content: `function setup() { content: `function setup() {
createCanvas(400, 400); createCanvas(400, 400);
} }
function draw() { function draw() {
background(220); background(220);
}` }`
} };
const file = (state = initialState, action) => { const file = (state = initialState, action) => {
switch (action.type) { switch (action.type) {
case ActionTypes.CHANGE_SELECTED_FILE: case ActionTypes.CHANGE_SELECTED_FILE:
return { return {
name: action.name, name: action.name,
content: action.content content: action.content
} };
case ActionTypes.NEW_PROJECT: case ActionTypes.NEW_PROJECT:
return { return {
name: action.file.name, name: action.file.name,
content: action.file.conent content: action.file.conent
} };
default: default:
return state return state;
} }
} };
export default file; export default file;
//i'll add this in when there are multiple files // i'll add this in when there are multiple files
// const files = (state = [], action) => { // const files = (state = [], action) => {
// switch (action.type) { // switch (action.type) {
// case ActionTypes.CHANGE_SELECTED_FILE: // case ActionTypes.CHANGE_SELECTED_FILE:
// //find the file with the name // //find the file with the name
// //update it // //update it
// //put in into the new array of files // //put in into the new array of files
// default: // default:
// return state // return state
// } // }
// } // }
// export default files // export default files

View file

@ -1,26 +1,26 @@
import * as ActionTypes from '../../../constants'; import * as ActionTypes from '../../../constants';
const initialState = { const initialState = {
isPlaying: false isPlaying: false
} };
const ide = (state = initialState, action) => { const ide = (state = initialState, action) => {
switch (action.type) { switch (action.type) {
case ActionTypes.TOGGLE_SKETCH: case ActionTypes.TOGGLE_SKETCH:
return { return {
isPlaying: !state.isPlaying isPlaying: !state.isPlaying
} };
case ActionTypes.START_SKETCH: case ActionTypes.START_SKETCH:
return { return {
isPlaying: true isPlaying: true
} };
case ActionTypes.STOP_SKETCH: case ActionTypes.STOP_SKETCH:
return { return {
isPlaying: false isPlaying: false
} };
default: default:
return state return state;
} }
} };
export default ide; export default ide;

View file

@ -1,35 +1,35 @@
import * as ActionTypes from '../../../constants'; import * as ActionTypes from '../../../constants';
const initialState = { const initialState = {
isVisible: false, isVisible: false,
fontSize: 18 fontSize: 18
} };
const preferences = (state = initialState, action) => { const preferences = (state = initialState, action) => {
switch (action.type) { switch (action.type) {
case ActionTypes.OPEN_PREFERENCES: case ActionTypes.OPEN_PREFERENCES:
return { return {
isVisible: true, isVisible: true,
fontSize: state.fontSize fontSize: state.fontSize
} };
case ActionTypes.CLOSE_PREFERENCES: case ActionTypes.CLOSE_PREFERENCES:
return { return {
isVisible: false, isVisible: false,
fontSize: state.fontSize fontSize: state.fontSize
} };
case ActionTypes.INCREASE_FONTSIZE: case ActionTypes.INCREASE_FONTSIZE:
return { return {
isVisible: state.isVisible, isVisible: state.isVisible,
fontSize: state.fontSize+2 fontSize: state.fontSize + 2
} };
case ActionTypes.DECREASE_FONTSIZE: case ActionTypes.DECREASE_FONTSIZE:
return { return {
isVisible: state.isVisible, isVisible: state.isVisible,
fontSize: state.fontSize-2 fontSize: state.fontSize - 2
} };
default: default:
return state return state;
} }
} };
export default preferences; export default preferences;

View file

@ -1,32 +1,32 @@
import * as ActionTypes from '../../../constants'; import * as ActionTypes from '../../../constants';
const initialState = { const initialState = {
name: "Hello p5.js" name: 'Hello p5.js'
} };
const project = (state = initialState, action) => { const project = (state = initialState, action) => {
switch (action.type) { switch (action.type) {
case ActionTypes.SET_PROJECT_NAME: case ActionTypes.SET_PROJECT_NAME:
return { return {
name: action.name name: action.name
} };
case ActionTypes.NEW_PROJECT: case ActionTypes.NEW_PROJECT:
return { return {
id: action.id, id: action.id,
name: action.name name: action.name
} };
case ActionTypes.SET_PROJECT: case ActionTypes.SET_PROJECT:
return { return {
id: action.project.id, id: action.project.id,
name: action.project.name, name: action.project.name,
file: { file: {
name: action.project.file.name, name: action.project.file.name,
content: action.project.file.content content: action.project.file.content
} }
} };
default: default:
return state; return state;
} }
} };
export default project; export default project;

View file

@ -1,51 +1,52 @@
import * as ActionTypes from '../../constants' import * as ActionTypes from '../../constants';
import { browserHistory } from 'react-router' import { browserHistory } from 'react-router';
import axios from 'axios' import axios from 'axios';
const ROOT_URL = location.href.indexOf('localhost') > 0 ? 'http://localhost:8000/api' : '/api'; const ROOT_URL = location.href.indexOf('localhost') > 0 ? 'http://localhost:8000/api' : '/api';
export function signUpUser(formValues) {
return function(dispatch) {
axios.post(`${ROOT_URL}/signup`, formValues, {withCredentials: true})
.then(response => {
dispatch({ type: ActionTypes.AUTH_USER,
user: response.data
});
browserHistory.push('/');
})
.catch(response => dispatch(authError(response.data.error)));
}
}
export function loginUser(formValues) {
return function(dispatch) {
axios.post(`${ROOT_URL}/login`, formValues, {withCredentials: true})
.then(response => {
dispatch({ type: ActionTypes.AUTH_USER,
user: response.data
});
browserHistory.push('/');
})
.catch(response => dispatch(authError(response.data.error)));
}
}
export function getUser() {
return function(dispatch) {
axios.get(`${ROOT_URL}/session`, {withCredentials: true})
.then(response => {
dispatch({type: ActionTypes.AUTH_USER,
user: response.data
});
})
.catch(response => dispatch(authError(response.data.error)));
}
}
export function authError(error) { export function authError(error) {
return { return {
type: ActionTypes.AUTH_ERROR, type: ActionTypes.AUTH_ERROR,
payload: error payload: error
}; };
} }
export function signUpUser(formValues) {
return (dispatch) => {
axios.post(`${ROOT_URL}/signup`, formValues, { withCredentials: true })
.then(response => {
dispatch({ type: ActionTypes.AUTH_USER,
user: response.data
});
browserHistory.push('/');
})
.catch(response => dispatch(authError(response.data.error)));
};
}
export function loginUser(formValues) {
return (dispatch) => {
axios.post(`${ROOT_URL}/login`, formValues, { withCredentials: true })
.then(response => {
dispatch({ type: ActionTypes.AUTH_USER,
user: response.data
});
browserHistory.push('/');
})
.catch(response => dispatch(authError(response.data.error)));
};
}
export function getUser() {
return (dispatch) => {
axios.get(`${ROOT_URL}/session`, { withCredentials: true })
.then(response => {
dispatch({
type: ActionTypes.AUTH_USER,
user: response.data
});
})
.catch(response => dispatch(authError(response.data.error)));
};
}

View file

@ -0,0 +1,34 @@
import React from 'react';
class LoginForm extends React.Component {
render() {
const { fields: { email, password }, handleSubmit } = this.props;
return (
<form className="login-form" onSubmit={handleSubmit(this.props.loginUser.bind(this))}>
<p className="login-form__field">
<label className="login-form__email-label" htmlFor="email">Email:</label>
<input
className="login-form__email-input"
id="email"
type="text"
placeholder="Email"
{...email}
/>
</p>
<p className="login-form__field">
<label className="signup-form__password-label" htmlFor="password">Password:</label>
<input
className="signup-form__password-input"
id="password"
type="password"
placeholder="Password"
{...password}
/>
</p>
<input type="submit" value="Login" />
</form>
);
}
}
export default LoginForm;

View file

@ -1,22 +0,0 @@
import React from 'react'
class LoginForm extends React.Component {
render() {
const {fields: {email, password}, handleSubmit } = this.props;
return (
<form className="login-form" onSubmit={handleSubmit(this.props.loginUser.bind(this))}>
<p className="login-form__field">
<label className="login-form__email-label" for="email">Email:</label>
<input className="login-form__email-input" id="email" type="text" placeholder="Email" {...email}/>
</p>
<p className="login-form__field">
<label className="signup-form__password-label" for="password">Password:</label>
<input className="signup-form__password-input" id="password" type="password" placeholder="Password" {...password}/>
</p>
<input type="submit" value="Login" />
</form>
)
}
}
export default LoginForm;

View file

@ -0,0 +1,58 @@
import React from 'react';
class SignupForm extends React.Component {
render() {
const { fields: { username, email, password, confirmPassword }, handleSubmit } = this.props;
return (
<form className="signup-form" onSubmit={handleSubmit(this.props.signUpUser.bind(this))}>
<p className="signup-form__field">
<label className="signup-form__username-label" htmlFor="username">Username:</label>
<input
className="signup-form__username-input"
id="username"
type="text"
placeholder="Username"
{...username}
/>
</p>
<p className="signup-form__field">
<label className="signup-form__email-label" htmlFor="email">Email:</label>
<input
className="signup-form__email-input"
id="email"
type="text"
placeholder="Email"
{...email}
/>
</p>
<p className="signup-form__field">
<label className="signup-form__password-label" htmlFor="password">Password:</label>
<input
className="signup-form__password-input"
id="password"
type="password"
placeholder="Password"
{...password}
/>
</p>
<p className="signup-form__field">
<label
className="signup-form__confirm-password-label"
htmlFor="confirm-password">
Confirm Password:
</label>
<input
className="signup-form__confirm-password-input"
id="confirm-password"
type="password"
placeholder="Confirm Password"
{...confirmPassword}
/>
</p>
<input type="submit" value="Sign Up" />
</form>
);
}
}
export default SignupForm;

View file

@ -1,30 +0,0 @@
import React from 'react'
class SignupForm extends React.Component {
render() {
const {fields: { username, email, password, confirmPassword }, handleSubmit} = this.props;
return (
<form className="signup-form" onSubmit={handleSubmit(this.props.signUpUser.bind(this))}>
<p className="signup-form__field">
<label className="signup-form__username-label" for="username">Username:</label>
<input className="signup-form__username-input" id="username" type="text" placeholder="Username" {...username}/>
</p>
<p className="signup-form__field">
<label className="signup-form__email-label" for="email">Email:</label>
<input className="signup-form__email-input" id="email" type="text" placeholder="Email" {...email}/>
</p>
<p className="signup-form__field">
<label className="signup-form__password-label" for="password">Password:</label>
<input className="signup-form__password-input" id="password" type="password" placeholder="Password" {...password}/>
</p>
<p className="signup-form__field">
<label className="signup-form__confirm-password-label" for="confirm-password">Confirm Password:</label>
<input className="signup-form__confirm-password-input" id="confirm-password" type="password" placeholder="Confirm Password" {...confirmPassword}/>
</p>
<input type="submit" value="Sign Up" />
</form>
)
}
}
export default SignupForm;

View file

@ -0,0 +1,37 @@
import React from 'react';
import { bindActionCreators } from 'redux';
import { reduxForm } from 'redux-form';
import * as UserActions from '../actions';
import LoginForm from '../components/LoginForm';
class LoginView extends React.Component {
render() {
return (
<div className="login">
<h1>Login</h1>
<LoginForm {...this.props} />
</div>
);
}
}
function mapStateToProps(state) {
return {
user: state.user
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(UserActions, dispatch);
}
function validate(formProps) {
const errors = {};
return errors;
}
export default reduxForm({
form: 'login',
fields: ['email', 'password'],
validate
}, mapStateToProps, mapDispatchToProps)(LoginView);

View file

@ -1,37 +0,0 @@
import React from 'react'
import { bindActionCreators } from 'redux'
import {reduxForm} from 'redux-form'
import * as UserActions from '../actions'
import LoginForm from '../components/LoginForm'
class LoginView extends React.Component {
render() {
return (
<div className="login">
<h1>Login</h1>
<LoginForm {...this.props} />
</div>
)
}
}
function mapStateToProps(state) {
return {
user: state.user
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(UserActions, dispatch);
}
function validate(formProps) {
const errors = {};
return errors;
}
export default reduxForm({
form: 'login',
fields: ['email', 'password'],
validate
}, mapStateToProps, mapDispatchToProps)(LoginView);

View file

@ -0,0 +1,38 @@
import React from 'react';
import { bindActionCreators } from 'redux';
import * as UserActions from '../actions';
import { reduxForm } from 'redux-form';
import SignupForm from '../components/SignupForm';
class SignupView extends React.Component {
render() {
return (
<div className="signup">
<h1>Sign Up</h1>
<SignupForm {...this.props} />
</div>
);
}
}
function mapStateToProps(state) {
return {
user: state.user
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(UserActions, dispatch);
}
function validate(formProps) {
const errors = {};
return errors;
}
// export default connect(mapStateToProps, mapDispatchToProps)(SignupView);
export default reduxForm({
form: 'signup',
fields: ['username', 'email', 'password', 'passwordConfirm'],
validate
}, mapStateToProps, mapDispatchToProps)(SignupView);

View file

@ -1,39 +0,0 @@
import React from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import * as UserActions from '../actions'
import { reduxForm } from 'redux-form'
import SignupForm from '../components/SignupForm'
class SignupView extends React.Component {
render() {
return (
<div className="signup">
<h1>Sign Up</h1>
<SignupForm {...this.props} />
</div>
)
}
}
function mapStateToProps(state) {
return {
user: state.user
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(UserActions, dispatch);
}
function validate(formProps) {
const errors = {};
return errors;
}
// export default connect(mapStateToProps, mapDispatchToProps)(SignupView);
export default reduxForm({
form: 'signup',
fields: ['username', 'email', 'password', 'passwordConfirm'],
validate
}, mapStateToProps, mapDispatchToProps)(SignupView);

View file

@ -1,17 +1,17 @@
import * as ActionTypes from '../../constants' import * as ActionTypes from '../../constants';
const user = (state = {authenticated: false}, action) => { const user = (state = { authenticated: false }, action) => {
switch (action.type) { switch (action.type) {
case ActionTypes.AUTH_USER: case ActionTypes.AUTH_USER:
return { ...action.user, return { ...action.user,
authenticated: true }; authenticated: true };
case ActionTypes.AUTH_ERROR: case ActionTypes.AUTH_ERROR:
return { return {
authenticated: false authenticated: false
} };
default: default:
return state; return state;
} }
} };
export default user; export default user;

View file

@ -1,18 +1,18 @@
import { combineReducers } from 'redux' import { combineReducers } from 'redux';
import file from './modules/IDE/reducers/files' import file 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 user from './modules/User/reducers' import user from './modules/User/reducers';
import { reducer as form } from 'redux-form' import { reducer as form } from 'redux-form';
const rootReducer = combineReducers({ const rootReducer = combineReducers({
form, form,
ide, ide,
file, file,
preferences, preferences,
user, user,
project project
}) });
export default rootReducer export default rootReducer;

View file

@ -1,24 +1,24 @@
import { Route, IndexRoute } from 'react-router' import { Route, IndexRoute } from 'react-router';
import React from 'react' import React from 'react';
import App from './modules/App/App' import App from './modules/App/App';
import IDEView from './modules/IDE/pages/IDEView' import IDEView from './modules/IDE/pages/IDEView';
import LoginView from './modules/User/pages/LoginView' import LoginView from './modules/User/pages/LoginView';
import SignupView from './modules/User/pages/SignupView' import SignupView from './modules/User/pages/SignupView';
import { getUser } from './modules/User/actions' import { getUser } from './modules/User/actions';
const routes = (store) => {
return (
<Route path="/" component={App}>
<IndexRoute component={IDEView} onEnter={checkAuth(store)}/>
<Route path="/login" component={LoginView}/>
<Route path="/signup" component={SignupView}/>
<Route path="/projects/:project_id" component={IDEView}/>
</Route>
);
}
const checkAuth = (store) => { const checkAuth = (store) => {
store.dispatch(getUser()); store.dispatch(getUser());
} };
const routes = (store) => {
return (
<Route path="/" component={App}>
<IndexRoute component={IDEView} onEnter={checkAuth(store)} />
<Route path="/login" component={LoginView} />
<Route path="/signup" component={SignupView} />
<Route path="/projects/:project_id" component={IDEView} />
</Route>
);
};
export default routes; export default routes;

View file

@ -1,10 +1,9 @@
import { createStore, applyMiddleware, compose } from 'redux' import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk' import thunk from 'redux-thunk';
import DevTools from './modules/App/components/DevTools' import DevTools from './modules/App/components/DevTools';
import rootReducer from './reducers' import rootReducer from './reducers';
export default function configureStore(initialState) { export default function configureStore(initialState) {
const enhancers = [ const enhancers = [
applyMiddleware(thunk), applyMiddleware(thunk),
]; ];
@ -18,15 +17,16 @@ export default function configureStore(initialState) {
rootReducer, rootReducer,
initialState, initialState,
compose(...enhancers) compose(...enhancers)
) );
if (module.hot) { if (module.hot) {
// Enable Webpack hot module replacement for reducers // Enable Webpack hot module replacement for reducers
module.hot.accept('./reducers', () => { module.hot.accept('./reducers', () => {
const nextRootReducer = require('./reducers').default const nextRootReducer = require('./reducers').default; // eslint-disable-line global-require
store.replaceReducer(nextRootReducer) store.replaceReducer(nextRootReducer);
}) });
} }
return store return store;
} }

View file

@ -11,7 +11,7 @@
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/catarak/p5.js-web-editor" "url": "git+https://github.com/catarak/p5.js-web-editor.git"
}, },
"devDependencies": { "devDependencies": {
"babel-eslint": "^6.1.0", "babel-eslint": "^6.1.0",
@ -56,6 +56,7 @@
"connect-mongo": "^1.2.0", "connect-mongo": "^1.2.0",
"cookie-parser": "^1.4.1", "cookie-parser": "^1.4.1",
"dotenv": "^2.0.0", "dotenv": "^2.0.0",
"eslint-loader": "^1.3.0",
"express": "^4.13.4", "express": "^4.13.4",
"express-session": "^1.13.0", "express-session": "^1.13.0",
"mongoose": "^4.4.16", "mongoose": "^4.4.16",

View file

@ -1,8 +1,8 @@
const passport = require('passport'); const passport = require('passport');
const GitHubStrategy = require('passport-github').Strategy; // const GitHubStrategy = require('passport-github').Strategy;
const LocalStrategy = require('passport-local').Strategy; const LocalStrategy = require('passport-local').Strategy;
import User from '../models/user' import User from '../models/user';
passport.serializeUser((user, done) => { passport.serializeUser((user, done) => {
done(null, user.id); done(null, user.id);
@ -18,23 +18,27 @@ passport.deserializeUser((id, done) => {
* Sign in using Email and Password. * Sign in using Email and Password.
*/ */
passport.use(new LocalStrategy({ usernameField: 'email' }, (email, password, done) => { passport.use(new LocalStrategy({ usernameField: 'email' }, (email, password, done) => {
User.findOne({ email: email.toLowerCase() }, (err, user) => { User.findOne({ email: email.toLowerCase() },
if (!user) { (err, user) => { // eslint-disable-line consistent-return
return done(null, false, { msg: `Email ${email} not found.` }); if (!user) {
} return done(null, false, { msg: `Email ${email} not found.` });
user.comparePassword(password, (err, isMatch) => {
if (isMatch) {
return done(null, user);
} }
return done(null, false, { msg: 'Invalid email or password.' }); user.comparePassword(password, (innerErr, isMatch) => {
if (innerErr) {
return done(innerErr);
}
if (isMatch) {
return done(null, user);
}
return done(null, false, { msg: 'Invalid email or password.' });
});
}); });
});
})); }));
/** /**
* Sign in with GitHub. * Sign in with GitHub.
*/ */
//TODO add dotenv so I can add github login // TODO add dotenv so I can add github login
// passport.use(new GitHubStrategy({ // passport.use(new GitHubStrategy({
// clientID: process.env.GITHUB_ID, // clientID: process.env.GITHUB_ID,
// clientSecret: process.env.GITHUB_SECRET, // clientSecret: process.env.GITHUB_SECRET,
@ -44,7 +48,8 @@ passport.use(new LocalStrategy({ usernameField: 'email' }, (email, password, don
// if (req.user) { // if (req.user) {
// User.findOne({ github: profile.id }, (err, existingUser) => { // User.findOne({ github: profile.id }, (err, existingUser) => {
// if (existingUser) { // if (existingUser) {
// req.flash('errors', { msg: 'There is already a GitHub account that belongs to you. Sign in with that account or delete it, then link it with your current account.' }); // req.flash('errors', { msg: 'There is already a GitHub account that belongs to you. '
// + 'Sign in with that account or delete it, then link it with your current account.' });
// done(err); // done(err);
// } else { // } else {
// User.findById(req.user.id, (err, user) => { // User.findById(req.user.id, (err, user) => {
@ -68,7 +73,8 @@ passport.use(new LocalStrategy({ usernameField: 'email' }, (email, password, don
// } // }
// User.findOne({ email: profile._json.email }, (err, existingEmailUser) => { // User.findOne({ email: profile._json.email }, (err, existingEmailUser) => {
// if (existingEmailUser) { // if (existingEmailUser) {
// req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with GitHub manually from Account Settings.' }); // req.flash('errors', { msg: 'There is already an account using this email address. Sign'
// + ' in to that account and link it with GitHub manually from Account Settings.' });
// done(err); // done(err);
// } else { // } else {
// const user = new User(); // const user = new User();

View file

@ -1,56 +1,56 @@
import Project from '../models/project' import Project from '../models/project';
export function createProject(req, res) { export function createProject(req, res) {
let projectValues = { const projectValues = {
user: req.user ? req.user._id : undefined, user: req.user ? req.user._id : undefined, // eslint-disable-line no-underscore-dangle
file: {} file: {}
} };
Object.assign(projectValues, req.body); Object.assign(projectValues, req.body);
Project.create(projectValues, function(err, newProject) { Project.create(projectValues, (err, newProject) => {
if (err) { return res.json({success: false}); } if (err) { return res.json({ success: false }); }
return res.json({ return res.json({
id: newProject._id, id: newProject._id, // eslint-disable-line no-underscore-dangle
name: newProject.name, name: newProject.name,
file: { file: {
name: newProject.file.name, name: newProject.file.name,
content: newProject.file.content content: newProject.file.content
} }
}); });
}); });
} }
export function updateProject(req, res) { export function updateProject(req, res) {
Project.update({_id: req.params.project_id}, Project.update({ _id: req.params.project_id },
{ {
$set: req.body $set: req.body
}, function(err, updatedProject) { }, (err, updatedProject) => {
if (err) { return res.json({success: false}) } if (err) { return res.json({ success: false }); }
return res.json({ return res.json({
id: updatedProject._id, id: updatedProject._id, // eslint-disable-line no-underscore-dangle
name: updatedProject.name, name: updatedProject.name,
file: { file: {
name: updatedProject.file.name, name: updatedProject.file.name,
content: updatedProject.file.content content: updatedProject.file.content
} }
}); });
}); });
} }
export function getProject(req, res) { export function getProject(req, res) {
Project.findById(req.params.project_id, function(err, project) { Project.findById(req.params.project_id, (err, project) => {
if (err) { if (err) {
return res.status(404).send({message: 'Project with that id does not exist'}); return res.status(404).send({ message: 'Project with that id does not exist' });
} }
return res.json({ return res.json({
id: project._id, id: project._id, // eslint-disable-line no-underscore-dangle
name: project.name, name: project.name,
file: { file: {
name: project.file.name, name: project.file.name,
content: project.file.conent content: project.file.conent
} }
}); });
}) });
} }

View file

@ -1,35 +1,28 @@
import User from '../models/user' import passport from 'passport';
import passport from 'passport'
import path from 'path'
export function destroySession(req, res) {
}
export function createSession(req, res, next) { export function createSession(req, res, next) {
passport.authenticate('local', (err, user, info) => { passport.authenticate('local', (err, user) => { // eslint-disable-line consistent-return
if (err) { return next(err); } if (err) { return next(err); }
if (!user) { if (!user) {
return res.status(401).send({ error: 'Invalid username or password' }); return res.status(401).send({ error: 'Invalid username or password' });
} }
req.logIn(user, (err) => { req.logIn(user, (innerErr) => {
if (err) { return next(err); } if (innerErr) { return next(innerErr); }
res.json({ return res.json({
email: req.user.email, email: req.user.email,
username: req.user.username username: req.user.username
}); });
}); });
})(req, res, next); })(req, res, next);
} }
export function getSession(req, res, next) { export function getSession(req, res) {
if (req.user) { if (req.user) {
return res.json({ return res.json({
email: req.user.email, email: req.user.email,
username: req.user.username username: req.user.username
}); });
} }
res.status(404).send({message: 'Session does not exist'}); return res.status(404).send({ message: 'Session does not exist' });
} }

View file

@ -1,30 +1,30 @@
import User from '../models/user' import User from '../models/user';
import passport from 'passport'
import path from 'path'
export function createUser(req, res, next) { export function createUser(req, res, next) {
const user = new User({ const user = new User({
username: req.body.username, username: req.body.username,
email: req.body.email, email: req.body.email,
password: req.body.password password: req.body.password
}); });
User.findOne({email: req.body.email}, (err, existingUser) => { User.findOne({ email: req.body.email },
if (existingUser) { (err, existingUser) => { // eslint-disable-line consistent-return
return res.status(422).send({ error: 'Email is in use' }); if (err) { res.status(404).send({ error: err }); }
}
user.save((err) => {
if (err) { return next(err); }
req.logIn(user, (err) => {
if (err) {
return next(err);
}
res.json({
email: req.user.email,
username: req.user.username
});
});
});
});
if (existingUser) {
return res.status(422).send({ error: 'Email is in use' });
}
user.save((saveErr) => { // eslint-disable-line consistent-return
if (saveErr) { return next(saveErr); }
req.logIn(user, (loginErr) => { // eslint-disable-line consistent-return
if (loginErr) {
return next(loginErr);
}
res.json({
email: req.user.email,
username: req.user.username
});
});
});
});
} }

View file

@ -3,15 +3,15 @@ const Schema = mongoose.Schema;
import shortid from 'shortid'; import shortid from 'shortid';
const fileSchema = new Schema({ const fileSchema = new Schema({
name: {type: String, default: 'sketch.js'}, name: { type: String, default: 'sketch.js' },
content: {type: String} content: { type: String }
}, {timestamps: true}); }, { timestamps: true });
const projectSchema = new Schema({ const projectSchema = new Schema({
name: {type: String, default: "Hello p5.js, it's the server"}, name: { type: String, default: "Hello p5.js, it's the server" },
user: {type: Schema.Types.ObjectId, ref: 'User'}, user: { type: Schema.Types.ObjectId, ref: 'User' },
file: {type: fileSchema}, file: { type: fileSchema },
_id: {type: String, default: shortid.generate} _id: { type: String, default: shortid.generate }
}, {timestamps: true}); }, { timestamps: true });
export default mongoose.model('Project', projectSchema); export default mongoose.model('Project', projectSchema);

View file

@ -3,27 +3,27 @@ const Schema = mongoose.Schema;
const bcrypt = require('bcrypt-nodejs'); const bcrypt = require('bcrypt-nodejs');
const userSchema = new Schema({ const userSchema = new Schema({
name: { type: String, default: '' }, name: { type: String, default: '' },
username: { type: String, required: true, unique: true}, username: { type: String, required: true, unique: true },
password: { type: String }, password: { type: String },
github: { type: String }, github: { type: String },
email: { type: String, unique: true }, email: { type: String, unique: true },
tokens: Array, tokens: Array,
admin: { type: Boolean, default: false } admin: { type: Boolean, default: false }
}, {timestamps: true}); }, { timestamps: true });
/** /**
* Password hash middleware. * Password hash middleware.
*/ */
userSchema.pre('save', function (next) { userSchema.pre('save', (next) => { // eslint-disable-line consistent-return
const user = this; const user = this;
if (!user.isModified('password')) { return next(); } if (!user.isModified('password')) { return next(); }
bcrypt.genSalt(10, (err, salt) => { bcrypt.genSalt(10, (err, salt) => { // eslint-disable-line consistent-return
if (err) { return next(err); } if (err) { return next(err); }
bcrypt.hash(user.password, salt, null, (err, hash) => { bcrypt.hash(user.password, salt, null, (innerErr, hash) => {
if (err) { return next(err); } if (innerErr) { return next(innerErr); }
user.password = hash; user.password = hash;
next(); return next();
}); });
}); });
}); });
@ -31,7 +31,7 @@ userSchema.pre('save', function (next) {
/** /**
* Helper method for validating user's password. * Helper method for validating user's password.
*/ */
userSchema.methods.comparePassword = function (candidatePassword, cb) { userSchema.methods.comparePassword = (candidatePassword, cb) => {
bcrypt.compare(candidatePassword, this.password, (err, isMatch) => { bcrypt.compare(candidatePassword, this.password, (err, isMatch) => {
cb(err, isMatch); cb(err, isMatch);
}); });

View file

@ -1,24 +1,24 @@
import {Router} from 'express' import { Router } from 'express';
const router = new Router(); const router = new Router();
import path from 'path' import path from 'path';
// this is intended to be a temporary file // this is intended to be a temporary file
// until i figure out isomorphic rendering // until i figure out isomorphic rendering
router.route('/').get(function(req, res) { router.route('/').get((req, res) => {
res.sendFile(path.resolve(__dirname + '/../../index.html')); res.sendFile(path.resolve(`${__dirname}/../../index.html`));
}); });
router.route('/signup').get(function(req, res) { router.route('/signup').get((req, res) => {
res.sendFile(path.resolve(__dirname + '/../../index.html')); res.sendFile(path.resolve(`${__dirname}/../../index.html`));
}); });
router.route('/projects/:project_id').get(function(req, res) { router.route('/projects/:project_id').get((req, res) => {
res.sendFile(path.resolve(__dirname + '/../../index.html')); res.sendFile(path.resolve(`${__dirname}/../../index.html`));
}); });
router.route('/login').get(function(req, res) { router.route('/login').get((req, res) => {
res.sendFile(path.resolve(__dirname + '/../../index.html')); res.sendFile(path.resolve(`${__dirname}/../../index.html`));
}); });
export default router; export default router;

View file

@ -1,15 +1,12 @@
import { Router } from 'express'; import { Router } from 'express';
import * as SessionController from '../controllers/session.controller'; import * as SessionController from '../controllers/session.controller';
import passport from 'passport';
const router = new Router(); const router = new Router();
router.route('/login').post(SessionController.createSession); router.route('/login').post(SessionController.createSession);
router.route('/logout').get(SessionController.destroySession);
router.route('/session').get(SessionController.getSession); router.route('/session').get(SessionController.getSession);
export default router; export default router;
//TODO add github authentication stuff // TODO add github authentication stuff

View file

@ -7,7 +7,7 @@ const MongoStore = require('connect-mongo')(session);
import passport from 'passport'; import passport from 'passport';
import path from 'path'; import path from 'path';
//Webpack Requirements // Webpack Requirements
import webpack from 'webpack'; import webpack from 'webpack';
import config from '../webpack.config'; import config from '../webpack.config';
import webpackDevMiddleware from 'webpack-dev-middleware'; import webpackDevMiddleware from 'webpack-dev-middleware';
@ -15,22 +15,22 @@ import webpackHotMiddleware from 'webpack-hot-middleware';
const app = new Express(); const app = new Express();
//add check if production environment here // add check if production environment here
const compiler = webpack(config); const compiler = webpack(config);
app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath })); app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath }));
app.use(webpackHotMiddleware(compiler)); app.use(webpackHotMiddleware(compiler));
//Import all required modules // Import all required modules
import serverConfig from './config'; import serverConfig from './config';
import users from './routes/user.routes'; import users from './routes/user.routes';
import sessions from './routes/session.routes'; import sessions from './routes/session.routes';
import projects from './routes/project.routes'; import projects from './routes/project.routes';
import serverRoutes from './routes/server.routes'; import serverRoutes from './routes/server.routes';
//Body parser, cookie parser, sessions, serve public assets // Body parser, cookie parser, sessions, serve public assets
app.use(Express.static(path.resolve(__dirname, '../static'))); app.use(Express.static(path.resolve(__dirname, '../static')));
app.use(bodyParser.urlencoded({extended: true})); app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json()); app.use(bodyParser.json());
app.use(cookieParser()); app.use(cookieParser());
app.use(session({ app.use(session({
@ -53,13 +53,15 @@ app.use(passport.session());
app.use('/api', users); app.use('/api', users);
app.use('/api', sessions); app.use('/api', sessions);
app.use('/api', projects); app.use('/api', projects);
//this is supposed to be TEMPORARY -- until i figure out // this is supposed to be TEMPORARY -- until i figure out
// isomorphic rendering // isomorphic rendering
app.use('/', serverRoutes); app.use('/', serverRoutes);
const passportConfig = require('./config/passport'); // configure passport
// const passportConfig = require('./config/passport');
require('./config/passport');
//Connect to MongoDB // Connect to MongoDB
// mongoose.connect(process.env.MONGODB_URI || process.env.MONGOLAB_URI); // mongoose.connect(process.env.MONGODB_URI || process.env.MONGOLAB_URI);
mongoose.connect(serverConfig.mongoURL); mongoose.connect(serverConfig.mongoURL);
mongoose.connection.on('error', () => { mongoose.connection.on('error', () => {
@ -67,9 +69,9 @@ mongoose.connection.on('error', () => {
process.exit(1); process.exit(1);
}); });
app.get("/", function(req, res) { app.get('/', (req, res) => {
res.sendFile(path.resolve(__dirname + '/../index.html')); res.sendFile(path.resolve(`${__dirname}/../index.html`));
}) });
// start app // start app
app.listen(serverConfig.port, (error) => { app.listen(serverConfig.port, (error) => {
@ -79,3 +81,4 @@ app.listen(serverConfig.port, (error) => {
}); });
export default app; export default app;