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