update eslint to latest version, fix lots of linting errors (#308)
* update eslint and dependencies, fix linting errors that can be fixed with --fix * fix lots of linting errors * update eslintrc, fix some linting errors * fix all server side linting errors, untested * fix errors that fixing linting errors had caused * fix client side eslint errors * fix client side linting errors * fix refs lint errors * fix more linting errors * update eslint and dependencies, fix linting errors that can be fixed with --fix * fix lots of linting errors * update eslintrc, fix some linting errors * fix all server side linting errors, untested * fix errors that fixing linting errors had caused * fix client side eslint errors * fix client side linting errors * fix refs lint errors * fix more linting errors * fix some accessibility linting errors * fix a lot of linting errors * fix a billion more linting errors * hopefully fix all linting errors, still need to test * fix bugs that fixing linting had caused
This commit is contained in:
parent
059308fbfe
commit
e87390adb9
73 changed files with 964 additions and 733 deletions
15
.eslintrc
15
.eslintrc
|
@ -24,7 +24,20 @@
|
|||
"no-console": 0,
|
||||
"no-alert": 0,
|
||||
"no-underscore-dangle": 0,
|
||||
"max-len": [1, 120, 4],
|
||||
"max-len": [1, 120, 2, {ignoreComments: true}],
|
||||
"quote-props": [1, "consistent-as-needed"],
|
||||
"no-unused-vars": [1, {"vars": "local", "args": "none"}],
|
||||
"consistent-return": ["error", { "treatUndefinedAsUnspecified": true }],
|
||||
"no-param-reassign": [2, { "props": false }],
|
||||
"react/self-closing-comp": ["error", {
|
||||
"component": true,
|
||||
"html": false
|
||||
}],
|
||||
"newline-per-chained-call": 0,
|
||||
"react/prefer-stateless-function": [2,
|
||||
{ "ignorePureComponents": true
|
||||
}],
|
||||
"class-methods-use-this": 0
|
||||
},
|
||||
"plugins": [
|
||||
"react", "jsx-a11y", "import"
|
||||
|
|
|
@ -1,85 +1,86 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
function Nav(props) {
|
||||
class Nav extends React.PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<nav className="nav" role="navigation" title="main-navigation">
|
||||
<ul className="nav__items-left" title="project-menu">
|
||||
<li className="nav__item">
|
||||
<a
|
||||
<button
|
||||
className="nav__new"
|
||||
onClick={() => {
|
||||
if (!props.unsavedChanges) {
|
||||
props.newProject();
|
||||
} else if (props.warnIfUnsavedChanges()) {
|
||||
props.newProject();
|
||||
if (!this.props.unsavedChanges) {
|
||||
this.props.newProject();
|
||||
} else if (this.props.warnIfUnsavedChanges()) {
|
||||
this.props.newProject();
|
||||
}
|
||||
}}
|
||||
>
|
||||
New
|
||||
</a>
|
||||
</button>
|
||||
</li>
|
||||
{(() => { // eslint-disable-line
|
||||
if (!props.project.owner || props.project.owner && props.project.owner.id === props.user.id) {
|
||||
if (!this.props.project.owner || (this.props.project.owner && this.props.project.owner.id === this.props.user.id)) {
|
||||
return (
|
||||
<li className="nav__item">
|
||||
<a
|
||||
<button
|
||||
className="nav__save"
|
||||
onClick={() => {
|
||||
if (props.user.authenticated) {
|
||||
props.saveProject();
|
||||
if (this.props.user.authenticated) {
|
||||
this.props.saveProject();
|
||||
} else {
|
||||
props.showErrorModal('forceAuthentication');
|
||||
this.props.showErrorModal('forceAuthentication');
|
||||
}
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</a>
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
})()}
|
||||
{(() => { // eslint-disable-line
|
||||
if (props.project.id && props.user.authenticated) {
|
||||
if (this.props.project.id && this.props.user.authenticated) {
|
||||
return (
|
||||
<li className="nav__item">
|
||||
<a className="nav__clone" onClick={props.cloneProject}>
|
||||
<button className="nav__clone" onClick={this.props.cloneProject}>
|
||||
Duplicate
|
||||
</a>
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
})()}
|
||||
{(() => { // eslint-disable-line
|
||||
if (props.project.id) {
|
||||
if (this.props.project.id) {
|
||||
return (
|
||||
<li className="nav__item">
|
||||
<a className="nav__export" onClick={() => props.exportProjectAsZip(props.project.id)}>
|
||||
<button className="nav__export" onClick={() => this.props.exportProjectAsZip(this.props.project.id)}>
|
||||
Download
|
||||
</a>
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
})()}
|
||||
{(() => { // eslint-disable-line
|
||||
if (props.project.id) {
|
||||
if (this.props.project.id) {
|
||||
return (
|
||||
<li className="nav__item">
|
||||
<a onClick={props.showShareModal}>
|
||||
<button onClick={this.props.showShareModal}>
|
||||
Share
|
||||
</a>
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
})()}
|
||||
{(() => { // eslint-disable-line
|
||||
if (props.user.authenticated) {
|
||||
if (this.props.user.authenticated) {
|
||||
return (
|
||||
<li className="nav__item">
|
||||
<p className="nav__open">
|
||||
<Link
|
||||
to={`/${props.user.username}/sketches`}
|
||||
onClick={props.stopSketch}
|
||||
to={`/${this.props.user.username}/sketches`}
|
||||
onClick={this.props.stopSketch}
|
||||
>
|
||||
Open
|
||||
</Link>
|
||||
|
@ -100,6 +101,7 @@ function Nav(props) {
|
|||
<a
|
||||
href="https://p5js.org/reference/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Reference</a>
|
||||
</p>
|
||||
</li>
|
||||
|
@ -113,7 +115,7 @@ function Nav(props) {
|
|||
</ul>
|
||||
<ul className="nav__items-right" title="user-menu">
|
||||
{(() => {
|
||||
if (!props.user.authenticated) {
|
||||
if (!this.props.user.authenticated) {
|
||||
return (
|
||||
<li className="nav__item">
|
||||
<p>
|
||||
|
@ -124,30 +126,35 @@ function Nav(props) {
|
|||
}
|
||||
return (
|
||||
<li className="nav__item">
|
||||
<a>Hello, {props.user.username}!</a>
|
||||
<a>Hello, {this.props.user.username}!</a>
|
||||
<ul className="nav__dropdown">
|
||||
<li className="nav__dropdown-heading">
|
||||
<a>Hello, {props.user.username}!</a>
|
||||
<a>Hello, {this.props.user.username}!</a>
|
||||
</li>
|
||||
<li>
|
||||
<Link to={`/${props.user.username}/sketches`}>
|
||||
<Link to={`/${this.props.user.username}/sketches`}>
|
||||
My sketches
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<a onClick={props.logoutUser} >
|
||||
<button onClick={this.props.logoutUser} >
|
||||
Log out
|
||||
</a>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
})()}
|
||||
</ul>
|
||||
<div className="nav__announce">This is a preview version of the editor, that has not yet been officially released. It is in development, you can report bugs <a href="https://github.com/processing/p5.js-web-editor/issues" target="_blank">here</a>. Please use with caution.</div>
|
||||
<div className="nav__announce">
|
||||
This is a preview version of the editor, that has not yet been officially released. It is in development, you can report bugs
|
||||
<a href="https://github.com/processing/p5.js-web-editor/issues" target="_blank" rel="noopener noreferrer">here</a>.
|
||||
Please use with caution.
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Nav.propTypes = {
|
||||
newProject: PropTypes.func.isRequired,
|
||||
|
@ -168,7 +175,16 @@ Nav.propTypes = {
|
|||
logoutUser: PropTypes.func.isRequired,
|
||||
stopSketch: PropTypes.func.isRequired,
|
||||
showShareModal: PropTypes.func.isRequired,
|
||||
showErrorModal: PropTypes.func.isRequired
|
||||
showErrorModal: PropTypes.func.isRequired,
|
||||
unsavedChanges: PropTypes.bool.isRequired,
|
||||
warnIfUnsavedChanges: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
Nav.defaultProps = {
|
||||
project: {
|
||||
id: undefined,
|
||||
owner: undefined
|
||||
}
|
||||
};
|
||||
|
||||
export default Nav;
|
||||
|
|
|
@ -53,7 +53,6 @@ export const COLLAPSE_CONSOLE = 'COLLAPSE_CONSOLE';
|
|||
|
||||
export const UPDATE_LINT_MESSAGE = 'UPDATE_LINT_MESSAGE';
|
||||
export const CLEAR_LINT_MESSAGE = 'CLEAR_LINT_MESSAGE';
|
||||
export const UPDATE_LINENUMBER = 'UPDATE_LINENUMBER';
|
||||
|
||||
export const SHOW_FILE_OPTIONS = 'SHOW_FILE_OPTIONS';
|
||||
export const HIDE_FILE_OPTIONS = 'HIDE_FILE_OPTIONS';
|
||||
|
|
|
@ -30,11 +30,15 @@ class App extends React.Component {
|
|||
}
|
||||
|
||||
App.propTypes = {
|
||||
children: PropTypes.object,
|
||||
children: PropTypes.element,
|
||||
location: PropTypes.shape({
|
||||
pathname: PropTypes.string
|
||||
}),
|
||||
}).isRequired,
|
||||
setPreviousPath: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
App.defaultProps = {
|
||||
children: null
|
||||
};
|
||||
|
||||
export default connect(() => ({}), { setPreviousPath })(App);
|
||||
|
|
|
@ -11,7 +11,11 @@ function Overlay(props) {
|
|||
}
|
||||
|
||||
Overlay.propTypes = {
|
||||
children: PropTypes.object
|
||||
children: PropTypes.element
|
||||
};
|
||||
|
||||
Overlay.defaultProps = {
|
||||
children: null
|
||||
};
|
||||
|
||||
export default Overlay;
|
||||
|
|
|
@ -14,10 +14,3 @@ export function clearLintMessage() {
|
|||
type: ActionTypes.CLEAR_LINT_MESSAGE
|
||||
};
|
||||
}
|
||||
|
||||
export function updateLineNumber(lineNumber) {
|
||||
return {
|
||||
type: ActionTypes.UPDATE_LINENUMBER,
|
||||
lineNumber
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import * as ActionTypes from '../../../constants';
|
||||
import axios from 'axios';
|
||||
import objectID from 'bson-objectid';
|
||||
import blobUtil from 'blob-util';
|
||||
import { setUnsavedChanges } from './ide';
|
||||
import { reset } from 'redux-form';
|
||||
import * as ActionTypes from '../../../constants';
|
||||
import { setUnsavedChanges } from './ide';
|
||||
|
||||
const ROOT_URL = location.href.indexOf('localhost') > 0 ? 'http://localhost:8000/api' : '/api';
|
||||
|
||||
|
@ -18,11 +18,11 @@ function createUniqueName(name, parentId, files) {
|
|||
.children.map(childFileId => files.find(file => file.id === childFileId));
|
||||
let testName = name;
|
||||
let index = 1;
|
||||
let existingName = siblingFiles.find((file) => name === file.name);
|
||||
let existingName = siblingFiles.find(file => name === file.name);
|
||||
|
||||
while (existingName) {
|
||||
testName = appendToFilename(name, `-${index}`);
|
||||
index++;
|
||||
index += 1;
|
||||
existingName = siblingFiles.find((file) => testName === file.name); // eslint-disable-line
|
||||
}
|
||||
return testName;
|
||||
|
@ -56,7 +56,7 @@ export function createFile(formProps) {
|
|||
children: []
|
||||
};
|
||||
axios.post(`${ROOT_URL}/projects/${state.project.id}/files`, postParams, { withCredentials: true })
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: ActionTypes.CREATE_FILE,
|
||||
...response.data,
|
||||
|
@ -113,7 +113,7 @@ export function createFolder(formProps) {
|
|||
fileType: 'folder'
|
||||
};
|
||||
axios.post(`${ROOT_URL}/projects/${state.project.id}/files`, postParams, { withCredentials: true })
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: ActionTypes.CREATE_FILE,
|
||||
...response.data,
|
||||
|
@ -200,7 +200,7 @@ export function deleteFile(id, parentId) {
|
|||
parentId
|
||||
});
|
||||
})
|
||||
.catch(response => {
|
||||
.catch((response) => {
|
||||
dispatch({
|
||||
type: ActionTypes.ERROR,
|
||||
error: response.data
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as ActionTypes from '../../../constants';
|
||||
import axios from 'axios';
|
||||
import * as ActionTypes from '../../../constants';
|
||||
|
||||
const ROOT_URL = location.href.indexOf('localhost') > 0 ? 'http://localhost:8000/api' : '/api';
|
||||
|
||||
|
@ -7,7 +7,7 @@ function updatePreferences(formParams, dispatch) {
|
|||
axios.put(`${ROOT_URL}/preferences`, formParams, { withCredentials: true })
|
||||
.then(() => {
|
||||
})
|
||||
.catch((response) => dispatch({
|
||||
.catch(response => dispatch({
|
||||
type: ActionTypes.ERROR,
|
||||
error: response.data
|
||||
}));
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import * as ActionTypes from '../../../constants';
|
||||
import { browserHistory } from 'react-router';
|
||||
import axios from 'axios';
|
||||
import objectID from 'bson-objectid';
|
||||
import moment from 'moment';
|
||||
import * as ActionTypes from '../../../constants';
|
||||
import { showToast, setToastText } from './toast';
|
||||
import { setUnsavedChanges,
|
||||
justOpenedProject,
|
||||
|
@ -9,7 +10,6 @@ import { setUnsavedChanges,
|
|||
setProjectSavedTime,
|
||||
resetProjectSavedTime,
|
||||
showErrorModal } from './ide';
|
||||
import moment from 'moment';
|
||||
|
||||
const ROOT_URL = location.href.indexOf('localhost') > 0 ? 'http://localhost:8000/api' : '/api';
|
||||
|
||||
|
@ -21,7 +21,7 @@ export function getProject(id) {
|
|||
dispatch(resetProjectSavedTime());
|
||||
}
|
||||
axios.get(`${ROOT_URL}/projects/${id}`, { withCredentials: true })
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: ActionTypes.SET_PROJECT,
|
||||
project: response.data,
|
||||
|
@ -86,7 +86,7 @@ export function saveProject(autosave = false) {
|
|||
});
|
||||
} else {
|
||||
axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true })
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
dispatch(setUnsavedChanges(false));
|
||||
dispatch(setProjectSavedTime(moment().format()));
|
||||
browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`);
|
||||
|
@ -108,7 +108,7 @@ export function saveProject(autosave = false) {
|
|||
}
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
.catch((response) => {
|
||||
if (response.status === 403) {
|
||||
dispatch(showErrorModal('staleSession'));
|
||||
} else {
|
||||
|
@ -131,7 +131,7 @@ export function autosaveProject() {
|
|||
export function createProject() {
|
||||
return (dispatch) => {
|
||||
axios.post(`${ROOT_URL}/projects`, {}, { withCredentials: true })
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`);
|
||||
dispatch({
|
||||
type: ActionTypes.NEW_PROJECT,
|
||||
|
@ -192,7 +192,7 @@ export function cloneProject() {
|
|||
// const newFiles = state.files;
|
||||
const formParams = Object.assign({}, { name: `${state.project.name} copy` }, { files: newFiles });
|
||||
axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true })
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`);
|
||||
dispatch({
|
||||
type: ActionTypes.NEW_PROJECT,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as ActionTypes from '../../../constants';
|
||||
import axios from 'axios';
|
||||
import * as ActionTypes from '../../../constants';
|
||||
import { showErrorModal, setPreviousPath } from './ide';
|
||||
import { resetProject } from './project';
|
||||
|
||||
|
@ -14,7 +14,7 @@ export function getProjects(username) {
|
|||
url = `${ROOT_URL}/projects`;
|
||||
}
|
||||
axios.get(url, { withCredentials: true })
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: ActionTypes.SET_PROJECTS,
|
||||
projects: response.data
|
||||
|
@ -41,7 +41,7 @@ export function deleteProject(id) {
|
|||
id
|
||||
});
|
||||
})
|
||||
.catch(response => {
|
||||
.catch((response) => {
|
||||
if (response.status === 403) {
|
||||
dispatch(showErrorModal('staleSession'));
|
||||
} else {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import axios from 'axios';
|
||||
import { createFile } from './files';
|
||||
const textFileRegex = /(text\/|application\/json)/;
|
||||
|
||||
const textFileRegex = /(text\/|application\/json)/;
|
||||
const s3BucketHttps = `https://s3-us-west-2.amazonaws.com/${process.env.S3_BUCKET}/`;
|
||||
const ROOT_URL = location.href.indexOf('localhost') > 0 ? 'http://localhost:8000/api' : '/api';
|
||||
const MAX_LOCAL_FILE_SIZE = 80000; // bytes, aka 80 KB
|
||||
|
@ -36,11 +36,11 @@ export function dropzoneAcceptCallback(file, done) {
|
|||
// check mime type
|
||||
// if text, local interceptor
|
||||
if (file.type.match(textFileRegex) && file.size < MAX_LOCAL_FILE_SIZE) {
|
||||
localIntercept(file).then(result => {
|
||||
localIntercept(file).then((result) => {
|
||||
file.content = result; // eslint-disable-line
|
||||
done('Uploading plaintext file locally.');
|
||||
})
|
||||
.catch(result => {
|
||||
.catch((result) => {
|
||||
done(`Failed to download file ${file.name}: ${result}`);
|
||||
console.warn(file);
|
||||
});
|
||||
|
@ -55,14 +55,14 @@ export function dropzoneAcceptCallback(file, done) {
|
|||
{
|
||||
withCredentials: true
|
||||
})
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
file.custom_status = 'ready'; // eslint-disable-line
|
||||
file.postData = response.data; // eslint-disable-line
|
||||
file.s3 = response.data.key; // eslint-disable-line
|
||||
file.previewTemplate.className += ' uploading'; // eslint-disable-line
|
||||
done();
|
||||
})
|
||||
.catch(response => {
|
||||
.catch((response) => {
|
||||
file.custom_status = 'rejected'; // eslint-disable-line
|
||||
if (response.data.responseText && response.data.responseText.message) {
|
||||
done(response.data.responseText.message);
|
||||
|
@ -76,7 +76,7 @@ export function dropzoneAcceptCallback(file, done) {
|
|||
export function dropzoneSendingCallback(file, xhr, formData) {
|
||||
return () => {
|
||||
if (!file.type.match(textFileRegex) || file.size >= MAX_LOCAL_FILE_SIZE) {
|
||||
Object.keys(file.postData).forEach(key => {
|
||||
Object.keys(file.postData).forEach((key) => {
|
||||
formData.append(key, file.postData[key]);
|
||||
});
|
||||
formData.append('Content-type', '');
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
import { browserHistory } from 'react-router';
|
||||
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
const squareLogoUrl = require('../../../images/p5js-square-logo.svg');
|
||||
const playUrl = require('../../../images/play.svg');
|
||||
const asteriskUrl = require('../../../images/p5-asterisk.svg');
|
||||
import { browserHistory } from 'react-router';
|
||||
|
||||
class About extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -13,7 +14,7 @@ class About extends React.Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.refs.about.focus();
|
||||
this.aboutSection.focus();
|
||||
}
|
||||
|
||||
closeAboutModal() {
|
||||
|
@ -22,7 +23,7 @@ class About extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<section className="about" ref="about" tabIndex="0">
|
||||
<section className="about" ref={(element) => { this.aboutSection = element; }} tabIndex="0">
|
||||
<header className="about__header">
|
||||
<h2 className="about__header-title">Welcome</h2>
|
||||
<button className="about__exit-button" onClick={this.closeAboutModal}>
|
||||
|
@ -36,6 +37,7 @@ class About extends React.Component {
|
|||
<a
|
||||
href="http://hello.p5js.org/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<InlineSVG className="about__play-video-button" src={playUrl} alt="Play Hello Video" />
|
||||
Play hello! video</a>
|
||||
|
@ -47,6 +49,7 @@ class About extends React.Component {
|
|||
<a
|
||||
href="https://p5js.org/examples/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
||||
Examples</a>
|
||||
|
@ -55,6 +58,7 @@ class About extends React.Component {
|
|||
<a
|
||||
href="https://p5js.org/tutorials/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
||||
Tutorials</a>
|
||||
|
@ -66,6 +70,7 @@ class About extends React.Component {
|
|||
<a
|
||||
href="https://p5js.org/libraries/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
||||
Libraries</a>
|
||||
|
@ -74,6 +79,7 @@ class About extends React.Component {
|
|||
<a
|
||||
href="https://p5js.org/reference/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
||||
Reference</a>
|
||||
|
@ -82,6 +88,7 @@ class About extends React.Component {
|
|||
<a
|
||||
href="https://forum.processing.org/two/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<InlineSVG className="about__content-column-asterisk" src={asteriskUrl} alt="p5 asterisk" />
|
||||
Forum</a>
|
||||
|
@ -93,18 +100,21 @@ class About extends React.Component {
|
|||
<a
|
||||
href="https://github.com/processing/p5.js-web-editor"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Contribute</a>
|
||||
</p>
|
||||
<p className="about__footer-list">
|
||||
<a
|
||||
href="https://github.com/processing/p5.js-web-editor/issues/new"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Report a bug</a>
|
||||
</p>
|
||||
<p className="about__footer-list">
|
||||
<a
|
||||
href="https://twitter.com/p5xjs?lang=en"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Twitter</a>
|
||||
</p>
|
||||
<button className="about__ok-button" onClick={this.closeAboutModal}>OK!</button>
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const upArrowUrl = require('../../../images/up-arrow.svg');
|
||||
const downArrowUrl = require('../../../images/down-arrow.svg');
|
||||
import classNames from 'classnames';
|
||||
|
||||
class Console extends React.Component {
|
||||
componentDidUpdate() {
|
||||
this.refs.console_messages.scrollTop = this.refs.console_messages.scrollHeight;
|
||||
this.consoleMessages.scrollTop = this.consoleMessages.scrollHeight;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -16,13 +17,13 @@ class Console extends React.Component {
|
|||
});
|
||||
|
||||
return (
|
||||
<div ref="console" className={consoleClass} role="main" tabIndex="0" title="console">
|
||||
<div className={consoleClass} role="main" tabIndex="0" title="console">
|
||||
<div className="preview-console__header">
|
||||
<h2 className="preview-console__header-title">Console</h2>
|
||||
<div className="preview-console__header-buttons">
|
||||
<a className="preview-console__clear" onClick={this.props.clearConsole} aria-label="clear console">
|
||||
<button className="preview-console__clear" onClick={this.props.clearConsole} aria-label="clear console">
|
||||
Clear
|
||||
</a>
|
||||
</button>
|
||||
<button className="preview-console__collapse" onClick={this.props.collapseConsole} aria-label="collapse console">
|
||||
<InlineSVG src={downArrowUrl} />
|
||||
</button>
|
||||
|
@ -31,20 +32,20 @@ class Console extends React.Component {
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="console_messages" className="preview-console__messages">
|
||||
{this.props.consoleEvents.map((consoleEvent, index) => {
|
||||
<div ref={(element) => { this.consoleMessages = element; }} className="preview-console__messages">
|
||||
{this.props.consoleEvents.map((consoleEvent) => {
|
||||
const args = consoleEvent.arguments;
|
||||
const method = consoleEvent.method;
|
||||
if (Object.keys(args).length === 0) {
|
||||
return (
|
||||
<div key={index} className="preview-console__undefined">
|
||||
<span key={`${index}-0`}>{'undefined'}</span>
|
||||
<div key={consoleEvent.id} className="preview-console__undefined">
|
||||
<span key={`${consoleEvent.id}-0`}>{'undefined'}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div key={index} className={`preview-console__${method}`}>
|
||||
{Object.keys(args).map((key) => <span key={`${index}-${key}`}>{args[key]}</span>)}
|
||||
<div key={consoleEvent.id} className={`preview-console__${method}`}>
|
||||
{Object.keys(args).map(key => <span key={`${consoleEvent.id}-${key}`}>{args[key]}</span>)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
@ -56,12 +57,18 @@ class Console extends React.Component {
|
|||
}
|
||||
|
||||
Console.propTypes = {
|
||||
consoleEvents: PropTypes.array,
|
||||
isPlaying: PropTypes.bool.isRequired,
|
||||
consoleEvents: PropTypes.arrayOf(PropTypes.shape({
|
||||
method: PropTypes.string.isRequired,
|
||||
args: PropTypes.arrayOf(PropTypes.string)
|
||||
})),
|
||||
isExpanded: PropTypes.bool.isRequired,
|
||||
collapseConsole: PropTypes.func.isRequired,
|
||||
expandConsole: PropTypes.func.isRequired,
|
||||
clearConsole: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
Console.defaultProps = {
|
||||
consoleEvents: []
|
||||
};
|
||||
|
||||
export default Console;
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import EditorAccessibility from '../components/EditorAccessibility';
|
||||
import CodeMirror from 'codemirror';
|
||||
import beautifyJS from 'js-beautify';
|
||||
const beautifyCSS = beautifyJS.css;
|
||||
const beautifyHTML = beautifyJS.html;
|
||||
import '../../../utils/p5-javascript';
|
||||
import 'codemirror/mode/css/css';
|
||||
import '../../../utils/htmlmixed';
|
||||
import 'codemirror/addon/selection/active-line';
|
||||
import 'codemirror/addon/lint/lint';
|
||||
import 'codemirror/addon/lint/javascript-lint';
|
||||
|
@ -15,22 +10,27 @@ import 'codemirror/addon/lint/html-lint';
|
|||
import 'codemirror/addon/comment/comment';
|
||||
import 'codemirror/keymap/sublime';
|
||||
import 'codemirror/addon/search/jump-to-line';
|
||||
|
||||
import { JSHINT } from 'jshint';
|
||||
window.JSHINT = JSHINT;
|
||||
import { CSSLint } from 'csslint';
|
||||
window.CSSLint = CSSLint;
|
||||
import { HTMLHint } from 'htmlhint';
|
||||
window.HTMLHint = HTMLHint;
|
||||
const beepUrl = require('../../../sounds/audioAlert.mp3');
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
import classNames from 'classnames';
|
||||
import { debounce } from 'lodash';
|
||||
import '../../../utils/htmlmixed';
|
||||
import '../../../utils/p5-javascript';
|
||||
import Timer from '../components/Timer';
|
||||
import EditorAccessibility from '../components/EditorAccessibility';
|
||||
|
||||
const beautifyCSS = beautifyJS.css;
|
||||
const beautifyHTML = beautifyJS.html;
|
||||
|
||||
window.JSHINT = JSHINT;
|
||||
window.CSSLint = CSSLint;
|
||||
window.HTMLHint = HTMLHint;
|
||||
|
||||
const beepUrl = require('../../../sounds/audioAlert.mp3');
|
||||
const downArrowUrl = require('../../../images/down-arrow.svg');
|
||||
const unsavedChangesDotUrl = require('../../../images/unsaved-changes-dot.svg');
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { debounce } from 'lodash';
|
||||
import Timer from '../components/Timer';
|
||||
|
||||
const rightArrowUrl = require('../../../images/right-arrow.svg');
|
||||
const leftArrowUrl = require('../../../images/left-arrow.svg');
|
||||
|
||||
|
@ -42,7 +42,7 @@ class Editor extends React.Component {
|
|||
componentDidMount() {
|
||||
this.beep = new Audio(beepUrl);
|
||||
this.widgets = [];
|
||||
this._cm = CodeMirror(this.refs.container, { // eslint-disable-line
|
||||
this._cm = CodeMirror(this.codemirrorContainer, { // eslint-disable-line
|
||||
theme: `p5-${this.props.theme}`,
|
||||
lineNumbers: true,
|
||||
styleActiveLine: true,
|
||||
|
@ -64,8 +64,8 @@ class Editor extends React.Component {
|
|||
}
|
||||
}, 2000),
|
||||
options: {
|
||||
asi: true,
|
||||
eqeqeq: false,
|
||||
'asi': true,
|
||||
'eqeqeq': false,
|
||||
'-W041': false
|
||||
}
|
||||
}
|
||||
|
@ -162,7 +162,7 @@ class Editor extends React.Component {
|
|||
|
||||
initializeDocuments(files) {
|
||||
this._docs = {};
|
||||
files.forEach(file => {
|
||||
files.forEach((file) => {
|
||||
if (file.name !== 'root') {
|
||||
this._docs[file.id] = CodeMirror.Doc(file.content, this.getFileMode(file.name)); // eslint-disable-line
|
||||
}
|
||||
|
@ -189,7 +189,7 @@ class Editor extends React.Component {
|
|||
if (this.props.editorOptionsVisible) {
|
||||
this.props.closeEditorOptions();
|
||||
} else {
|
||||
this.refs.optionsButton.focus();
|
||||
this.optionsButton.focus();
|
||||
this.props.showEditorOptions();
|
||||
}
|
||||
}
|
||||
|
@ -198,7 +198,7 @@ class Editor extends React.Component {
|
|||
|
||||
render() {
|
||||
const editorSectionClass = classNames({
|
||||
editor: true,
|
||||
'editor': true,
|
||||
'sidebar--contracted': !this.props.isExpanded,
|
||||
'editor--options': this.props.editorOptionsVisible
|
||||
});
|
||||
|
@ -238,7 +238,7 @@ class Editor extends React.Component {
|
|||
className="editor__options-button"
|
||||
aria-label="editor options"
|
||||
tabIndex="0"
|
||||
ref="optionsButton"
|
||||
ref={(element) => { this.optionsButton = element; }}
|
||||
onClick={() => {
|
||||
this.toggleEditorOptions();
|
||||
}}
|
||||
|
@ -248,18 +248,17 @@ class Editor extends React.Component {
|
|||
</button>
|
||||
<ul className="editor__options" title="editor options">
|
||||
<li>
|
||||
<a onClick={this.tidyCode}>Tidy</a>
|
||||
<button onClick={this.tidyCode}>Tidy</button>
|
||||
</li>
|
||||
<li>
|
||||
<a onClick={this.props.showKeyboardShortcutModal}>Keyboard shortcuts</a>
|
||||
<button onClick={this.props.showKeyboardShortcutModal}>Keyboard shortcuts</button>
|
||||
</li>
|
||||
</ul>
|
||||
</header>
|
||||
<div ref="container" className="editor-holder" tabIndex="0">
|
||||
<div ref={(element) => { this.codemirrorContainer = element; }} className="editor-holder" tabIndex="0">
|
||||
</div>
|
||||
<EditorAccessibility
|
||||
lintMessages={this.props.lintMessages}
|
||||
lineNumber={this.props.lineNumber}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
|
@ -268,11 +267,14 @@ class Editor extends React.Component {
|
|||
|
||||
Editor.propTypes = {
|
||||
lintWarning: PropTypes.bool.isRequired,
|
||||
lineNumber: PropTypes.string.isRequired,
|
||||
lintMessages: PropTypes.array.isRequired,
|
||||
lintMessages: PropTypes.arrayOf(PropTypes.shape({
|
||||
severity: PropTypes.string.isRequired,
|
||||
line: PropTypes.number.isRequired,
|
||||
message: PropTypes.string.isRequired,
|
||||
id: PropTypes.number.isRequired
|
||||
})).isRequired,
|
||||
updateLintMessage: PropTypes.func.isRequired,
|
||||
clearLintMessage: PropTypes.func.isRequired,
|
||||
updateLineNumber: PropTypes.func.isRequired,
|
||||
indentationAmount: PropTypes.number.isRequired,
|
||||
isTabIndent: PropTypes.bool.isRequired,
|
||||
updateFileContent: PropTypes.func.isRequired,
|
||||
|
@ -281,7 +283,7 @@ Editor.propTypes = {
|
|||
name: PropTypes.string.isRequired,
|
||||
content: PropTypes.string.isRequired,
|
||||
id: PropTypes.string.isRequired
|
||||
}),
|
||||
}).isRequired,
|
||||
editorOptionsVisible: PropTypes.bool.isRequired,
|
||||
showEditorOptions: PropTypes.func.isRequired,
|
||||
closeEditorOptions: PropTypes.func.isRequired,
|
||||
|
@ -293,7 +295,11 @@ Editor.propTypes = {
|
|||
theme: PropTypes.string.isRequired,
|
||||
unsavedChanges: PropTypes.bool.isRequired,
|
||||
projectSavedTime: PropTypes.string.isRequired,
|
||||
files: PropTypes.array.isRequired,
|
||||
files: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
content: PropTypes.string.isRequired
|
||||
})).isRequired,
|
||||
isExpanded: PropTypes.bool.isRequired,
|
||||
collapseSidebar: PropTypes.func.isRequired,
|
||||
expandSidebar: PropTypes.func.isRequired,
|
||||
|
@ -301,4 +307,8 @@ Editor.propTypes = {
|
|||
clearConsole: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
Editor.defaultProps = {
|
||||
isUserOwner: false
|
||||
};
|
||||
|
||||
export default Editor;
|
||||
|
|
|
@ -5,11 +5,11 @@ class EditorAccessibility extends React.Component {
|
|||
|
||||
}
|
||||
render() {
|
||||
let messages = [];
|
||||
const messages = [];
|
||||
if (this.props.lintMessages.length > 0) {
|
||||
this.props.lintMessages.forEach((lintMessage, i) => {
|
||||
messages.push(
|
||||
<li key={i}>
|
||||
<li key={lintMessage.id}>
|
||||
{lintMessage.severity} in line
|
||||
{lintMessage.line} :
|
||||
{lintMessage.message}
|
||||
|
@ -35,8 +35,12 @@ class EditorAccessibility extends React.Component {
|
|||
}
|
||||
|
||||
EditorAccessibility.propTypes = {
|
||||
lintMessages: PropTypes.array.isRequired,
|
||||
lineNumber: PropTypes.string.isRequired,
|
||||
lintMessages: PropTypes.arrayOf(PropTypes.shape({
|
||||
severity: PropTypes.string.isRequired,
|
||||
line: PropTypes.number.isRequired,
|
||||
message: PropTypes.string.isRequired,
|
||||
id: PropTypes.number.isRequired
|
||||
})).isRequired,
|
||||
};
|
||||
|
||||
export default EditorAccessibility;
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
import { Link } from 'react-router';
|
||||
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
|
||||
class ErrorModal extends React.Component {
|
||||
componentDidMount() {
|
||||
this.refs.modal.focus();
|
||||
this.errorModal.focus();
|
||||
}
|
||||
|
||||
|
||||
|
@ -23,7 +24,7 @@ class ErrorModal extends React.Component {
|
|||
staleSession() {
|
||||
return (
|
||||
<p>
|
||||
It looks like you've been logged out. Please
|
||||
It looks like you've been logged out. Please
|
||||
<Link to="/login" onClick={this.props.closeModal}>log in</Link>.
|
||||
</p>
|
||||
);
|
||||
|
@ -39,7 +40,7 @@ class ErrorModal extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<section className="error-modal" ref="modal" tabIndex="0">
|
||||
<section className="error-modal" ref={(element) => { this.errorModal = element; }} tabIndex="0">
|
||||
<header className="error-modal__header">
|
||||
<h2 className="error-modal__title">Error</h2>
|
||||
<button className="error-modal__exit-button" onClick={this.props.closeModal}>
|
||||
|
@ -63,7 +64,7 @@ class ErrorModal extends React.Component {
|
|||
}
|
||||
|
||||
ErrorModal.propTypes = {
|
||||
type: PropTypes.string,
|
||||
type: PropTypes.string.isRequired,
|
||||
closeModal: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import * as FileActions from '../actions/files';
|
||||
import * as IDEActions from '../actions/ide';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
import classNames from 'classnames';
|
||||
import * as IDEActions from '../actions/ide';
|
||||
import * as FileActions from '../actions/files';
|
||||
|
||||
const downArrowUrl = require('../../../images/down-arrow.svg');
|
||||
const folderRightUrl = require('../../../images/triangle-arrow-right.svg');
|
||||
const folderDownUrl = require('../../../images/triangle-arrow-down.svg');
|
||||
const fileUrl = require('../../../images/file.svg');
|
||||
import classNames from 'classnames';
|
||||
|
||||
export class FileNode extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -54,7 +55,7 @@ export class FileNode extends React.Component {
|
|||
if (this.props.isOptionsOpen) {
|
||||
this.props.hideFileOptions(this.props.id);
|
||||
} else {
|
||||
this.refs[`fileOptions-${this.props.id}`].focus();
|
||||
this[`fileOptions-${this.props.id}`].focus();
|
||||
this.props.showFileOptions(this.props.id);
|
||||
}
|
||||
}
|
||||
|
@ -68,7 +69,7 @@ export class FileNode extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
let itemClass = classNames({
|
||||
const itemClass = classNames({
|
||||
'sidebar__root-item': this.props.name === 'root',
|
||||
'sidebar__file-item': this.props.name !== 'root',
|
||||
'sidebar__file-item--selected': this.props.isSelectedFile,
|
||||
|
@ -77,11 +78,7 @@ export class FileNode extends React.Component {
|
|||
'sidebar__file-item--closed': this.props.isFolderClosed
|
||||
});
|
||||
return (
|
||||
<div
|
||||
className={itemClass}
|
||||
onClick={this.handleFileClick}
|
||||
onBlur={() => setTimeout(() => this.props.hideFileOptions(this.props.id), 200)}
|
||||
>
|
||||
<div className={itemClass}>
|
||||
{(() => { // eslint-disable-line
|
||||
if (this.props.name !== 'root') {
|
||||
return (
|
||||
|
@ -97,28 +94,28 @@ export class FileNode extends React.Component {
|
|||
}
|
||||
return (
|
||||
<div>
|
||||
<span
|
||||
<button
|
||||
className="sidebar__file-item-closed"
|
||||
onClick={() => this.props.showFolderChildren(this.props.id)}
|
||||
>
|
||||
<InlineSVG className="folder-right" src={folderRightUrl} />
|
||||
</span>
|
||||
<span
|
||||
</button>
|
||||
<button
|
||||
className="sidebar__file-item-open"
|
||||
onClick={() => this.props.hideFolderChildren(this.props.id)}
|
||||
>
|
||||
<InlineSVG className="folder-down" src={folderDownUrl} />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
<a className="sidebar__file-item-name">{this.props.name}</a>
|
||||
<button className="sidebar__file-item-name" onClick={this.handleFileClick}>{this.props.name}</button>
|
||||
<input
|
||||
type="text"
|
||||
className="sidebar__file-item-input"
|
||||
value={this.props.name}
|
||||
onChange={this.handleFileNameChange}
|
||||
ref="fileNameInput"
|
||||
ref={(element) => { this.fileNameInput = element; }}
|
||||
onBlur={() => {
|
||||
this.validateFileName();
|
||||
this.props.hideEditFileName(this.props.id);
|
||||
|
@ -128,21 +125,26 @@ export class FileNode extends React.Component {
|
|||
<button
|
||||
className="sidebar__file-item-show-options"
|
||||
aria-label="view file options"
|
||||
ref={`fileOptions-${this.props.id}`}
|
||||
ref={(element) => { this[`fileOptions-${this.props.id}`] = element; }}
|
||||
tabIndex="0"
|
||||
onClick={this.toggleFileOptions}
|
||||
onBlur={() => setTimeout(() => this.props.hideFileOptions(this.props.id), 200)}
|
||||
>
|
||||
<InlineSVG src={downArrowUrl} />
|
||||
</button>
|
||||
<div ref="fileOptions" className="sidebar__file-item-options">
|
||||
<div className="sidebar__file-item-options">
|
||||
<ul title="file options">
|
||||
{(() => { // eslint-disable-line
|
||||
if (this.props.fileType === 'folder') {
|
||||
return (
|
||||
<li>
|
||||
<a aria-label="add file" onClick={this.props.newFile} >
|
||||
<button
|
||||
aria-label="add file"
|
||||
onClick={this.props.newFile}
|
||||
className="sidebar__file-item-option"
|
||||
>
|
||||
Add File
|
||||
</a>
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
@ -151,26 +153,31 @@ export class FileNode extends React.Component {
|
|||
if (this.props.fileType === 'folder') {
|
||||
return (
|
||||
<li>
|
||||
<a aria-label="add folder" onClick={this.props.newFolder} >
|
||||
<button
|
||||
aria-label="add folder"
|
||||
onClick={this.props.newFolder}
|
||||
className="sidebar__file-item-option"
|
||||
>
|
||||
Add Folder
|
||||
</a>
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
})()}
|
||||
<li>
|
||||
<a
|
||||
<button
|
||||
onClick={() => {
|
||||
this.originalFileName = this.props.name;
|
||||
this.props.showEditFileName(this.props.id);
|
||||
setTimeout(() => this.refs.fileNameInput.focus(), 0);
|
||||
setTimeout(() => this.fileNameInput.focus(), 0);
|
||||
}}
|
||||
className="sidebar__file-item-option"
|
||||
>
|
||||
Rename
|
||||
</a>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
<button
|
||||
onClick={() => {
|
||||
if (window.confirm(`Are you sure you want to delete ${this.props.name}?`)) {
|
||||
this.isDeleting = true;
|
||||
|
@ -178,9 +185,10 @@ export class FileNode extends React.Component {
|
|||
setTimeout(() => this.props.deleteFile(this.props.id, this.props.parentId), 100);
|
||||
}
|
||||
}}
|
||||
className="sidebar__file-item-option"
|
||||
>
|
||||
Delete
|
||||
</a>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -205,7 +213,7 @@ export class FileNode extends React.Component {
|
|||
FileNode.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
parentId: PropTypes.string,
|
||||
children: PropTypes.array,
|
||||
children: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
fileType: PropTypes.string.isRequired,
|
||||
isSelectedFile: PropTypes.bool,
|
||||
|
@ -226,9 +234,22 @@ FileNode.propTypes = {
|
|||
hideFolderChildren: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
FileNode.defaultProps = {
|
||||
id: '1',
|
||||
name: 'test',
|
||||
fileType: 'file',
|
||||
children: [],
|
||||
parentId: '0',
|
||||
isSelectedFile: false,
|
||||
isOptionsOpen: false,
|
||||
isEditingName: false,
|
||||
isFolderClosed: false
|
||||
};
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
// this is a hack, state is updated before ownProps
|
||||
return state.files.find((file) => file.id === ownProps.id) || { ...ownProps, name: 'test', fileType: 'file' };
|
||||
return state.files.find(file => file.id === ownProps.id) || { ...ownProps, name: 'test', fileType: 'file' };
|
||||
// return state.files.find(file => file.id === ownProps.id);
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import Dropzone from 'dropzone';
|
||||
const s3Bucket = `https://s3-us-west-2.amazonaws.com/${process.env.S3_BUCKET}/`;
|
||||
import * as UploaderActions from '../actions/uploader';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import * as UploaderActions from '../actions/uploader';
|
||||
|
||||
const s3Bucket = `https://s3-us-west-2.amazonaws.com/${process.env.S3_BUCKET}/`;
|
||||
|
||||
class FileUploader extends React.Component {
|
||||
componentDidMount() {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
|
||||
class KeyboardShortcutModal extends React.Component {
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { reduxForm } from 'redux-form';
|
||||
import NewFileForm from './NewFileForm';
|
||||
import classNames from 'classnames';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
import NewFileForm from './NewFileForm';
|
||||
import FileUploader from './FileUploader';
|
||||
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
|
||||
import FileUploader from './FileUploader';
|
||||
|
||||
// At some point this will probably be generalized to a generic modal
|
||||
// in which you can insert different content
|
||||
|
@ -21,16 +22,16 @@ class NewFileModal extends React.Component {
|
|||
}
|
||||
|
||||
focusOnModal() {
|
||||
this.refs.modal.focus();
|
||||
this.modal.focus();
|
||||
}
|
||||
|
||||
render() {
|
||||
const modalClass = classNames({
|
||||
modal: true,
|
||||
'modal': true,
|
||||
'modal--reduced': !this.props.canUploadMedia
|
||||
});
|
||||
return (
|
||||
<section className={modalClass} tabIndex="0" ref="modal">
|
||||
<section className={modalClass} tabIndex="0" ref={(element) => { this.modal = element; }}>
|
||||
<div className="modal-content">
|
||||
<div className="modal__header">
|
||||
<h2 className="modal__title">Add File</h2>
|
||||
|
|
|
@ -8,7 +8,7 @@ class NewFolderForm extends React.Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.refs.fileName.focus();
|
||||
this.fileName.focus();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -27,7 +27,7 @@ class NewFolderForm extends React.Component {
|
|||
id="name"
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
ref="fileName"
|
||||
ref={(element) => { this.fileName = element; }}
|
||||
{...domOnlyProps(name)}
|
||||
/>
|
||||
<input type="submit" value="Add Folder" aria-label="add folder" />
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { reduxForm } from 'redux-form';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
import NewFolderForm from './NewFolderForm';
|
||||
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
|
||||
class NewFolderModal extends React.Component {
|
||||
componentDidMount() {
|
||||
this.refs.modal.focus();
|
||||
this.newFolderModal.focus();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<section className="modal" ref="modal" tabIndex="0">
|
||||
<section className="modal" ref={(element) => { this.newFolderModal = element; }} tabIndex="0">
|
||||
<div className="modal-content-folder">
|
||||
<div className="modal__header">
|
||||
<h2 className="modal__title">Add Folder</h2>
|
||||
|
|
|
@ -49,7 +49,7 @@ class Preferences extends React.Component {
|
|||
render() {
|
||||
const beep = new Audio(beepUrl);
|
||||
const preferencesContainerClass = classNames({
|
||||
preferences: true,
|
||||
'preferences': true,
|
||||
'preferences--selected': this.props.isVisible
|
||||
});
|
||||
|
||||
|
@ -79,15 +79,14 @@ class Preferences extends React.Component {
|
|||
</button>
|
||||
<input
|
||||
className="preference__value"
|
||||
aria-live="status"
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
role="status"
|
||||
value={this.props.fontSize}
|
||||
onChange={this.handleUpdateFont}
|
||||
ref="fontSizeInput"
|
||||
ref={(element) => { this.fontSizeInput = element; }}
|
||||
onClick={() => {
|
||||
this.refs.fontSizeInput.select();
|
||||
this.fontSizeInput.select();
|
||||
}}
|
||||
>
|
||||
</input>
|
||||
|
@ -113,15 +112,14 @@ class Preferences extends React.Component {
|
|||
</button>
|
||||
<input
|
||||
className="preference__value"
|
||||
aria-live="status"
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
role="status"
|
||||
value={this.props.indentationAmount}
|
||||
onChange={this.handleUpdateIndentation}
|
||||
ref="indentationInput"
|
||||
ref={(element) => { this.indentationInput = element; }}
|
||||
onClick={() => {
|
||||
this.refs.indentationInput.select();
|
||||
this.indentationInput.select();
|
||||
}}
|
||||
>
|
||||
</input>
|
||||
|
@ -248,13 +246,13 @@ class Preferences extends React.Component {
|
|||
checked={!this.props.lintWarning}
|
||||
/>
|
||||
<label htmlFor="lint-warning-off" className="preference__option">Off</label>
|
||||
<div
|
||||
<button
|
||||
className="preference__preview-button"
|
||||
onClick={() => beep.play()}
|
||||
aria-label="preview sound"
|
||||
>
|
||||
Preview sound
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="preference">
|
||||
|
@ -303,7 +301,7 @@ class Preferences extends React.Component {
|
|||
id="text-output-off"
|
||||
className="preference__radio-button"
|
||||
value="Off"
|
||||
checked={!Boolean(this.props.textOutput)}
|
||||
checked={!(this.props.textOutput)}
|
||||
/>
|
||||
<label htmlFor="text-output-off" className="preference__option preference__canvas">Off</label>
|
||||
|
||||
|
|
|
@ -86,9 +86,8 @@ class PreviewFrame extends React.Component {
|
|||
this.renderFrameContents();
|
||||
}
|
||||
|
||||
if (this.props.dispatchConsoleEvent) {
|
||||
window.addEventListener('message', (messageEvent) => {
|
||||
messageEvent.data.forEach(message => {
|
||||
messageEvent.data.forEach((message) => {
|
||||
const args = message.arguments;
|
||||
Object.keys(args).forEach((key) => {
|
||||
if (args[key].includes('Exiting potential infinite loop')) {
|
||||
|
@ -100,7 +99,6 @@ class PreviewFrame extends React.Component {
|
|||
this.props.dispatchConsoleEvent(messageEvent.data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
// if sketch starts or stops playing, want to rerender
|
||||
|
@ -128,7 +126,6 @@ class PreviewFrame extends React.Component {
|
|||
|
||||
if (this.props.fullView && this.props.files[0].id !== prevProps.files[0].id) {
|
||||
this.renderSketch();
|
||||
return;
|
||||
}
|
||||
|
||||
// small bug - if autorefresh is on, and the usr changes files
|
||||
|
@ -136,11 +133,11 @@ class PreviewFrame extends React.Component {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this).contentDocument.body);
|
||||
ReactDOM.unmountComponentAtNode(this.iframeElement.contentDocument.body);
|
||||
}
|
||||
|
||||
clearPreview() {
|
||||
const doc = ReactDOM.findDOMNode(this);
|
||||
const doc = this.iframeElement;
|
||||
doc.srcDoc = '';
|
||||
}
|
||||
|
||||
|
@ -204,7 +201,7 @@ class PreviewFrame extends React.Component {
|
|||
scriptsToInject = scriptsToInject.concat(interceptorScripts);
|
||||
}
|
||||
|
||||
scriptsToInject.forEach(scriptToInject => {
|
||||
scriptsToInject.forEach((scriptToInject) => {
|
||||
const script = sketchDoc.createElement('script');
|
||||
script.src = scriptToInject;
|
||||
sketchDoc.head.appendChild(script);
|
||||
|
@ -223,7 +220,7 @@ class PreviewFrame extends React.Component {
|
|||
resolvePathsForElementsWithAttribute(attr, sketchDoc, files) {
|
||||
const elements = sketchDoc.querySelectorAll(`[${attr}]`);
|
||||
const elementsArray = Array.prototype.slice.call(elements);
|
||||
elementsArray.forEach(element => {
|
||||
elementsArray.forEach((element) => {
|
||||
if (element.getAttribute(attr).match(MEDIA_FILE_REGEX_NO_QUOTES)) {
|
||||
const resolvedFile = resolvePathToFile(element.getAttribute(attr), files);
|
||||
if (resolvedFile) {
|
||||
|
@ -235,7 +232,7 @@ class PreviewFrame extends React.Component {
|
|||
|
||||
resolveJSAndCSSLinks(files) {
|
||||
const newFiles = [];
|
||||
files.forEach(file => {
|
||||
files.forEach((file) => {
|
||||
const newFile = { ...file };
|
||||
if (file.name.match(/.*\.js$/i)) {
|
||||
newFile.content = this.resolveJSLinksInString(newFile.content, files);
|
||||
|
@ -251,7 +248,7 @@ class PreviewFrame extends React.Component {
|
|||
let newContent = content;
|
||||
let jsFileStrings = content.match(STRING_REGEX);
|
||||
jsFileStrings = jsFileStrings || [];
|
||||
jsFileStrings.forEach(jsFileString => {
|
||||
jsFileStrings.forEach((jsFileString) => {
|
||||
if (jsFileString.match(MEDIA_FILE_REGEX)) {
|
||||
const filePath = jsFileString.substr(1, jsFileString.length - 2);
|
||||
const resolvedFile = resolvePathToFile(filePath, files);
|
||||
|
@ -275,7 +272,7 @@ class PreviewFrame extends React.Component {
|
|||
let newContent = content;
|
||||
let cssFileStrings = content.match(STRING_REGEX);
|
||||
cssFileStrings = cssFileStrings || [];
|
||||
cssFileStrings.forEach(cssFileString => {
|
||||
cssFileStrings.forEach((cssFileString) => {
|
||||
if (cssFileString.match(MEDIA_FILE_REGEX)) {
|
||||
const filePath = cssFileString.substr(1, cssFileString.length - 2);
|
||||
const resolvedFile = resolvePathToFile(filePath, files);
|
||||
|
@ -292,7 +289,7 @@ class PreviewFrame extends React.Component {
|
|||
resolveScripts(sketchDoc, files) {
|
||||
const scriptsInHTML = sketchDoc.getElementsByTagName('script');
|
||||
const scriptsInHTMLArray = Array.prototype.slice.call(scriptsInHTML);
|
||||
scriptsInHTMLArray.forEach(script => {
|
||||
scriptsInHTMLArray.forEach((script) => {
|
||||
if (script.getAttribute('src') && script.getAttribute('src').match(NOT_EXTERNAL_LINK_REGEX) !== null) {
|
||||
const resolvedFile = resolvePathToFile(script.getAttribute('src'), files);
|
||||
if (resolvedFile) {
|
||||
|
@ -313,13 +310,13 @@ class PreviewFrame extends React.Component {
|
|||
resolveStyles(sketchDoc, files) {
|
||||
const inlineCSSInHTML = sketchDoc.getElementsByTagName('style');
|
||||
const inlineCSSInHTMLArray = Array.prototype.slice.call(inlineCSSInHTML);
|
||||
inlineCSSInHTMLArray.forEach(style => {
|
||||
inlineCSSInHTMLArray.forEach((style) => {
|
||||
style.innerHTML = this.resolveCSSLinksInString(style.innerHTML, files); // eslint-disable-line
|
||||
});
|
||||
|
||||
const cssLinksInHTML = sketchDoc.querySelectorAll('link[rel="stylesheet"]');
|
||||
const cssLinksInHTMLArray = Array.prototype.slice.call(cssLinksInHTML);
|
||||
cssLinksInHTMLArray.forEach(css => {
|
||||
cssLinksInHTMLArray.forEach((css) => {
|
||||
if (css.getAttribute('href') && css.getAttribute('href').match(NOT_EXTERNAL_LINK_REGEX) !== null) {
|
||||
const resolvedFile = resolvePathToFile(css.getAttribute('href'), files);
|
||||
if (resolvedFile) {
|
||||
|
@ -337,7 +334,7 @@ class PreviewFrame extends React.Component {
|
|||
}
|
||||
|
||||
renderSketch() {
|
||||
const doc = ReactDOM.findDOMNode(this);
|
||||
const doc = this.iframeElement;
|
||||
if (this.props.isPlaying) {
|
||||
srcDoc.set(doc, this.injectLocalFiles());
|
||||
if (this.props.endSketchRefresh) {
|
||||
|
@ -350,7 +347,7 @@ class PreviewFrame extends React.Component {
|
|||
}
|
||||
|
||||
renderFrameContents() {
|
||||
const doc = ReactDOM.findDOMNode(this).contentDocument;
|
||||
const doc = this.iframeElement.contentDocument;
|
||||
if (doc.readyState === 'complete') {
|
||||
this.renderSketch();
|
||||
} else {
|
||||
|
@ -366,8 +363,8 @@ class PreviewFrame extends React.Component {
|
|||
role="main"
|
||||
tabIndex="0"
|
||||
frameBorder="0"
|
||||
ref="iframe"
|
||||
title="sketch output"
|
||||
ref={(element) => { this.iframeElement = element; }}
|
||||
sandbox="allow-scripts allow-pointer-lock allow-same-origin allow-popups allow-forms"
|
||||
/>
|
||||
);
|
||||
|
@ -379,14 +376,16 @@ PreviewFrame.propTypes = {
|
|||
isTextOutputPlaying: PropTypes.bool.isRequired,
|
||||
textOutput: PropTypes.number.isRequired,
|
||||
setTextOutput: PropTypes.func.isRequired,
|
||||
content: PropTypes.string,
|
||||
htmlFile: PropTypes.shape({
|
||||
content: PropTypes.string.isRequired
|
||||
}),
|
||||
files: PropTypes.array.isRequired,
|
||||
dispatchConsoleEvent: PropTypes.func,
|
||||
children: PropTypes.element,
|
||||
autorefresh: PropTypes.bool.isRequired,
|
||||
}).isRequired,
|
||||
files: PropTypes.arrayOf(PropTypes.shape({
|
||||
content: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
url: PropTypes.string,
|
||||
id: PropTypes.string.isRequired
|
||||
})).isRequired,
|
||||
dispatchConsoleEvent: PropTypes.func.isRequired,
|
||||
endSketchRefresh: PropTypes.func.isRequired,
|
||||
previewIsRefreshing: PropTypes.bool.isRequired,
|
||||
fullView: PropTypes.bool,
|
||||
|
@ -395,4 +394,8 @@ PreviewFrame.propTypes = {
|
|||
expandConsole: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
PreviewFrame.defaultProps = {
|
||||
fullView: false
|
||||
};
|
||||
|
||||
export default PreviewFrame;
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
|
||||
class ShareModal extends React.Component {
|
||||
componentDidMount() {
|
||||
this.refs.shareModal.focus();
|
||||
this.shareModal.focus();
|
||||
}
|
||||
render() {
|
||||
const hostname = window.location.origin;
|
||||
return (
|
||||
<section className="share-modal" ref="shareModal" tabIndex="0">
|
||||
<section className="share-modal" ref={(element) => { this.shareModal = element; }} tabIndex="0">
|
||||
<header className="share-modal__header">
|
||||
<h2>Share Sketch</h2>
|
||||
<button className="about__exit-button" onClick={this.props.closeShareModal}>
|
||||
|
@ -17,26 +18,29 @@ class ShareModal extends React.Component {
|
|||
</button>
|
||||
</header>
|
||||
<div className="share-modal__section">
|
||||
<label className="share-modal__label">Embed</label>
|
||||
<label className="share-modal__label" htmlFor="share-modal__embed">Embed</label>
|
||||
<input
|
||||
type="text"
|
||||
className="share-modal__input"
|
||||
id="share-modal__embed"
|
||||
value={`<iframe src="${hostname}/embed/${this.props.projectId}"></iframe>`}
|
||||
/>
|
||||
</div>
|
||||
<div className="share-modal__section">
|
||||
<label className="share-modal__label">Fullscreen</label>
|
||||
<label className="share-modal__label" htmlFor="share-modal__fullscreen">Fullscreen</label>
|
||||
<input
|
||||
type="text"
|
||||
className="share-modal__input"
|
||||
id="share-modal__fullscreen"
|
||||
value={`${hostname}/full/${this.props.projectId}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="share-modal__section">
|
||||
<label className="share-modal__label">Edit</label>
|
||||
<label className="share-modal__label" htmlFor="share-modal__edit">Edit</label>
|
||||
<input
|
||||
type="text"
|
||||
className="share-modal__input"
|
||||
id="share-modal__edit"
|
||||
value={`${hostname}/${this.props.ownerUsername}/sketches/${this.props.projectId}`}
|
||||
/>
|
||||
</div>
|
||||
|
@ -48,7 +52,7 @@ class ShareModal extends React.Component {
|
|||
ShareModal.propTypes = {
|
||||
projectId: PropTypes.string.isRequired,
|
||||
closeShareModal: PropTypes.func.isRequired,
|
||||
ownerUsername: PropTypes.string
|
||||
ownerUsername: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default ShareModal;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
// import SidebarItem from './SidebarItem';
|
||||
import ConnectedFileNode from './FileNode';
|
||||
|
||||
const folderUrl = require('../../../images/folder.svg');
|
||||
const downArrowUrl = require('../../../images/down-arrow.svg');
|
||||
import ConnectedFileNode from './FileNode';
|
||||
|
||||
class Sidebar extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -22,14 +22,14 @@ class Sidebar extends React.Component {
|
|||
if (this.props.projectOptionsVisible) {
|
||||
this.props.closeProjectOptions();
|
||||
} else {
|
||||
this.refs.sidebarOptions.focus();
|
||||
this.sidebarOptions.focus();
|
||||
this.props.openProjectOptions();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const sidebarClass = classNames({
|
||||
sidebar: true,
|
||||
'sidebar': true,
|
||||
'sidebar--contracted': !this.props.isExpanded,
|
||||
'sidebar--project-options': this.props.projectOptionsVisible
|
||||
});
|
||||
|
@ -48,7 +48,7 @@ class Sidebar extends React.Component {
|
|||
aria-label="add file or folder"
|
||||
className="sidebar__add"
|
||||
tabIndex="0"
|
||||
ref="sidebarOptions"
|
||||
ref={(element) => { this.sidebarOptions = element; }}
|
||||
onClick={this.toggleProjectOptions}
|
||||
onBlur={() => setTimeout(this.props.closeProjectOptions, 200)}
|
||||
>
|
||||
|
@ -56,14 +56,14 @@ class Sidebar extends React.Component {
|
|||
</button>
|
||||
<ul className="sidebar__project-options">
|
||||
<li>
|
||||
<a aria-label="add folder" onClick={this.props.newFolder} >
|
||||
<button aria-label="add folder" onClick={this.props.newFolder} >
|
||||
Add folder
|
||||
</a>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<a aria-label="add file" onClick={this.props.newFile} >
|
||||
<button aria-label="add file" onClick={this.props.newFile} >
|
||||
Add file
|
||||
</a>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -75,17 +75,14 @@ class Sidebar extends React.Component {
|
|||
}
|
||||
|
||||
Sidebar.propTypes = {
|
||||
files: PropTypes.array.isRequired,
|
||||
files: PropTypes.arrayOf(PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
id: PropTypes.string.isRequired
|
||||
})).isRequired,
|
||||
setSelectedFile: PropTypes.func.isRequired,
|
||||
isExpanded: PropTypes.bool.isRequired,
|
||||
projectOptionsVisible: PropTypes.bool.isRequired,
|
||||
newFile: PropTypes.func.isRequired,
|
||||
showFileOptions: PropTypes.func.isRequired,
|
||||
hideFileOptions: PropTypes.func.isRequired,
|
||||
deleteFile: PropTypes.func.isRequired,
|
||||
showEditFileName: PropTypes.func.isRequired,
|
||||
hideEditFileName: PropTypes.func.isRequired,
|
||||
updateFileName: PropTypes.func.isRequired,
|
||||
openProjectOptions: PropTypes.func.isRequired,
|
||||
closeProjectOptions: PropTypes.func.isRequired,
|
||||
newFolder: PropTypes.func.isRequired
|
||||
|
|
|
@ -3,10 +3,11 @@ import { connect } from 'react-redux';
|
|||
import { bindActionCreators } from 'redux';
|
||||
import moment from 'moment';
|
||||
import { Link, browserHistory } from 'react-router';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
import * as SketchActions from '../actions/projects';
|
||||
import * as ProjectActions from '../actions/project';
|
||||
import * as ToastActions from '../actions/toast';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
const trashCan = require('../../../images/trash-can.svg');
|
||||
|
||||
|
@ -47,10 +48,11 @@ class SketchList extends React.Component {
|
|||
</thead>
|
||||
<tbody>
|
||||
{this.props.sketches.map(sketch =>
|
||||
// eslint-disable-next-line
|
||||
<tr
|
||||
className="sketches-table__row visibility-toggle"
|
||||
key={sketch.id}
|
||||
onClick={() => browserHistory.push(`/${username}/sketches/${sketch._id}`)}
|
||||
onClick={() => browserHistory.push(`/${username}/sketches/${sketch.id}`)}
|
||||
>
|
||||
<td className="sketch-list__trash-column">
|
||||
{(() => { // eslint-disable-line
|
||||
|
@ -71,7 +73,7 @@ class SketchList extends React.Component {
|
|||
}
|
||||
})()}
|
||||
</td>
|
||||
<td scope="row"><Link to={`/${username}/sketches/${sketch._id}`}>{sketch.name}</Link></td>
|
||||
<th scope="row"><Link to={`/${username}/sketches/${sketch.id}`}>{sketch.name}</Link></th>
|
||||
<td>{moment(sketch.createdAt).format('MMM D, YYYY h:mm A')}</td>
|
||||
<td>{moment(sketch.updatedAt).format('MMM D, YYYY h:mm A')}</td>
|
||||
</tr>
|
||||
|
@ -85,14 +87,23 @@ class SketchList extends React.Component {
|
|||
}
|
||||
|
||||
SketchList.propTypes = {
|
||||
user: PropTypes.object.isRequired,
|
||||
user: PropTypes.shape({
|
||||
username: PropTypes.string
|
||||
}).isRequired,
|
||||
getProjects: PropTypes.func.isRequired,
|
||||
sketches: PropTypes.array.isRequired,
|
||||
sketches: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
createdAt: PropTypes.string.isRequired,
|
||||
updatedAt: PropTypes.string.isRequired
|
||||
})).isRequired,
|
||||
username: PropTypes.string,
|
||||
deleteProject: PropTypes.func.isRequired,
|
||||
previousPath: PropTypes.string.isRequired,
|
||||
showToast: PropTypes.func.isRequired,
|
||||
setToastText: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
SketchList.defaultProps = {
|
||||
username: undefined
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
|
|
|
@ -2,14 +2,14 @@ import React from 'react';
|
|||
|
||||
class TextOutput extends React.Component {
|
||||
componentDidMount() {
|
||||
this.refs.canvasTextOutput.focus();
|
||||
this.canvasTextOutput.focus();
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<section
|
||||
className="text-output"
|
||||
id="canvas-sub"
|
||||
ref="canvasTextOutput"
|
||||
ref={(element) => { this.canvasTextOutput = element; }}
|
||||
tabIndex="0"
|
||||
aria-label="text-output"
|
||||
title="canvas text output"
|
||||
|
|
|
@ -49,4 +49,8 @@ Timer.propTypes = {
|
|||
isUserOwner: PropTypes.bool
|
||||
};
|
||||
|
||||
Timer.defaultProps = {
|
||||
isUserOwner: false
|
||||
};
|
||||
|
||||
export default Timer;
|
||||
|
|
|
@ -2,9 +2,10 @@ import React, { PropTypes } from 'react';
|
|||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
import * as ToastActions from '../actions/toast';
|
||||
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
|
||||
function Toast(props) {
|
||||
return (
|
||||
<section className="toast">
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { Link } from 'react-router';
|
||||
const InlineSVG = require('react-inlinesvg');
|
||||
import classNames from 'classnames';
|
||||
import InlineSVG from '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 editProjectNameUrl = require('../../../images/pencil.svg');
|
||||
import classNames from 'classnames';
|
||||
|
||||
class Toolbar extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -38,19 +39,19 @@ class Toolbar extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
let playButtonClass = classNames({
|
||||
const playButtonClass = classNames({
|
||||
'toolbar__play-button': true,
|
||||
'toolbar__play-button--selected': this.props.isPlaying
|
||||
});
|
||||
let stopButtonClass = classNames({
|
||||
const stopButtonClass = classNames({
|
||||
'toolbar__stop-button': true,
|
||||
'toolbar__stop-button--selected': !this.props.isPlaying
|
||||
});
|
||||
let preferencesButtonClass = classNames({
|
||||
const preferencesButtonClass = classNames({
|
||||
'toolbar__preferences-button': true,
|
||||
'toolbar__preferences-button--selected': this.props.preferencesIsVisible
|
||||
});
|
||||
let nameContainerClass = classNames({
|
||||
const nameContainerClass = classNames({
|
||||
'toolbar__project-name-container': true,
|
||||
'toolbar__project-name-container--editing': this.props.project.isEditingName
|
||||
});
|
||||
|
@ -110,7 +111,7 @@ class Toolbar extends React.Component {
|
|||
e.preventDefault();
|
||||
this.originalProjectName = this.props.project.name;
|
||||
this.props.showEditProjectName();
|
||||
setTimeout(() => this.refs.projectNameInput.focus(), 0);
|
||||
setTimeout(() => this.projectNameInput.focus(), 0);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -122,7 +123,7 @@ class Toolbar extends React.Component {
|
|||
className="toolbar__project-name-input"
|
||||
value={this.props.project.name}
|
||||
onChange={this.handleProjectNameChange}
|
||||
ref="projectNameInput"
|
||||
ref={(element) => { this.projectNameInput = element; }}
|
||||
onBlur={() => {
|
||||
this.validateProjectName();
|
||||
this.props.hideEditProjectName();
|
||||
|
@ -157,7 +158,6 @@ class Toolbar extends React.Component {
|
|||
Toolbar.propTypes = {
|
||||
isPlaying: PropTypes.bool.isRequired,
|
||||
preferencesIsVisible: PropTypes.bool.isRequired,
|
||||
startSketch: PropTypes.func.isRequired,
|
||||
stopSketch: PropTypes.func.isRequired,
|
||||
startTextOutput: PropTypes.func.isRequired,
|
||||
stopTextOutput: PropTypes.func.isRequired,
|
||||
|
@ -182,4 +182,9 @@ Toolbar.propTypes = {
|
|||
clearConsole: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
Toolbar.defaultProps = {
|
||||
owner: undefined,
|
||||
currentUser: undefined
|
||||
};
|
||||
|
||||
export default Toolbar;
|
||||
|
|
|
@ -38,18 +38,34 @@ class FullView extends React.Component {
|
|||
FullView.propTypes = {
|
||||
params: PropTypes.shape({
|
||||
project_id: PropTypes.string
|
||||
}),
|
||||
}).isRequired,
|
||||
project: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
owner: PropTypes.shape({
|
||||
username: PropTypes.string
|
||||
})
|
||||
}).isRequired,
|
||||
htmlFile: PropTypes.object,
|
||||
jsFiles: PropTypes.array,
|
||||
cssFiles: PropTypes.array,
|
||||
htmlFile: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
content: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired
|
||||
}).isRequired,
|
||||
jsFiles: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
content: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired
|
||||
})).isRequired,
|
||||
cssFiles: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
content: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired
|
||||
})).isRequired,
|
||||
getProject: PropTypes.func.isRequired,
|
||||
files: PropTypes.array
|
||||
files: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
content: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired
|
||||
})).isRequired,
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router';
|
||||
import SplitPane from 'react-split-pane';
|
||||
import Editor from '../components/Editor';
|
||||
import Sidebar from '../components/Sidebar';
|
||||
import PreviewFrame from '../components/PreviewFrame';
|
||||
|
@ -13,9 +17,6 @@ import ErrorModal from '../components/ErrorModal';
|
|||
import Nav from '../../../components/Nav';
|
||||
import Console from '../components/Console';
|
||||
import Toast from '../components/Toast';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router';
|
||||
import * as FileActions from '../actions/files';
|
||||
import * as IDEActions from '../actions/ide';
|
||||
import * as ProjectActions from '../actions/project';
|
||||
|
@ -25,7 +26,6 @@ import * as UserActions from '../../User/actions';
|
|||
import * as ToastActions from '../actions/toast';
|
||||
import * as ConsoleActions from '../actions/console';
|
||||
import { getHTMLFile } from '../reducers/files';
|
||||
import SplitPane from 'react-split-pane';
|
||||
import Overlay from '../../App/components/Overlay';
|
||||
import SketchList from '../components/SketchList';
|
||||
import About from '../components/About';
|
||||
|
@ -55,7 +55,7 @@ class IDEView extends React.Component {
|
|||
this.isMac = navigator.userAgent.toLowerCase().indexOf('mac') !== -1;
|
||||
document.addEventListener('keydown', this.handleGlobalKeydown, false);
|
||||
|
||||
this.props.router.setRouteLeaveHook(this.props.route, (route) => this.warnIfUnsavedChanges(route));
|
||||
this.props.router.setRouteLeaveHook(this.props.route, route => this.warnIfUnsavedChanges(route));
|
||||
|
||||
window.onbeforeunload = () => this.warnIfUnsavedChanges();
|
||||
|
||||
|
@ -104,7 +104,7 @@ class IDEView extends React.Component {
|
|||
}
|
||||
|
||||
if (this.props.route.path !== prevProps.route.path) {
|
||||
this.props.router.setRouteLeaveHook(this.props.route, (route) => this.warnIfUnsavedChanges(route));
|
||||
this.props.router.setRouteLeaveHook(this.props.route, route => this.warnIfUnsavedChanges(route));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,16 +120,16 @@ class IDEView extends React.Component {
|
|||
}
|
||||
|
||||
_handleConsolePaneOnDragFinished() {
|
||||
this.consoleSize = this.refs.consolePane.state.draggedSize;
|
||||
this.refs.consolePane.setState({
|
||||
this.consoleSize = this.consolePane.state.draggedSize;
|
||||
this.consolePane.setState({
|
||||
resized: false,
|
||||
draggedSize: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
_handleSidebarPaneOnDragFinished() {
|
||||
this.sidebarSize = this.refs.sidebarPane.state.draggedSize;
|
||||
this.refs.sidebarPane.setState({
|
||||
this.sidebarSize = this.sidebarPane.state.draggedSize;
|
||||
this.sidebarPane.setState({
|
||||
resized: false,
|
||||
draggedSize: undefined
|
||||
});
|
||||
|
@ -140,7 +140,7 @@ class IDEView extends React.Component {
|
|||
if (e.keyCode === 83 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac))) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (this.isUserOwner() || this.props.user.authenticated && !this.props.project.owner) {
|
||||
if (this.isUserOwner() || (this.props.user.authenticated && !this.props.project.owner)) {
|
||||
this.props.saveProject();
|
||||
}
|
||||
// 13 === enter
|
||||
|
@ -170,7 +170,7 @@ class IDEView extends React.Component {
|
|||
warnIfUnsavedChanges(route) { // eslint-disable-line
|
||||
if (route && (route.action === 'PUSH' && (route.pathname === '/login' || route.pathname === '/signup'))) {
|
||||
// don't warn
|
||||
} else if (route && this.props.location.pathname === '/login' || this.props.location.pathname === '/signup') {
|
||||
} else if (route && (this.props.location.pathname === '/login' || this.props.location.pathname === '/signup')) {
|
||||
// don't warn
|
||||
} else if (this.props.ide.unsavedChanges) {
|
||||
if (!window.confirm('Are you sure you want to leave this page? You have unsaved changes.')) {
|
||||
|
@ -202,7 +202,6 @@ class IDEView extends React.Component {
|
|||
<Toolbar
|
||||
className="Toolbar"
|
||||
isPlaying={this.props.ide.isPlaying}
|
||||
startSketch={this.props.startSketch}
|
||||
stopSketch={this.props.stopSketch}
|
||||
startTextOutput={this.props.startTextOutput}
|
||||
stopTextOutput={this.props.stopTextOutput}
|
||||
|
@ -246,7 +245,7 @@ class IDEView extends React.Component {
|
|||
<SplitPane
|
||||
split="vertical"
|
||||
defaultSize={this.sidebarSize}
|
||||
ref="sidebarPane"
|
||||
ref={(element) => { this.sidebarPane = element; }}
|
||||
onDragFinished={this._handleSidebarPaneOnDragFinished}
|
||||
allowResize={this.props.ide.sidebarIsExpanded}
|
||||
minSize={20}
|
||||
|
@ -270,15 +269,15 @@ class IDEView extends React.Component {
|
|||
<SplitPane
|
||||
split="vertical"
|
||||
defaultSize={'50%'}
|
||||
onChange={() => (this.refs.overlay.style.display = 'block')}
|
||||
onDragFinished={() => (this.refs.overlay.style.display = 'none')}
|
||||
onChange={() => (this.overlay.style.display = 'block')}
|
||||
onDragFinished={() => (this.overlay.style.display = 'none')}
|
||||
>
|
||||
<SplitPane
|
||||
split="horizontal"
|
||||
primary="second"
|
||||
defaultSize={this.consoleSize}
|
||||
minSize={29}
|
||||
ref="consolePane"
|
||||
ref={(element) => { this.consolePane = element; }}
|
||||
onDragFinished={this._handleConsolePaneOnDragFinished}
|
||||
allowResize={this.props.ide.consoleIsExpanded}
|
||||
className="editor-preview-subpanel"
|
||||
|
@ -286,7 +285,6 @@ class IDEView extends React.Component {
|
|||
<Editor
|
||||
lintWarning={this.props.preferences.lintWarning}
|
||||
lintMessages={this.props.editorAccessibility.lintMessages}
|
||||
updateLineNumber={this.props.updateLineNumber}
|
||||
updateLintMessage={this.props.updateLintMessage}
|
||||
clearLintMessage={this.props.clearLintMessage}
|
||||
file={this.props.selectedFile}
|
||||
|
@ -295,8 +293,6 @@ class IDEView extends React.Component {
|
|||
indentationAmount={this.props.preferences.indentationAmount}
|
||||
isTabIndent={this.props.preferences.isTabIndent}
|
||||
files={this.props.files}
|
||||
lintMessages={this.props.editorAccessibility.lintMessages}
|
||||
lineNumber={this.props.editorAccessibility.lineNumber}
|
||||
editorOptionsVisible={this.props.ide.editorOptionsVisible}
|
||||
showEditorOptions={this.props.showEditorOptions}
|
||||
closeEditorOptions={this.props.closeEditorOptions}
|
||||
|
@ -317,7 +313,6 @@ class IDEView extends React.Component {
|
|||
/>
|
||||
<Console
|
||||
consoleEvents={this.props.console}
|
||||
isPlaying={this.props.ide.isPlaying}
|
||||
isExpanded={this.props.ide.consoleIsExpanded}
|
||||
expandConsole={this.props.expandConsole}
|
||||
collapseConsole={this.props.collapseConsole}
|
||||
|
@ -325,7 +320,7 @@ class IDEView extends React.Component {
|
|||
/>
|
||||
</SplitPane>
|
||||
<div className="preview-frame-holder">
|
||||
<div className="preview-frame-overlay" ref="overlay">
|
||||
<div className="preview-frame-overlay" ref={(element) => { this.overlay = element; }}>
|
||||
</div>
|
||||
<div>
|
||||
{(() => {
|
||||
|
@ -351,7 +346,6 @@ class IDEView extends React.Component {
|
|||
endSketchRefresh={this.props.endSketchRefresh}
|
||||
stopSketch={this.props.stopSketch}
|
||||
setBlobUrl={this.props.setBlobUrl}
|
||||
stopSketch={this.props.stopSketch}
|
||||
expandConsole={this.props.expandConsole}
|
||||
/>
|
||||
</div>
|
||||
|
@ -449,10 +443,10 @@ IDEView.propTypes = {
|
|||
project_id: PropTypes.string,
|
||||
username: PropTypes.string,
|
||||
reset_password_token: PropTypes.string,
|
||||
}),
|
||||
}).isRequired,
|
||||
location: PropTypes.shape({
|
||||
pathname: PropTypes.string
|
||||
}),
|
||||
}).isRequired,
|
||||
getProject: PropTypes.func.isRequired,
|
||||
user: PropTypes.shape({
|
||||
authenticated: PropTypes.bool.isRequired,
|
||||
|
@ -483,12 +477,9 @@ IDEView.propTypes = {
|
|||
justOpenedProject: PropTypes.bool.isRequired,
|
||||
errorType: PropTypes.string
|
||||
}).isRequired,
|
||||
startSketch: PropTypes.func.isRequired,
|
||||
stopSketch: PropTypes.func.isRequired,
|
||||
startTextOutput: PropTypes.func.isRequired,
|
||||
stopTextOutput: PropTypes.func.isRequired,
|
||||
detectInfiniteLoops: PropTypes.func.isRequired,
|
||||
resetInfiniteLoops: PropTypes.func.isRequired,
|
||||
project: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
|
@ -501,11 +492,9 @@ IDEView.propTypes = {
|
|||
openPreferences: PropTypes.func.isRequired,
|
||||
editorAccessibility: PropTypes.shape({
|
||||
lintMessages: PropTypes.array.isRequired,
|
||||
lineNumber: PropTypes.string.isRequired
|
||||
}).isRequired,
|
||||
updateLintMessage: PropTypes.func.isRequired,
|
||||
clearLintMessage: PropTypes.func.isRequired,
|
||||
updateLineNumber: PropTypes.func.isRequired,
|
||||
preferences: PropTypes.shape({
|
||||
fontSize: PropTypes.number.isRequired,
|
||||
indentationAmount: PropTypes.number.isRequired,
|
||||
|
@ -524,14 +513,22 @@ IDEView.propTypes = {
|
|||
setAutosave: PropTypes.func.isRequired,
|
||||
setLintWarning: PropTypes.func.isRequired,
|
||||
setTextOutput: PropTypes.func.isRequired,
|
||||
files: PropTypes.array.isRequired,
|
||||
files: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
content: PropTypes.string.isRequired
|
||||
})).isRequired,
|
||||
updateFileContent: PropTypes.func.isRequired,
|
||||
selectedFile: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
content: PropTypes.string.isRequired
|
||||
}),
|
||||
}).isRequired,
|
||||
setSelectedFile: PropTypes.func.isRequired,
|
||||
htmlFile: PropTypes.object.isRequired,
|
||||
htmlFile: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
content: PropTypes.string.isRequired
|
||||
}).isRequired,
|
||||
dispatchConsoleEvent: PropTypes.func.isRequired,
|
||||
newFile: PropTypes.func.isRequired,
|
||||
closeNewFileModal: PropTypes.func.isRequired,
|
||||
|
@ -565,13 +562,11 @@ IDEView.propTypes = {
|
|||
toast: PropTypes.shape({
|
||||
isVisible: PropTypes.bool.isRequired
|
||||
}).isRequired,
|
||||
showToast: PropTypes.func.isRequired,
|
||||
setToastText: PropTypes.func.isRequired,
|
||||
autosaveProject: PropTypes.func.isRequired,
|
||||
router: PropTypes.shape({
|
||||
setRouteLeaveHook: PropTypes.func
|
||||
}).isRequired,
|
||||
route: PropTypes.object.isRequired,
|
||||
route: PropTypes.oneOfType([PropTypes.object, PropTypes.element]).isRequired,
|
||||
setUnsavedChanges: PropTypes.func.isRequired,
|
||||
setTheme: PropTypes.func.isRequired,
|
||||
setAutorefresh: PropTypes.func.isRequired,
|
||||
|
@ -580,8 +575,10 @@ IDEView.propTypes = {
|
|||
startRefreshSketch: PropTypes.func.isRequired,
|
||||
setBlobUrl: PropTypes.func.isRequired,
|
||||
setPreviousPath: PropTypes.func.isRequired,
|
||||
resetProject: PropTypes.func.isRequired,
|
||||
console: PropTypes.array.isRequired,
|
||||
console: PropTypes.arrayOf(PropTypes.shape({
|
||||
method: PropTypes.string.isRequired,
|
||||
args: PropTypes.arrayOf(PropTypes.string)
|
||||
})).isRequired,
|
||||
clearConsole: PropTypes.func.isRequired,
|
||||
showErrorModal: PropTypes.func.isRequired,
|
||||
hideErrorModal: PropTypes.func.isRequired
|
||||
|
|
|
@ -2,11 +2,18 @@ import * as ActionTypes from '../../../constants';
|
|||
|
||||
const consoleMax = 200;
|
||||
const initialState = [];
|
||||
let messageId = 0;
|
||||
|
||||
const console = (state = initialState, action) => {
|
||||
let messages;
|
||||
switch (action.type) {
|
||||
case ActionTypes.CONSOLE_EVENT:
|
||||
return state.concat(action.event).slice(-consoleMax);
|
||||
messages = [...action.event];
|
||||
messages.forEach((message) => {
|
||||
message.id = messageId;
|
||||
messageId += 1;
|
||||
});
|
||||
return state.concat(messages).slice(-consoleMax);
|
||||
case ActionTypes.CLEAR_CONSOLE:
|
||||
return [];
|
||||
default:
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
import * as ActionTypes from '../../../constants';
|
||||
|
||||
const initialState = {
|
||||
lineNumber: 'line',
|
||||
lintMessages: []
|
||||
};
|
||||
let messageId = 0;
|
||||
|
||||
const editorAccessibility = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.UPDATE_LINT_MESSAGE:
|
||||
messageId += 1;
|
||||
return Object.assign({}, state, {
|
||||
lintMessages: state.lintMessages.concat(
|
||||
{ severity: action.severity, line: action.line, message: action.message })
|
||||
{ severity: action.severity, line: action.line, message: action.message, id: messageId })
|
||||
});
|
||||
case ActionTypes.CLEAR_LINT_MESSAGE:
|
||||
return Object.assign({}, state, { lintMessages: [] });
|
||||
case ActionTypes.UPDATE_LINENUMBER:
|
||||
return Object.assign({}, state, { lineNumber: `line ${action.lineNumber}` });
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as ActionTypes from '../../../constants';
|
||||
import objectID from 'bson-objectid';
|
||||
import * as ActionTypes from '../../../constants';
|
||||
|
||||
const defaultSketch = `function setup() {
|
||||
createCanvas(400, 400);
|
||||
|
@ -42,7 +42,8 @@ const initialState = () => {
|
|||
id: r,
|
||||
_id: r,
|
||||
children: [a, b, c],
|
||||
fileType: 'folder'
|
||||
fileType: 'folder',
|
||||
content: ''
|
||||
},
|
||||
{
|
||||
name: 'sketch.js',
|
||||
|
@ -92,7 +93,7 @@ function deleteChild(state, parentId, id) {
|
|||
|
||||
function deleteMany(state, ids) {
|
||||
const newState = [...state];
|
||||
ids.forEach(id => {
|
||||
ids.forEach((id) => {
|
||||
let fileIndex;
|
||||
newState.find((file, index) => {
|
||||
if (file.id === id) {
|
||||
|
@ -111,7 +112,7 @@ const files = (state, action) => {
|
|||
}
|
||||
switch (action.type) {
|
||||
case ActionTypes.UPDATE_FILE_CONTENT:
|
||||
return state.map(file => {
|
||||
return state.map((file) => {
|
||||
if (file.name !== action.name) {
|
||||
return file;
|
||||
}
|
||||
|
@ -119,7 +120,7 @@ const files = (state, action) => {
|
|||
return Object.assign({}, file, { content: action.content });
|
||||
});
|
||||
case ActionTypes.SET_BLOB_URL:
|
||||
return state.map(file => {
|
||||
return state.map((file) => {
|
||||
if (file.name !== action.name) {
|
||||
return file;
|
||||
}
|
||||
|
@ -151,7 +152,7 @@ const files = (state, action) => {
|
|||
fileType: action.fileType || 'file' }];
|
||||
}
|
||||
case ActionTypes.SHOW_FILE_OPTIONS:
|
||||
return state.map(file => {
|
||||
return state.map((file) => {
|
||||
if (file.id !== action.id) {
|
||||
return file;
|
||||
}
|
||||
|
@ -159,7 +160,7 @@ const files = (state, action) => {
|
|||
return Object.assign({}, file, { isOptionsOpen: true });
|
||||
});
|
||||
case ActionTypes.HIDE_FILE_OPTIONS:
|
||||
return state.map(file => {
|
||||
return state.map((file) => {
|
||||
if (file.id !== action.id) {
|
||||
return file;
|
||||
}
|
||||
|
@ -167,7 +168,7 @@ const files = (state, action) => {
|
|||
return Object.assign({}, file, { isOptionsOpen: false });
|
||||
});
|
||||
case ActionTypes.UPDATE_FILE_NAME:
|
||||
return state.map(file => {
|
||||
return state.map((file) => {
|
||||
if (file.id !== action.id) {
|
||||
return file;
|
||||
}
|
||||
|
@ -188,7 +189,7 @@ const files = (state, action) => {
|
|||
// return newState.filter(file => file.id !== action.id);
|
||||
}
|
||||
case ActionTypes.SHOW_EDIT_FILE_NAME:
|
||||
return state.map(file => {
|
||||
return state.map((file) => {
|
||||
if (file.id !== action.id) {
|
||||
return file;
|
||||
}
|
||||
|
@ -196,7 +197,7 @@ const files = (state, action) => {
|
|||
return Object.assign({}, file, { isEditingName: true });
|
||||
});
|
||||
case ActionTypes.HIDE_EDIT_FILE_NAME:
|
||||
return state.map(file => {
|
||||
return state.map((file) => {
|
||||
if (file.id !== action.id) {
|
||||
return file;
|
||||
}
|
||||
|
@ -204,21 +205,21 @@ const files = (state, action) => {
|
|||
return Object.assign({}, file, { isEditingName: false });
|
||||
});
|
||||
case ActionTypes.SET_SELECTED_FILE:
|
||||
return state.map(file => {
|
||||
return state.map((file) => {
|
||||
if (file.id === action.selectedFile) {
|
||||
return Object.assign({}, file, { isSelectedFile: true });
|
||||
}
|
||||
return Object.assign({}, file, { isSelectedFile: false });
|
||||
});
|
||||
case ActionTypes.SHOW_FOLDER_CHILDREN:
|
||||
return state.map(file => {
|
||||
return state.map((file) => {
|
||||
if (file.id === action.id) {
|
||||
return Object.assign({}, file, { isFolderClosed: false });
|
||||
}
|
||||
return file;
|
||||
});
|
||||
case ActionTypes.HIDE_FOLDER_CHILDREN:
|
||||
return state.map(file => {
|
||||
return state.map((file) => {
|
||||
if (file.id === action.id) {
|
||||
return Object.assign({}, file, { isFolderClosed: true });
|
||||
}
|
||||
|
@ -229,9 +230,9 @@ const files = (state, action) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const getHTMLFile = (state) => state.filter(file => file.name.match(/.*\.html$/i))[0];
|
||||
export const getJSFiles = (state) => state.filter(file => file.name.match(/.*\.js$/i));
|
||||
export const getCSSFiles = (state) => state.filter(file => file.name.match(/.*\.css$/i));
|
||||
export const getLinkedFiles = (state) => state.filter(file => file.url);
|
||||
export const getHTMLFile = state => state.filter(file => file.name.match(/.*\.html$/i))[0];
|
||||
export const getJSFiles = state => state.filter(file => file.name.match(/.*\.js$/i));
|
||||
export const getCSSFiles = state => state.filter(file => file.name.match(/.*\.css$/i));
|
||||
export const getLinkedFiles = state => state.filter(file => file.url);
|
||||
|
||||
export default files;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as ActionTypes from '../../../constants';
|
||||
import generate from 'project-name-generator';
|
||||
import * as ActionTypes from '../../../constants';
|
||||
|
||||
const initialState = () => {
|
||||
const generatedString = generate({ words: 2 }).spaced;
|
||||
|
|
|
@ -5,7 +5,7 @@ const sketches = (state = [], action) => {
|
|||
case ActionTypes.SET_PROJECTS:
|
||||
return action.projects;
|
||||
case ActionTypes.DELETE_PROJECT:
|
||||
return state.filter((sketch) =>
|
||||
return state.filter(sketch =>
|
||||
sketch.id !== action.id
|
||||
);
|
||||
default:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as ActionTypes from '../../constants';
|
||||
import { browserHistory } from 'react-router';
|
||||
import axios from 'axios';
|
||||
import * as ActionTypes from '../../constants';
|
||||
import { showErrorModal, justOpenedProject } from '../IDE/actions/ide';
|
||||
|
||||
|
||||
|
@ -16,7 +16,7 @@ export function authError(error) {
|
|||
export function signUpUser(previousPath, formValues) {
|
||||
return (dispatch) => {
|
||||
axios.post(`${ROOT_URL}/signup`, formValues, { withCredentials: true })
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
dispatch({ type: ActionTypes.AUTH_USER,
|
||||
user: response.data
|
||||
});
|
||||
|
@ -48,7 +48,7 @@ export function loginUserFailure(error) {
|
|||
export function validateAndLoginUser(previousPath, formProps, dispatch) {
|
||||
return new Promise((resolve, reject) => {
|
||||
loginUser(formProps)
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
dispatch({ type: ActionTypes.AUTH_USER,
|
||||
user: response.data
|
||||
});
|
||||
|
@ -60,7 +60,7 @@ export function validateAndLoginUser(previousPath, formProps, dispatch) {
|
|||
browserHistory.push(previousPath);
|
||||
resolve();
|
||||
})
|
||||
.catch(response => {
|
||||
.catch((response) => {
|
||||
reject({ password: response.data.message, _error: 'Login failed!' });
|
||||
});
|
||||
});
|
||||
|
@ -69,7 +69,7 @@ export function validateAndLoginUser(previousPath, formProps, dispatch) {
|
|||
export function getUser() {
|
||||
return (dispatch) => {
|
||||
axios.get(`${ROOT_URL}/session`, { withCredentials: true })
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: ActionTypes.AUTH_USER,
|
||||
user: response.data
|
||||
|
@ -79,7 +79,7 @@ export function getUser() {
|
|||
preferences: response.data.preferences
|
||||
});
|
||||
})
|
||||
.catch(response => {
|
||||
.catch((response) => {
|
||||
dispatch(authError(response.data.error));
|
||||
});
|
||||
};
|
||||
|
@ -88,13 +88,13 @@ export function getUser() {
|
|||
export function validateSession() {
|
||||
return (dispatch, getState) => {
|
||||
axios.get(`${ROOT_URL}/session`, { withCredentials: true })
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
const state = getState();
|
||||
if (state.user.username !== response.data.username) {
|
||||
dispatch(showErrorModal('staleSession'));
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
.catch((response) => {
|
||||
if (response.status === 404) {
|
||||
dispatch(showErrorModal('staleSession'));
|
||||
}
|
||||
|
|
|
@ -40,9 +40,14 @@ LoginForm.propTypes = {
|
|||
handleSubmit: PropTypes.func.isRequired,
|
||||
validateAndLoginUser: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool,
|
||||
invalid: PropTypes.bool,
|
||||
pristine: PropTypes.bool,
|
||||
previousPath: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
LoginForm.defaultProps = {
|
||||
submitting: false,
|
||||
pristine: true,
|
||||
invalid: false
|
||||
};
|
||||
|
||||
export default LoginForm;
|
||||
|
|
|
@ -44,7 +44,13 @@ NewPasswordForm.propTypes = {
|
|||
pristine: PropTypes.bool,
|
||||
params: PropTypes.shape({
|
||||
reset_password_token: PropTypes.string,
|
||||
}),
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
NewPasswordForm.defaultProps = {
|
||||
invalid: false,
|
||||
pristine: true,
|
||||
submitting: false
|
||||
};
|
||||
|
||||
export default NewPasswordForm;
|
||||
|
|
|
@ -31,7 +31,13 @@ ResetPasswordForm.propTypes = {
|
|||
pristine: PropTypes.bool,
|
||||
user: PropTypes.shape({
|
||||
resetPasswordInitiate: PropTypes.bool
|
||||
})
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
ResetPasswordForm.defaultProps = {
|
||||
submitting: false,
|
||||
pristine: true,
|
||||
invalid: false
|
||||
};
|
||||
|
||||
export default ResetPasswordForm;
|
||||
|
|
|
@ -69,4 +69,10 @@ SignupForm.propTypes = {
|
|||
previousPath: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
SignupForm.defaultProps = {
|
||||
submitting: false,
|
||||
pristine: true,
|
||||
invalid: false
|
||||
};
|
||||
|
||||
export default SignupForm;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { reduxForm } from 'redux-form';
|
||||
import { Link, browserHistory } from 'react-router';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
import { validateAndLoginUser } from '../actions';
|
||||
import LoginForm from '../components/LoginForm';
|
||||
// import GithubButton from '../components/GithubButton';
|
||||
import { Link, browserHistory } from 'react-router';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
const logoUrl = require('../../../images/p5js-logo.svg');
|
||||
|
||||
|
@ -41,7 +41,7 @@ class LoginView extends React.Component {
|
|||
{/* <h2 className="form-container__divider">Or</h2>
|
||||
<GithubButton buttonText="Login with Github" /> */}
|
||||
<p className="form__navigation-options">
|
||||
Don't have an account?
|
||||
Don't have an account?
|
||||
<Link className="form__signup-button" to="/signup">Sign Up</Link>
|
||||
</p>
|
||||
<p className="form__navigation-options">
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { reduxForm } from 'redux-form';
|
||||
import NewPasswordForm from '../components/NewPasswordForm';
|
||||
import * as UserActions from '../actions';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import classNames from 'classnames';
|
||||
import { browserHistory } from 'react-router';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import NewPasswordForm from '../components/NewPasswordForm';
|
||||
import * as UserActions from '../actions';
|
||||
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
const logoUrl = require('../../../images/p5js-logo.svg');
|
||||
|
||||
|
@ -55,11 +56,11 @@ class NewPasswordView extends React.Component {
|
|||
NewPasswordView.propTypes = {
|
||||
params: PropTypes.shape({
|
||||
reset_password_token: PropTypes.string,
|
||||
}),
|
||||
}).isRequired,
|
||||
validateResetPasswordToken: PropTypes.func.isRequired,
|
||||
user: PropTypes.shape({
|
||||
resetPasswordInvalid: PropTypes.bool
|
||||
})
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
function validate(formProps) {
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { Link, browserHistory } from 'react-router';
|
||||
import * as UserActions from '../actions';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { reduxForm } from 'redux-form';
|
||||
import ResetPasswordForm from '../components/ResetPasswordForm';
|
||||
import classNames from 'classnames';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { reduxForm } from 'redux-form';
|
||||
import * as UserActions from '../actions';
|
||||
import ResetPasswordForm from '../components/ResetPasswordForm';
|
||||
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
const logoUrl = require('../../../images/p5js-logo.svg');
|
||||
|
||||
|
@ -43,7 +44,7 @@ class ResetPasswordView extends React.Component {
|
|||
<h2 className="form-container__title">Reset Your Password</h2>
|
||||
<ResetPasswordForm {...this.props} />
|
||||
<p className="reset-password__submitted">
|
||||
Your password reset email should arrive shortly. If you don't see it, check
|
||||
Your password reset email should arrive shortly. If you don't see it, check
|
||||
in your spam folder as sometimes it can end up there.
|
||||
</p>
|
||||
<p className="form__navigation-options">
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import * as UserActions from '../actions';
|
||||
import { reduxForm } from 'redux-form';
|
||||
import SignupForm from '../components/SignupForm';
|
||||
import axios from 'axios';
|
||||
import { Link, browserHistory } from 'react-router';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
import { reduxForm } from 'redux-form';
|
||||
import * as UserActions from '../actions';
|
||||
import SignupForm from '../components/SignupForm';
|
||||
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
const logoUrl = require('../../../images/p5js-logo.svg');
|
||||
|
||||
|
@ -66,7 +67,7 @@ function asyncValidate(formProps, dispatch, props) {
|
|||
queryParams[fieldToValidate] = formProps[fieldToValidate];
|
||||
queryParams.check_type = fieldToValidate;
|
||||
return axios.get('/api/signup/duplicate_check', { params: queryParams })
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
if (response.data.exists) {
|
||||
const error = {};
|
||||
error[fieldToValidate] = response.data.message;
|
||||
|
@ -90,7 +91,7 @@ function validate(formProps) {
|
|||
|
||||
if (!formProps.email) {
|
||||
errors.email = 'Please enter an email.';
|
||||
} else if (!formProps.email.match(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i)) {
|
||||
} else if (!formProps.email.match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i)) {
|
||||
errors.email = 'Please enter a valid email address.';
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { combineReducers } from 'redux';
|
||||
import { reducer as form } from 'redux-form';
|
||||
import files from './modules/IDE/reducers/files';
|
||||
import ide from './modules/IDE/reducers/ide';
|
||||
import preferences from './modules/IDE/reducers/preferences';
|
||||
|
@ -8,7 +9,6 @@ import user from './modules/User/reducers';
|
|||
import sketches from './modules/IDE/reducers/projects';
|
||||
import toast from './modules/IDE/reducers/toast';
|
||||
import console from './modules/IDE/reducers/console';
|
||||
import { reducer as form } from 'redux-form';
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
form,
|
||||
|
|
|
@ -14,7 +14,7 @@ const checkAuth = (store) => {
|
|||
store.dispatch(getUser());
|
||||
};
|
||||
|
||||
const routes = (store) =>
|
||||
const routes = store =>
|
||||
(
|
||||
<Route path="/" component={App}>
|
||||
<IndexRoute component={IDEView} onEnter={checkAuth(store)} />
|
||||
|
|
|
@ -175,3 +175,16 @@
|
|||
height:1px;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
|
||||
%link {
|
||||
@include themify() {
|
||||
text-decoration: none;
|
||||
color: getThemifyVariable('inactive-text-color');
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: getThemifyVariable('primary-text-color');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,10 +6,13 @@ html, body {
|
|||
font-size: #{$base-font-size}px;
|
||||
}
|
||||
|
||||
body, input, button {
|
||||
body, input {
|
||||
@include themify() {
|
||||
color: getThemifyVariable('primary-text-color');
|
||||
}
|
||||
}
|
||||
|
||||
body, input, button {
|
||||
font-family: Montserrat, sans-serif;
|
||||
}
|
||||
|
||||
|
@ -20,13 +23,7 @@ body, input, button {
|
|||
|
||||
a {
|
||||
@include themify() {
|
||||
text-decoration: none;
|
||||
color: getThemifyVariable('inactive-text-color');
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: getThemifyVariable('primary-text-color');
|
||||
}
|
||||
@extend %link;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,10 +31,6 @@ input, button {
|
|||
font-size: 1rem;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: #{5 / $base-font-size}rem;
|
||||
border: 1px solid ;
|
||||
|
@ -55,6 +48,14 @@ input[type="submit"] {
|
|||
}
|
||||
}
|
||||
|
||||
button {
|
||||
@include themify() {
|
||||
@extend %link;
|
||||
}
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: #{21 / $base-font-size}em;
|
||||
}
|
||||
|
@ -71,3 +72,6 @@ h6 {
|
|||
thead {
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
||||
|
|
|
@ -97,6 +97,11 @@
|
|||
}
|
||||
|
||||
.preview-console__clear {
|
||||
@include themify() {
|
||||
@extend %link;
|
||||
}
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding-right: #{10 / $base-font-size}rem;
|
||||
.preview-console--collapsed & {
|
||||
display: none;
|
||||
|
|
|
@ -24,7 +24,8 @@
|
|||
margin-right: #{-6 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.preference button {
|
||||
.preference__minus-button,
|
||||
.preference__plus-button {
|
||||
@include themify() {
|
||||
@extend %preferences-button;
|
||||
width: #{32 / $base-font-size}rem;
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
height: #{20 / $base-font-size}rem;
|
||||
font-size: #{12 / $base-font-size}rem;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
@include themify() {
|
||||
color: map-get($theme-map, 'inactive-text-color');
|
||||
&:hover > .file-item__content .sidebar__file-item-name {
|
||||
|
@ -106,6 +107,15 @@
|
|||
.sidebar__file-item--editing & {
|
||||
display: none;
|
||||
}
|
||||
&:before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
content: '';
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar__file-item-show-options {
|
||||
|
@ -225,6 +235,8 @@
|
|||
& svg {
|
||||
height: #{10 / $base-font-size}rem;
|
||||
}
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.sidebar__file-item-closed {
|
||||
|
@ -260,6 +272,14 @@
|
|||
width: #{145 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.sidebar__file-item-option {
|
||||
@include themify() {
|
||||
@extend %link;
|
||||
}
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
||||
.sidebar__file-item--closed .file-item__children {
|
||||
display: none;
|
||||
|
|
28
package.json
28
package.json
|
@ -20,27 +20,25 @@
|
|||
"url": "git+https://github.com/catarak/p5.js-web-editor.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^6.1.0",
|
||||
"babel-eslint": "^7.1.1",
|
||||
"babel-loader": "^6.2.4",
|
||||
"babel-plugin-transform-react-constant-elements": "^6.8.0",
|
||||
"babel-plugin-transform-react-inline-elements": "^6.8.0",
|
||||
"babel-plugin-transform-react-remove-prop-types": "^0.2.6",
|
||||
"babel-polyfill": "^6.8.0",
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"babel-preset-es2015-native-modules": "^6.9.2",
|
||||
"babel-preset-react": "^6.5.0",
|
||||
"babel-preset-react-hmre": "^1.1.1",
|
||||
"babel-preset-react-optimize": "^1.0.1",
|
||||
"babel-preset-stage-0": "^6.5.0",
|
||||
"babel-register": "^6.8.0",
|
||||
"chunk-manifest-webpack-plugin": "^0.1.0",
|
||||
"css-loader": "^0.23.1",
|
||||
"cssnano": "^3.7.1",
|
||||
"eslint": "^2.13.1",
|
||||
"eslint-config-airbnb": "^9.0.1",
|
||||
"eslint-plugin-import": "^1.9.2",
|
||||
"eslint-plugin-jsx-a11y": "^1.5.3",
|
||||
"eslint-plugin-react": "^5.2.2",
|
||||
"eslint": "^3.14.0",
|
||||
"eslint-config-airbnb": "^14.0.0",
|
||||
"eslint-plugin-import": "^2.2.0",
|
||||
"eslint-plugin-jsx-a11y": "^3.0.2",
|
||||
"eslint-plugin-react": "^6.9.0",
|
||||
"extract-text-webpack-plugin": "^1.0.1",
|
||||
"file-loader": "^0.8.5",
|
||||
"node-sass": "^3.7.0",
|
||||
|
@ -49,14 +47,8 @@
|
|||
"postcss-focus": "^1.0.0",
|
||||
"postcss-loader": "^0.9.1",
|
||||
"postcss-reporter": "^1.3.3",
|
||||
"redux-devtools": "^3.3.1",
|
||||
"redux-devtools-dock-monitor": "^1.1.1",
|
||||
"redux-devtools-log-monitor": "^1.0.11",
|
||||
"sass-loader": "^3.2.0",
|
||||
"style-loader": "^0.13.1",
|
||||
"webpack": "^1.14.0",
|
||||
"webpack-dev-middleware": "^1.6.1",
|
||||
"webpack-hot-middleware": "^2.10.0",
|
||||
"webpack-manifest-plugin": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -67,6 +59,8 @@
|
|||
"async": "^2.0.0",
|
||||
"axios": "^0.12.0",
|
||||
"babel-core": "^6.8.0",
|
||||
"babel-polyfill": "^6.8.0",
|
||||
"babel-register": "^6.8.0",
|
||||
"bcrypt-nodejs": "0.0.3",
|
||||
"blob-util": "^1.2.1",
|
||||
"body-parser": "^1.15.1",
|
||||
|
@ -106,6 +100,9 @@
|
|||
"react-router": "^2.6.0",
|
||||
"react-split-pane": "^0.1.44",
|
||||
"redux": "^3.5.2",
|
||||
"redux-devtools": "^3.3.1",
|
||||
"redux-devtools-dock-monitor": "^1.1.1",
|
||||
"redux-devtools-log-monitor": "^1.0.11",
|
||||
"redux-form": "^5.3.3",
|
||||
"redux-thunk": "^2.1.0",
|
||||
"request": "^2.76.0",
|
||||
|
@ -113,6 +110,9 @@
|
|||
"s3-policy": "^0.2.0",
|
||||
"shortid": "^2.2.6",
|
||||
"srcdoc-polyfill": "^0.2.0",
|
||||
"webpack": "^1.14.0",
|
||||
"webpack-dev-middleware": "^1.6.1",
|
||||
"webpack-hot-middleware": "^2.10.0",
|
||||
"xhr": "^2.2.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import User from '../models/user';
|
||||
|
||||
const passport = require('passport');
|
||||
const GitHubStrategy = require('passport-github').Strategy;
|
||||
const LocalStrategy = require('passport-local').Strategy;
|
||||
|
||||
import User from '../models/user';
|
||||
|
||||
passport.serializeUser((user, done) => {
|
||||
done(null, user.id);
|
||||
});
|
||||
|
@ -30,7 +30,7 @@ passport.use(new LocalStrategy({ usernameField: 'email' }, (email, password, don
|
|||
return done(null, false, { msg: 'Invalid email or password.' });
|
||||
});
|
||||
})
|
||||
.catch((err) => done(null, false, { msg: err }));
|
||||
.catch(err => done(null, false, { msg: err }));
|
||||
}));
|
||||
|
||||
/**
|
||||
|
@ -42,20 +42,19 @@ passport.use(new GitHubStrategy({
|
|||
callbackURL: '/auth/github/callback',
|
||||
passReqToCallback: true
|
||||
}, (req, accessToken, refreshToken, profile, done) => {
|
||||
User.findOne({ github: profile.id }, (err, existingUser) => {
|
||||
User.findOne({ github: profile.id }, (findByGithubErr, existingUser) => {
|
||||
if (existingUser) {
|
||||
return done(null, existingUser);
|
||||
done(null, existingUser);
|
||||
return;
|
||||
}
|
||||
User.findOne({ email: profile._json.email }, (err, existingEmailUser) => {
|
||||
User.findOne({ email: profile._json.email }, (findByEmailErr, existingEmailUser) => {
|
||||
if (existingEmailUser) {
|
||||
existingEmailUser.email = existingEmailUser.email || profile._json.email;
|
||||
existingEmailUser.github = profile.id;
|
||||
existingEmailUser.username = existingEmailUser.username || profile.username;
|
||||
existingEmailUser.tokens.push({ kind: 'github', accessToken });
|
||||
existingEmailUser.name = existingEmailUser.name || profile.displayName;
|
||||
existingEmailUser.save((err) => {
|
||||
return done(null, existingEmailUser);
|
||||
});
|
||||
existingEmailUser.save(saveErr => done(null, existingEmailUser));
|
||||
} else {
|
||||
const user = new User();
|
||||
user.email = profile._json.email;
|
||||
|
@ -63,9 +62,7 @@ passport.use(new GitHubStrategy({
|
|||
user.username = profile.username;
|
||||
user.tokens.push({ kind: 'github', accessToken });
|
||||
user.name = profile.displayName;
|
||||
user.save((err) => {
|
||||
return done(null, user);
|
||||
});
|
||||
user.save(saveErr => done(null, user));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,3 +26,5 @@ export function signS3(req, res) {
|
|||
};
|
||||
return res.json(result);
|
||||
}
|
||||
|
||||
export default signS3;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import jsdom, { serializeDocument } from 'jsdom';
|
||||
import Project from '../models/project';
|
||||
import {
|
||||
injectMediaUrls,
|
||||
resolvePathsForElementsWithAttribute,
|
||||
resolveScripts,
|
||||
resolveStyles } from '../utils/previewGeneration';
|
||||
import jsdom, { serializeDocument } from 'jsdom';
|
||||
|
||||
export function serveProject(req, res) {
|
||||
Project.findById(req.params.project_id)
|
||||
|
@ -32,3 +32,5 @@ export function serveProject(req, res) {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default serveProject;
|
||||
|
|
|
@ -16,16 +16,18 @@ export function createFile(req, res) {
|
|||
}, (err, updatedProject) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return res.json({ success: false });
|
||||
res.json({ success: false });
|
||||
return;
|
||||
}
|
||||
const newFile = updatedProject.files[updatedProject.files.length - 1];
|
||||
updatedProject.files.id(req.body.parentId).children.push(newFile.id);
|
||||
updatedProject.save(innerErr => {
|
||||
updatedProject.save((innerErr) => {
|
||||
if (innerErr) {
|
||||
console.log(innerErr);
|
||||
return res.json({ success: false });
|
||||
res.json({ success: false });
|
||||
return;
|
||||
}
|
||||
return res.json(updatedProject.files[updatedProject.files.length - 1]);
|
||||
res.json(updatedProject.files[updatedProject.files.length - 1]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -38,13 +40,13 @@ function getAllDescendantIds(files, nodeId) {
|
|||
}
|
||||
|
||||
function deleteMany(files, ids) {
|
||||
ids.forEach(id => {
|
||||
ids.forEach((id) => {
|
||||
files.id(id).remove();
|
||||
});
|
||||
}
|
||||
|
||||
function deleteChild(files, parentId, id) {
|
||||
files = files.map((file) => {
|
||||
return files.map((file) => {
|
||||
if (file.id === parentId) {
|
||||
file.children = file.children.filter(child => child !== id);
|
||||
return file;
|
||||
|
@ -57,11 +59,11 @@ export function deleteFile(req, res) {
|
|||
Project.findById(req.params.project_id, (err, project) => {
|
||||
const idsToDelete = getAllDescendantIds(project.files, req.params.file_id);
|
||||
deleteMany(project.files, [req.params.file_id, ...idsToDelete]);
|
||||
deleteChild(project.files, req.query.parentId, req.params.file_id);
|
||||
project.files = deleteChild(project.files, req.query.parentId, req.params.file_id);
|
||||
// project.files.id(req.params.file_id).remove();
|
||||
// const childrenArray = project.files.id(req.query.parentId).children;
|
||||
// project.files.id(req.query.parentId).children = childrenArray.filter(id => id !== req.params.file_id);
|
||||
project.save(innerErr => {
|
||||
project.save((innerErr) => {
|
||||
res.json(project.files);
|
||||
});
|
||||
});
|
||||
|
@ -70,12 +72,14 @@ export function deleteFile(req, res) {
|
|||
export function getFileContent(req, res) {
|
||||
Project.findById(req.params.project_id, (err, project) => {
|
||||
if (err) {
|
||||
return res.status(404).send({ success: false, message: 'Project with that id does not exist.' });
|
||||
res.status(404).send({ success: false, message: 'Project with that id does not exist.' });
|
||||
return;
|
||||
}
|
||||
const filePath = req.params[0];
|
||||
const resolvedFile = resolvePathToFile(filePath, project.files);
|
||||
if (!resolvedFile) {
|
||||
return res.status(404).send({ success: false, message: 'File with that name and path does not exist.' });
|
||||
res.status(404).send({ success: false, message: 'File with that name and path does not exist.' });
|
||||
return;
|
||||
}
|
||||
res.send(resolvedFile.content);
|
||||
});
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import Project from '../models/project';
|
||||
import User from '../models/user';
|
||||
import archiver from 'archiver';
|
||||
import request from 'request';
|
||||
import moment from 'moment';
|
||||
import Project from '../models/project';
|
||||
import User from '../models/user';
|
||||
|
||||
|
||||
export function createProject(req, res) {
|
||||
if (!req.user) {
|
||||
return res.status(403).send({ success: false, message: 'Session does not match owner of project.' });
|
||||
res.status(403).send({ success: false, message: 'Session does not match owner of project.' });
|
||||
return;
|
||||
}
|
||||
|
||||
let projectValues = {
|
||||
|
@ -17,20 +17,27 @@ export function createProject(req, res) {
|
|||
projectValues = Object.assign(projectValues, req.body);
|
||||
|
||||
Project.create(projectValues, (err, newProject) => {
|
||||
if (err) { return res.json({ success: false }); }
|
||||
if (err) {
|
||||
res.json({ success: false });
|
||||
return;
|
||||
}
|
||||
Project.populate(newProject,
|
||||
{ path: 'user', select: 'username' },
|
||||
(innerErr, newProjectWithUser) => {
|
||||
if (innerErr) { return res.json({ success: false }); }
|
||||
return res.json(newProjectWithUser);
|
||||
if (innerErr) {
|
||||
res.json({ success: false });
|
||||
return;
|
||||
}
|
||||
res.json(newProjectWithUser);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function updateProject(req, res) {
|
||||
Project.findById(req.params.project_id, (err, project) => {
|
||||
Project.findById(req.params.project_id, (findProjectErr, project) => {
|
||||
if (!req.user || !project.user.equals(req.user._id)) {
|
||||
return res.status(403).send({ success: false, message: 'Session does not match owner of project.' });
|
||||
res.status(403).send({ success: false, message: 'Session does not match owner of project.' });
|
||||
return;
|
||||
}
|
||||
// if (req.body.updatedAt && moment(req.body.updatedAt) < moment(project.updatedAt)) {
|
||||
// return res.status(409).send({ success: false, message: 'Attempted to save stale version of project.' });
|
||||
|
@ -40,27 +47,29 @@ export function updateProject(req, res) {
|
|||
$set: req.body
|
||||
})
|
||||
.populate('user', 'username')
|
||||
.exec((err, updatedProject) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return res.json({ success: false });
|
||||
.exec((updateProjectErr, updatedProject) => {
|
||||
if (updateProjectErr) {
|
||||
console.log(updateProjectErr);
|
||||
res.json({ success: false });
|
||||
return;
|
||||
}
|
||||
if (updatedProject.files.length !== req.body.files.length) {
|
||||
const oldFileIds = updatedProject.files.map(file => file.id);
|
||||
const newFileIds = req.body.files.map(file => file.id);
|
||||
const staleIds = oldFileIds.filter(id => newFileIds.indexOf(id) === -1);
|
||||
staleIds.forEach(staleId => {
|
||||
staleIds.forEach((staleId) => {
|
||||
updatedProject.files.id(staleId).remove();
|
||||
});
|
||||
updatedProject.save((innerErr) => {
|
||||
if (innerErr) {
|
||||
console.log(innerErr);
|
||||
return res.json({ success: false });
|
||||
res.json({ success: false });
|
||||
return;
|
||||
}
|
||||
return res.json(updatedProject);
|
||||
res.json(updatedProject);
|
||||
});
|
||||
}
|
||||
return res.json(updatedProject);
|
||||
res.json(updatedProject);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -77,15 +86,17 @@ export function getProject(req, res) {
|
|||
}
|
||||
|
||||
export function deleteProject(req, res) {
|
||||
Project.findById(req.params.project_id, (err, project) => {
|
||||
Project.findById(req.params.project_id, (findProjectErr, project) => {
|
||||
if (!req.user || !project.user.equals(req.user._id)) {
|
||||
return res.status(403).json({ success: false, message: 'Session does not match owner of project.' });
|
||||
res.status(403).json({ success: false, message: 'Session does not match owner of project.' });
|
||||
return;
|
||||
}
|
||||
Project.remove({ _id: req.params.project_id }, (err) => {
|
||||
if (err) {
|
||||
return res.status(404).send({ message: 'Project with that id does not exist' });
|
||||
Project.remove({ _id: req.params.project_id }, (removeProjectError) => {
|
||||
if (removeProjectError) {
|
||||
res.status(404).send({ message: 'Project with that id does not exist' });
|
||||
return;
|
||||
}
|
||||
return res.json({ success: true });
|
||||
res.json({ success: true });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -100,7 +111,7 @@ export function getProjects(req, res) {
|
|||
});
|
||||
} else {
|
||||
// could just move this to client side
|
||||
return res.json([]);
|
||||
res.json([]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,18 +119,18 @@ export function getProjectsForUser(req, res) {
|
|||
if (req.params.username) {
|
||||
User.findOne({ username: req.params.username }, (err, user) => {
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'User with that username does not exist.' });
|
||||
res.status(404).json({ message: 'User with that username does not exist.' });
|
||||
return;
|
||||
}
|
||||
Project.find({ user: user._id }) // eslint-disable-line no-underscore-dangle
|
||||
.sort('-createdAt')
|
||||
.select('name files id createdAt updatedAt')
|
||||
.exec((err, projects) => res.json(projects));
|
||||
.exec((innerErr, projects) => res.json(projects));
|
||||
});
|
||||
} else {
|
||||
// could just move this to client side
|
||||
return res.json([]);
|
||||
res.json([]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildZip(project, req, res) {
|
||||
|
@ -127,7 +138,6 @@ function buildZip(project, req, res) {
|
|||
const rootFile = project.files.find(file => file.name === 'root');
|
||||
const numFiles = project.files.filter(file => file.fileType !== 'folder').length;
|
||||
const files = project.files;
|
||||
const projectName = project.name;
|
||||
let numCompletedFiles = 0;
|
||||
|
||||
zip.on('error', (err) => {
|
||||
|
@ -140,14 +150,13 @@ function buildZip(project, req, res) {
|
|||
function addFileToZip(file, path) {
|
||||
if (file.fileType === 'folder') {
|
||||
const newPath = file.name === 'root' ? path : `${path}${file.name}/`;
|
||||
file.children.forEach(fileId => {
|
||||
file.children.forEach((fileId) => {
|
||||
const childFile = files.find(f => f.id === fileId);
|
||||
(() => {
|
||||
addFileToZip(childFile, newPath);
|
||||
})();
|
||||
});
|
||||
} else {
|
||||
if (file.url) {
|
||||
} else if (file.url) {
|
||||
request({ method: 'GET', url: file.url, encoding: null }, (err, response, body) => {
|
||||
zip.append(body, { name: `${path}${file.name}` });
|
||||
numCompletedFiles += 1;
|
||||
|
@ -163,7 +172,6 @@ function buildZip(project, req, res) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
addFileToZip(rootFile, '/');
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import User from '../models/user';
|
||||
import crypto from 'crypto';
|
||||
import async from 'async';
|
||||
import nodemailer from 'nodemailer';
|
||||
import mg from 'nodemailer-mailgun-transport';
|
||||
import User from '../models/user';
|
||||
|
||||
export function createUser(req, res, next) {
|
||||
const user = new User({
|
||||
|
@ -12,17 +12,25 @@ export function createUser(req, res, next) {
|
|||
});
|
||||
|
||||
User.findOne({ email: req.body.email },
|
||||
(err, existingUser) => { // eslint-disable-line consistent-return
|
||||
if (err) { res.status(404).send({ error: err }); }
|
||||
(err, existingUser) => {
|
||||
if (err) {
|
||||
res.status(404).send({ error: err });
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingUser) {
|
||||
return res.status(422).send({ error: 'Email is in use' });
|
||||
res.status(422).send({ error: 'Email is in use' });
|
||||
return;
|
||||
}
|
||||
user.save((saveErr) => { // eslint-disable-line consistent-return
|
||||
if (saveErr) { return next(saveErr); }
|
||||
req.logIn(user, (loginErr) => { // eslint-disable-line consistent-return
|
||||
user.save((saveErr) => {
|
||||
if (saveErr) {
|
||||
next(saveErr);
|
||||
return;
|
||||
}
|
||||
req.logIn(user, (loginErr) => {
|
||||
if (loginErr) {
|
||||
return next(loginErr);
|
||||
next(loginErr);
|
||||
return;
|
||||
}
|
||||
res.json({
|
||||
email: req.user.email,
|
||||
|
@ -58,21 +66,24 @@ export function duplicateUserCheck(req, res) {
|
|||
export function updatePreferences(req, res) {
|
||||
User.findById(req.user.id, (err, user) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: err });
|
||||
res.status(500).json({ error: err });
|
||||
return;
|
||||
}
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: 'Document not found' });
|
||||
res.status(404).json({ error: 'Document not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
const preferences = Object.assign({}, user.preferences, req.body.preferences);
|
||||
user.preferences = preferences;
|
||||
|
||||
user.save((err) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: err });
|
||||
user.save((saveErr) => {
|
||||
if (saveErr) {
|
||||
res.status(500).json({ error: saveErr });
|
||||
return;
|
||||
}
|
||||
|
||||
return res.json(user.preferences);
|
||||
res.json(user.preferences);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -88,13 +99,14 @@ export function resetPasswordInitiate(req, res) {
|
|||
(token, done) => {
|
||||
User.findOne({ email: req.body.email }, (err, user) => {
|
||||
if (!user) {
|
||||
return res.json({ success: true, message: 'If the email is registered with the editor, an email has been sent.' });
|
||||
res.json({ success: true, message: 'If the email is registered with the editor, an email has been sent.' });
|
||||
return;
|
||||
}
|
||||
user.resetPasswordToken = token;
|
||||
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour
|
||||
|
||||
user.save((err) => {
|
||||
done(err, token, user);
|
||||
user.save((saveErr) => {
|
||||
done(saveErr, token, user);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@ -124,41 +136,41 @@ export function resetPasswordInitiate(req, res) {
|
|||
], (err) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return res.json({ success: false });
|
||||
res.json({ success: false });
|
||||
return;
|
||||
}
|
||||
// send email here
|
||||
return res.json({ success: true, message: 'If the email is registered with the editor, an email has been sent.' });
|
||||
res.json({ success: true, message: 'If the email is registered with the editor, an email has been sent.' });
|
||||
});
|
||||
}
|
||||
|
||||
export function validateResetPasswordToken(req, res) {
|
||||
User.findOne({ resetPasswordToken: req.params.token, resetPasswordExpires: { $gt: Date.now() } }, (err, user) => {
|
||||
if (!user) {
|
||||
return res.status(401).json({ success: false, message: 'Password reset token is invalid or has expired.' });
|
||||
res.status(401).json({ success: false, message: 'Password reset token is invalid or has expired.' });
|
||||
return;
|
||||
}
|
||||
res.json({ success: true });
|
||||
});
|
||||
}
|
||||
|
||||
export function updatePassword(req, res) {
|
||||
User.findOne({ resetPasswordToken: req.params.token, resetPasswordExpires: { $gt: Date.now() } }, function (err, user) {
|
||||
User.findOne({ resetPasswordToken: req.params.token, resetPasswordExpires: { $gt: Date.now() } }, (err, user) => {
|
||||
if (!user) {
|
||||
return res.status(401).json({ success: false, message: 'Password reset token is invalid or has expired.' });
|
||||
res.status(401).json({ success: false, message: 'Password reset token is invalid or has expired.' });
|
||||
return;
|
||||
}
|
||||
|
||||
user.password = req.body.password;
|
||||
user.resetPasswordToken = undefined;
|
||||
user.resetPasswordExpires = undefined;
|
||||
|
||||
user.save(function (err) {
|
||||
req.logIn(user, function (err) {
|
||||
return res.json({
|
||||
user.save((saveErr) => {
|
||||
req.logIn(user, loginErr => res.json({
|
||||
email: req.user.email,
|
||||
username: req.user.username,
|
||||
preferences: req.user.preferences,
|
||||
id: req.user._id
|
||||
});
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -3,10 +3,9 @@ import Q from 'q';
|
|||
import mongoose from 'mongoose';
|
||||
import objectID from 'bson-objectid';
|
||||
import shortid from 'shortid';
|
||||
import eachSeries from 'async/eachSeries';
|
||||
import User from './models/user';
|
||||
import Project from './models/project';
|
||||
import async from 'async';
|
||||
import eachSeries from 'async/eachSeries';
|
||||
|
||||
const defaultHTML =
|
||||
`<!DOCTYPE html>
|
||||
|
@ -30,8 +29,8 @@ const defaultCSS =
|
|||
}
|
||||
`;
|
||||
|
||||
const client_id = process.env.GITHUB_ID;
|
||||
const client_secret = process.env.GITHUB_SECRET;
|
||||
const clientId = process.env.GITHUB_ID;
|
||||
const clientSecret = process.env.GITHUB_SECRET;
|
||||
|
||||
const headers = { 'User-Agent': 'p5js-web-editor/0.0.1' };
|
||||
|
||||
|
@ -41,144 +40,106 @@ mongoose.connection.on('error', () => {
|
|||
process.exit(1);
|
||||
});
|
||||
|
||||
getp5User();
|
||||
|
||||
function getp5User() {
|
||||
User.findOne({ username: 'p5' }, (err, user) => {
|
||||
if (err) throw err;
|
||||
|
||||
if (!user) {
|
||||
user = new User({
|
||||
username: 'p5',
|
||||
email: 'p5-examples@gmail.com',
|
||||
password: 'test'
|
||||
});
|
||||
user.save(err => {
|
||||
if (err) throw err;
|
||||
console.log('Created a user p5' + user);
|
||||
});
|
||||
}
|
||||
|
||||
Project.find({ user: user._id }, (err, projects) => {
|
||||
// if there are already some sketches, delete them
|
||||
console.log('Deleting old projects...');
|
||||
projects.forEach(project => {
|
||||
Project.remove({ _id: project._id }, err => {
|
||||
if (err) throw err;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return getCategories()
|
||||
.then(getSketchesInCategories)
|
||||
.then(getSketchContent)
|
||||
.then(createProjectsInP5user);
|
||||
});
|
||||
}
|
||||
|
||||
function getCategories() {
|
||||
let categories = [];
|
||||
const categories = [];
|
||||
const options = {
|
||||
url: 'https://api.github.com/repos/processing/p5.js-website/contents/dist/assets/examples/en?client_id=' +
|
||||
client_id + '&client_secret=' + client_secret,
|
||||
url: `https://api.github.com/repos/processing/p5.js-website/contents/dist/assets/examples/en?client_id=${
|
||||
clientId}&client_secret=${clientSecret}`,
|
||||
method: 'GET',
|
||||
headers
|
||||
};
|
||||
return rp(options).then(res => {
|
||||
return rp(options).then((res) => {
|
||||
const json = JSON.parse(res);
|
||||
|
||||
json.forEach(metadata => {
|
||||
json.forEach((metadata) => {
|
||||
let category = '';
|
||||
for (let j = 1; j < metadata.name.split('_').length; j++) {
|
||||
category += metadata.name.split('_')[j] + ' ';
|
||||
for (let j = 1; j < metadata.name.split('_').length; j += 1) {
|
||||
category += `${metadata.name.split('_')[j]} `;
|
||||
}
|
||||
categories.push({ url: metadata.url, name: category });
|
||||
});
|
||||
|
||||
return categories;
|
||||
}).catch(err => {
|
||||
}).catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
function getSketchesInCategories(categories) {
|
||||
return Q.all(categories.map(category => {
|
||||
return Q.all(categories.map((category) => {
|
||||
const options = {
|
||||
url: category.url.replace('?ref=master', '') + '?client_id=' + client_id + '&client_secret=' + client_secret,
|
||||
url: `${category.url.replace('?ref=master', '')}?client_id=${clientId}&client_secret=${clientSecret}`,
|
||||
method: 'GET',
|
||||
headers
|
||||
};
|
||||
|
||||
return rp(options).then(res => {
|
||||
let projectsInOneCategory = [];
|
||||
return rp(options).then((res) => {
|
||||
const projectsInOneCategory = [];
|
||||
const examples = JSON.parse(res);
|
||||
examples.forEach(example => {
|
||||
examples.forEach((example) => {
|
||||
let projectName;
|
||||
if (example.name === '02_Instance_Container.js') {
|
||||
for (let i = 1; i < 5; i++) {
|
||||
const instanceProjectName = category.name + ': ' + 'Instance Container ' + i;
|
||||
for (let i = 1; i < 5; i += 1) {
|
||||
const instanceProjectName = `${category.name}: Instance Container ${i}`;
|
||||
projectsInOneCategory.push({ sketchUrl: example.download_url, projectName: instanceProjectName });
|
||||
}
|
||||
} else {
|
||||
if (example.name.split('_')[1]) {
|
||||
projectName = category.name + ': ' + example.name.split('_').slice(1).join(' ').replace('.js', '');
|
||||
projectName = `${category.name}: ${example.name.split('_').slice(1).join(' ').replace('.js', '')}`;
|
||||
} else {
|
||||
projectName = category.name + ': ' + example.name.replace('.js', '');
|
||||
projectName = `${category.name}: ${example.name.replace('.js', '')}`;
|
||||
}
|
||||
projectsInOneCategory.push({ sketchUrl: example.download_url, projectName });
|
||||
}
|
||||
});
|
||||
return projectsInOneCategory;
|
||||
}).catch(err => {
|
||||
}).catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
function getSketchContent(projectsInAllCategories) {
|
||||
return Q.all(projectsInAllCategories.map(projectsInOneCategory => {
|
||||
return Q.all(projectsInOneCategory.map(project => {
|
||||
return Q.all(projectsInAllCategories.map(projectsInOneCategory => Q.all(projectsInOneCategory.map((project) => {
|
||||
const options = {
|
||||
url: project.sketchUrl.replace('?ref=master', '') + '?client_id=' + client_id + '&client_secret=' + client_secret,
|
||||
url: `${project.sketchUrl.replace('?ref=master', '')}?client_id=${clientId}&client_secret=${clientSecret}`,
|
||||
method: 'GET',
|
||||
headers
|
||||
};
|
||||
|
||||
return rp(options).then(res => {
|
||||
return rp(options).then((res) => {
|
||||
const noNumberprojectName = project.projectName.replace(/(\d+)/g, '');
|
||||
if (noNumberprojectName === 'Instance Mode : Instance Container ') {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
let splitedRes = res.split('*/')[1].split('</html>')[i] + '</html>\n';
|
||||
for (let i = 0; i < 4; i += 1) {
|
||||
const splitedRes = `${res.split('*/')[1].split('</html>')[i]}</html>\n`;
|
||||
project.sketchContent = splitedRes.replace('p5.js', 'https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.4/p5.min.js');
|
||||
}
|
||||
} else {
|
||||
project.sketchContent = res;
|
||||
}
|
||||
return project;
|
||||
}).catch(err => {
|
||||
}).catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
}));
|
||||
}));
|
||||
}))));
|
||||
}
|
||||
|
||||
function createProjectsInP5user(projectsInAllCategories) {
|
||||
let assetsfiles = [];
|
||||
const options = {
|
||||
url: 'https://api.github.com/repos/processing/p5.js-website/contents/dist/assets/examples/assets?client_id=' +
|
||||
client_id + '&client_secret=' + client_secret,
|
||||
url: `https://api.github.com/repos/processing/p5.js-website/contents/dist/assets/examples/assets?client_id=${
|
||||
clientId}&client_secret=${clientSecret}`,
|
||||
method: 'GET',
|
||||
headers
|
||||
};
|
||||
|
||||
rp(options).then(res => {
|
||||
rp(options).then((res) => {
|
||||
const assets = JSON.parse(res);
|
||||
|
||||
User.findOne({ username: 'p5' }, (err, user) => {
|
||||
if (err) throw err;
|
||||
|
||||
async.eachSeries(projectsInAllCategories, (projectsInOneCategory, categoryCallback) => {
|
||||
async.eachSeries(projectsInOneCategory, (project, projectCallback) => {
|
||||
eachSeries(projectsInAllCategories, (projectsInOneCategory, categoryCallback) => {
|
||||
eachSeries(projectsInOneCategory, (project, projectCallback) => {
|
||||
let newProject;
|
||||
const a = objectID().toHexString();
|
||||
const b = objectID().toHexString();
|
||||
|
@ -267,12 +228,12 @@ function createProjectsInP5user(projectsInAllCategories) {
|
|||
});
|
||||
}
|
||||
|
||||
let assetsInProject = project.sketchContent.match(/assets\/[\w-]+\.[\w]*/g) || project.sketchContent.match(/assets\/[\w-]*/g) || [];
|
||||
const assetsInProject = project.sketchContent.match(/assets\/[\w-]+\.[\w]*/g) || project.sketchContent.match(/assets\/[\w-]*/g) || [];
|
||||
|
||||
assetsInProject.forEach((assetName, i) => {
|
||||
assetName = assetName.split('assets/')[1];
|
||||
assetsInProject.forEach((assetNamePath, i) => {
|
||||
let assetName = assetNamePath.split('assets/')[1];
|
||||
|
||||
assets.forEach(asset => {
|
||||
assets.forEach((asset) => {
|
||||
if (asset.name === assetName || asset.name.split('.')[0] === assetName) {
|
||||
assetName = asset.name;
|
||||
}
|
||||
|
@ -301,25 +262,61 @@ function createProjectsInP5user(projectsInAllCategories) {
|
|||
children: [],
|
||||
fileType: 'file'
|
||||
});
|
||||
console.log('create assets: ' + assetName);
|
||||
console.log(`create assets: ${assetName}`);
|
||||
// add asset file inside the newly created assets folder at index 4
|
||||
newProject.files[4].children.push(fileID);
|
||||
}
|
||||
});
|
||||
|
||||
newProject.save((err, newProject) => {
|
||||
if (err) throw err;
|
||||
console.log('Created a new project in p5 user: ' + newProject.name);
|
||||
newProject.save((saveErr, savedProject) => {
|
||||
if (saveErr) throw saveErr;
|
||||
console.log(`Created a new project in p5 user: ${savedProject.name}`);
|
||||
projectCallback();
|
||||
});
|
||||
}, (err) => {
|
||||
}, (categoryErr) => {
|
||||
categoryCallback();
|
||||
});
|
||||
}, (err) => {
|
||||
}, (examplesErr) => {
|
||||
process.exit();
|
||||
});
|
||||
});
|
||||
}).catch(err => {
|
||||
}).catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
function getp5User() {
|
||||
User.findOne({ username: 'p5' }, (err, user) => {
|
||||
if (err) throw err;
|
||||
|
||||
let p5User = user;
|
||||
if (!p5User) {
|
||||
p5User = new User({
|
||||
username: 'p5',
|
||||
email: 'p5-examples@gmail.com',
|
||||
password: 'test'
|
||||
});
|
||||
p5User.save((saveErr) => {
|
||||
if (saveErr) throw saveErr;
|
||||
console.log(`Created a user p5${p5User}`);
|
||||
});
|
||||
}
|
||||
|
||||
Project.find({ user: p5User._id }, (projectsErr, projects) => {
|
||||
// if there are already some sketches, delete them
|
||||
console.log('Deleting old projects...');
|
||||
projects.forEach((project) => {
|
||||
Project.remove({ _id: project._id }, (removeErr) => {
|
||||
if (removeErr) throw removeErr;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return getCategories()
|
||||
.then(getSketchesInCategories)
|
||||
.then(getSketchContent)
|
||||
.then(createProjectsInP5user);
|
||||
});
|
||||
}
|
||||
|
||||
getp5User();
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import mongoose from 'mongoose';
|
||||
const Schema = mongoose.Schema;
|
||||
import shortid from 'shortid';
|
||||
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const fileSchema = new Schema({
|
||||
name: { type: String, default: 'sketch.js' },
|
||||
content: { type: String, default: '' },
|
||||
|
@ -11,7 +12,7 @@ const fileSchema = new Schema({
|
|||
isSelectedFile: { type: Boolean }
|
||||
}, { timestamps: true, _id: true });
|
||||
|
||||
fileSchema.virtual('id').get(function () {
|
||||
fileSchema.virtual('id').get(function getFileId() {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
|
@ -26,7 +27,7 @@ const projectSchema = new Schema({
|
|||
_id: { type: String, default: shortid.generate }
|
||||
}, { timestamps: true });
|
||||
|
||||
projectSchema.virtual('id').get(function () {
|
||||
projectSchema.virtual('id').get(function getProjectId() {
|
||||
return this._id;
|
||||
});
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import mongoose from 'mongoose';
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const bcrypt = require('bcrypt-nodejs');
|
||||
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const userSchema = new Schema({
|
||||
name: { type: String, default: '' },
|
||||
username: { type: String, required: true, unique: true },
|
||||
|
@ -39,7 +41,7 @@ userSchema.pre('save', function checkPassword(next) { // eslint-disable-line con
|
|||
});
|
||||
});
|
||||
|
||||
userSchema.virtual('id').get(function () {
|
||||
userSchema.virtual('id').get(function idToString() {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { Router } from 'express';
|
||||
const router = new Router();
|
||||
import { renderIndex } from '../views/index';
|
||||
import { get404Sketch } from '../views/404Page';
|
||||
import { userExists } from '../controllers/user.controller.js';
|
||||
import { userExists } from '../controllers/user.controller';
|
||||
|
||||
const router = new Router();
|
||||
|
||||
// this is intended to be a temporary file
|
||||
// until i figure out isomorphic rendering
|
||||
|
@ -48,7 +49,7 @@ router.route('/about').get((req, res) => {
|
|||
});
|
||||
|
||||
router.route('/:username/sketches').get((req, res) => {
|
||||
userExists(req.params.username, (exists) => (
|
||||
userExists(req.params.username, exists => (
|
||||
exists ? res.send(renderIndex()) : get404Sketch(html => res.send(html))
|
||||
));
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Router } from 'express';
|
||||
import * as UserController from '../controllers/user.controller';
|
||||
|
||||
const router = new Router();
|
||||
|
||||
router.route('/signup').post(UserController.createUser);
|
||||
|
|
|
@ -3,24 +3,15 @@ import mongoose from 'mongoose';
|
|||
import bodyParser from 'body-parser';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import session from 'express-session';
|
||||
const MongoStore = require('connect-mongo')(session);
|
||||
import connectMongo from 'connect-mongo';
|
||||
import passport from 'passport';
|
||||
import path from 'path';
|
||||
|
||||
// Webpack Requirements
|
||||
import webpack from 'webpack';
|
||||
import config from '../webpack.config.dev';
|
||||
import webpackDevMiddleware from 'webpack-dev-middleware';
|
||||
import webpackHotMiddleware from 'webpack-hot-middleware';
|
||||
|
||||
const app = new Express();
|
||||
|
||||
// Run Webpack dev server in development mode
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const compiler = webpack(config);
|
||||
app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath }));
|
||||
app.use(webpackHotMiddleware(compiler));
|
||||
}
|
||||
import config from '../webpack.config.dev';
|
||||
|
||||
// Import all required modules
|
||||
import serverConfig from './config';
|
||||
|
@ -35,6 +26,16 @@ import embedRoutes from './routes/embed.routes';
|
|||
import { renderIndex } from './views/index';
|
||||
import { get404Sketch } from './views/404Page';
|
||||
|
||||
const app = new Express();
|
||||
const MongoStore = connectMongo(session);
|
||||
|
||||
// Run Webpack dev server in development mode
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const compiler = webpack(config);
|
||||
app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath }));
|
||||
app.use(webpackHotMiddleware(compiler));
|
||||
}
|
||||
|
||||
// Body parser, cookie parser, sessions, serve public assets
|
||||
|
||||
app.use(Express.static(path.resolve(__dirname, '../static')));
|
||||
|
|
|
@ -15,7 +15,7 @@ export function resolvePathToFile(filePath, files) {
|
|||
file._id.valueOf().toString() === childFileId.valueOf()
|
||||
)
|
||||
);
|
||||
childFiles.some(childFile => {
|
||||
childFiles.some((childFile) => {
|
||||
if (childFile.name === filePathSegment) {
|
||||
currentFile = childFile;
|
||||
foundChild = true;
|
||||
|
@ -30,3 +30,5 @@ export function resolvePathToFile(filePath, files) {
|
|||
});
|
||||
return resolvedFile;
|
||||
}
|
||||
|
||||
export default resolvePathToFile;
|
||||
|
|
|
@ -10,7 +10,7 @@ function resolveLinksInString(content, files, projectId) {
|
|||
let fileStrings = content.match(STRING_REGEX);
|
||||
const fileStringRegex = /^('|")(?!(http:\/\/|https:\/\/)).*('|")$/i;
|
||||
fileStrings = fileStrings || [];
|
||||
fileStrings.forEach(fileString => {
|
||||
fileStrings.forEach((fileString) => {
|
||||
// if string does not begin with http or https
|
||||
if (fileString.match(fileStringRegex)) {
|
||||
const filePath = fileString.substr(1, fileString.length - 2);
|
||||
|
@ -35,7 +35,7 @@ function resolveLinksInString(content, files, projectId) {
|
|||
}
|
||||
|
||||
export function injectMediaUrls(filesToInject, allFiles, projectId) {
|
||||
filesToInject.forEach(file => {
|
||||
filesToInject.forEach((file) => {
|
||||
file.content = resolveLinksInString(file.content, allFiles, projectId);
|
||||
});
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ export function injectMediaUrls(filesToInject, allFiles, projectId) {
|
|||
export function resolvePathsForElementsWithAttribute(attr, sketchDoc, files) {
|
||||
const elements = sketchDoc.querySelectorAll(`[${attr}]`);
|
||||
const elementsArray = Array.prototype.slice.call(elements);
|
||||
elementsArray.forEach(element => {
|
||||
elementsArray.forEach((element) => {
|
||||
if (element.getAttribute(attr).match(MEDIA_FILE_REGEX_NO_QUOTES)) {
|
||||
const resolvedFile = resolvePathToFile(element.getAttribute(attr), files);
|
||||
if (resolvedFile) {
|
||||
|
@ -56,7 +56,7 @@ export function resolvePathsForElementsWithAttribute(attr, sketchDoc, files) {
|
|||
export function resolveScripts(sketchDoc, files, projectId) {
|
||||
const scriptsInHTML = sketchDoc.getElementsByTagName('script');
|
||||
const scriptsInHTMLArray = Array.prototype.slice.call(scriptsInHTML);
|
||||
scriptsInHTMLArray.forEach(script => {
|
||||
scriptsInHTMLArray.forEach((script) => {
|
||||
if (script.getAttribute('src') && script.getAttribute('src').match(NOT_EXTERNAL_LINK_REGEX) !== null) {
|
||||
const resolvedFile = resolvePathToFile(script.getAttribute('src'), files);
|
||||
if (resolvedFile) {
|
||||
|
@ -76,13 +76,13 @@ export function resolveScripts(sketchDoc, files, projectId) {
|
|||
export function resolveStyles(sketchDoc, files, projectId) {
|
||||
const inlineCSSInHTML = sketchDoc.getElementsByTagName('style');
|
||||
const inlineCSSInHTMLArray = Array.prototype.slice.call(inlineCSSInHTML);
|
||||
inlineCSSInHTMLArray.forEach(style => {
|
||||
inlineCSSInHTMLArray.forEach((style) => {
|
||||
style.innerHTML = resolveLinksInString(style.innerHTML, files, projectId);
|
||||
});
|
||||
|
||||
const cssLinksInHTML = sketchDoc.querySelectorAll('link[rel="stylesheet"]');
|
||||
const cssLinksInHTMLArray = Array.prototype.slice.call(cssLinksInHTML);
|
||||
cssLinksInHTMLArray.forEach(css => {
|
||||
cssLinksInHTMLArray.forEach((css) => {
|
||||
if (css.getAttribute('href') && css.getAttribute('href').match(NOT_EXTERNAL_LINK_REGEX) !== null) {
|
||||
const resolvedFile = resolvePathToFile(css.getAttribute('href'), files);
|
||||
if (resolvedFile) {
|
||||
|
|
|
@ -20,19 +20,19 @@ export function get404Sketch(callback) {
|
|||
|
||||
instanceMode = jsFiles.find(file => file.name === 'sketch.js').content.includes('Instance Mode');
|
||||
|
||||
jsFiles.forEach(file => { // Add js files as script tags
|
||||
jsFiles.forEach((file) => { // Add js files as script tags
|
||||
const html = htmlFile.split('</body>');
|
||||
html[0] = `${html[0]}<script>${file.content}</script>`;
|
||||
htmlFile = html.join('</body>');
|
||||
});
|
||||
|
||||
cssFiles.forEach(file => { // Add css files as style tags
|
||||
cssFiles.forEach((file) => { // Add css files as style tags
|
||||
const html = htmlFile.split('</head>');
|
||||
html[0] = `${html[0]}<style>${file.content}</style>`;
|
||||
htmlFile = html.join('</head>');
|
||||
});
|
||||
|
||||
linkedFiles.forEach(file => { // Add linked files as link tags
|
||||
linkedFiles.forEach((file) => { // Add linked files as link tags
|
||||
const html = htmlFile.split('<head>');
|
||||
html[1] = `<link href=${file.url}>${html[1]}`;
|
||||
htmlFile = html.join('<head>');
|
||||
|
@ -112,3 +112,6 @@ export function get404Sketch(callback) {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default get404Sketch;
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit a1c126721ac667f7750ec181c8ccf363f2658d8b
|
||||
Subproject commit 9fa934af4ca85853ba2fe911558d5f658621ea0b
|
Loading…
Reference in a new issue