fix merge conflict
This commit is contained in:
commit
c8f54dced9
32 changed files with 462 additions and 132 deletions
|
@ -21,6 +21,13 @@ function Nav(props) {
|
||||||
Save
|
Save
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
|
<li className="nav__item">
|
||||||
|
<p className="nav__open">
|
||||||
|
<Link to="/sketches">
|
||||||
|
Open
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul className="nav__items-right">
|
<ul className="nav__items-right">
|
||||||
<li className="nav__item">
|
<li className="nav__item">
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export const CHANGE_SELECTED_FILE = 'CHANGE_SELECTED_FILE';
|
export const UPDATE_FILE_CONTENT = 'UPDATE_FILE_CONTENT';
|
||||||
export const TOGGLE_SKETCH = 'TOGGLE_SKETCH';
|
export const TOGGLE_SKETCH = 'TOGGLE_SKETCH';
|
||||||
|
|
||||||
export const START_SKETCH = 'START_SKETCH';
|
export const START_SKETCH = 'START_SKETCH';
|
||||||
|
@ -27,6 +27,9 @@ export const PROJECT_SAVE_FAIL = 'PROJECT_SAVE_FAIL';
|
||||||
export const NEW_PROJECT = 'NEW_PROJECT';
|
export const NEW_PROJECT = 'NEW_PROJECT';
|
||||||
|
|
||||||
export const SET_PROJECT = 'SET_PROJECT';
|
export const SET_PROJECT = 'SET_PROJECT';
|
||||||
|
export const SET_PROJECTS = 'SET_PROJECTS';
|
||||||
|
|
||||||
|
export const SET_SELECTED_FILE = 'SET_SELECTED_FILE';
|
||||||
|
|
||||||
// eventually, handle errors more specifically and better
|
// eventually, handle errors more specifically and better
|
||||||
export const ERROR = 'ERROR';
|
export const ERROR = 'ERROR';
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import * as ActionTypes from '../../../constants';
|
import * as ActionTypes from '../../../constants';
|
||||||
|
|
||||||
export function updateFile(name, content) {
|
export function updateFileContent(name, content) {
|
||||||
return {
|
return {
|
||||||
type: ActionTypes.CHANGE_SELECTED_FILE,
|
type: ActionTypes.UPDATE_FILE_CONTENT,
|
||||||
name,
|
name,
|
||||||
content
|
content
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,3 +17,10 @@ export function stopSketch() {
|
||||||
type: ActionTypes.STOP_SKETCH
|
type: ActionTypes.STOP_SKETCH
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setSelectedFile(fileId) {
|
||||||
|
return {
|
||||||
|
type: ActionTypes.SET_SELECTED_FILE,
|
||||||
|
selectedFile: fileId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -12,7 +12,8 @@ export function getProject(id) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ActionTypes.SET_PROJECT,
|
type: ActionTypes.SET_PROJECT,
|
||||||
project: response.data,
|
project: response.data,
|
||||||
file: response.data.file
|
files: response.data.files,
|
||||||
|
selectedFile: response.data.selectedFile
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(response => dispatch({
|
.catch(response => dispatch({
|
||||||
|
@ -34,7 +35,7 @@ export function saveProject() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const formParams = Object.assign({}, state.project);
|
const formParams = Object.assign({}, state.project);
|
||||||
formParams.file = state.file;
|
formParams.files = [...state.files];
|
||||||
if (state.project.id) {
|
if (state.project.id) {
|
||||||
axios.put(`${ROOT_URL}/projects/${state.project.id}`, formParams, { withCredentials: true })
|
axios.put(`${ROOT_URL}/projects/${state.project.id}`, formParams, { withCredentials: true })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -47,6 +48,12 @@ export function saveProject() {
|
||||||
error: response.data
|
error: response.data
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
|
// this might be unnecessary, but to prevent collisions in mongodb
|
||||||
|
formParams.files.map(file => {
|
||||||
|
const newFile = Object.assign({}, file);
|
||||||
|
delete newFile.id;
|
||||||
|
return newFile;
|
||||||
|
});
|
||||||
axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true })
|
axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
browserHistory.push(`/projects/${response.data.id}`);
|
browserHistory.push(`/projects/${response.data.id}`);
|
||||||
|
@ -54,10 +61,8 @@ export function saveProject() {
|
||||||
type: ActionTypes.NEW_PROJECT,
|
type: ActionTypes.NEW_PROJECT,
|
||||||
name: response.data.name,
|
name: response.data.name,
|
||||||
id: response.data.id,
|
id: response.data.id,
|
||||||
file: {
|
selectedFile: response.data.selectedFile,
|
||||||
name: response.data.file.name,
|
files: response.data.files
|
||||||
content: response.data.file.content
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(response => dispatch({
|
.catch(response => dispatch({
|
||||||
|
@ -78,10 +83,8 @@ export function createProject() {
|
||||||
type: ActionTypes.NEW_PROJECT,
|
type: ActionTypes.NEW_PROJECT,
|
||||||
name: response.data.name,
|
name: response.data.name,
|
||||||
id: response.data.id,
|
id: response.data.id,
|
||||||
file: {
|
selectedFile: response.data.selectedFile,
|
||||||
name: response.data.file.name,
|
files: response.data.files
|
||||||
content: response.data.file.content
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(response => dispatch({
|
.catch(response => dispatch({
|
||||||
|
|
|
@ -8,13 +8,14 @@ class Editor extends React.Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this._cm = CodeMirror(this.refs.container, { // eslint-disable-line
|
this._cm = CodeMirror(this.refs.container, { // eslint-disable-line
|
||||||
theme: 'p5-widget',
|
theme: 'p5-widget',
|
||||||
value: this.props.content,
|
value: this.props.file.content,
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
styleActiveLine: true,
|
styleActiveLine: true,
|
||||||
mode: 'javascript'
|
mode: 'javascript'
|
||||||
});
|
});
|
||||||
this._cm.on('change', () => { // eslint-disable-line
|
this._cm.on('change', () => { // eslint-disable-line
|
||||||
this.props.updateFile('sketch.js', this._cm.getValue());
|
// this.props.updateFileContent('sketch.js', this._cm.getValue());
|
||||||
|
this.props.updateFileContent(this.props.file.name, this._cm.getValue());
|
||||||
});
|
});
|
||||||
this._cm.getWrapperElement().style['font-size'] = `${this.props.fontSize}px`;
|
this._cm.getWrapperElement().style['font-size'] = `${this.props.fontSize}px`;
|
||||||
this._cm.setOption('indentWithTabs', this.props.isTabIndent);
|
this._cm.setOption('indentWithTabs', this.props.isTabIndent);
|
||||||
|
@ -22,9 +23,9 @@ class Editor extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (this.props.content !== prevProps.content &&
|
if (this.props.file.content !== prevProps.file.content &&
|
||||||
this.props.content !== this._cm.getValue()) {
|
this.props.file.content !== this._cm.getValue()) {
|
||||||
this._cm.setValue(this.props.content); // eslint-disable-line no-underscore-dangle
|
this._cm.setValue(this.props.file.content); // eslint-disable-line no-underscore-dangle
|
||||||
}
|
}
|
||||||
if (this.props.fontSize !== prevProps.fontSize) {
|
if (this.props.fontSize !== prevProps.fontSize) {
|
||||||
this._cm.getWrapperElement().style['font-size'] = `${this.props.fontSize}px`;
|
this._cm.getWrapperElement().style['font-size'] = `${this.props.fontSize}px`;
|
||||||
|
@ -54,6 +55,12 @@ Editor.propTypes = {
|
||||||
fontSize: PropTypes.number.isRequired,
|
fontSize: PropTypes.number.isRequired,
|
||||||
indentationAmount: PropTypes.number.isRequired,
|
indentationAmount: PropTypes.number.isRequired,
|
||||||
isTabIndent: PropTypes.bool.isRequired
|
isTabIndent: PropTypes.bool.isRequired
|
||||||
|
file: PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
content: PropTypes.string.isRequired
|
||||||
|
}),
|
||||||
|
updateFileContent: PropTypes.func.isRequired,
|
||||||
|
fontSize: PropTypes.number.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Editor;
|
export default Editor;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
import escapeStringRegexp from 'escape-string-regexp';
|
||||||
|
import srcDoc from 'srcdoc-polyfill';
|
||||||
|
|
||||||
class PreviewFrame extends React.Component {
|
class PreviewFrame extends React.Component {
|
||||||
|
|
||||||
|
@ -11,11 +13,7 @@ class PreviewFrame extends React.Component {
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (this.props.isPlaying !== prevProps.isPlaying) {
|
if (this.props.isPlaying !== prevProps.isPlaying) {
|
||||||
if (this.props.isPlaying) {
|
this.renderSketch();
|
||||||
this.renderSketch();
|
|
||||||
} else {
|
|
||||||
this.clearPreview();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.isPlaying && this.props.content !== prevProps.content) {
|
if (this.props.isPlaying && this.props.content !== prevProps.content) {
|
||||||
|
@ -28,22 +26,48 @@ class PreviewFrame extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
clearPreview() {
|
clearPreview() {
|
||||||
const doc = ReactDOM.findDOMNode(this).contentDocument;
|
const doc = ReactDOM.findDOMNode(this);
|
||||||
doc.write('');
|
doc.srcDoc = '';
|
||||||
doc.close();
|
}
|
||||||
|
|
||||||
|
injectLocalFiles() {
|
||||||
|
let htmlFile = this.props.htmlFile.content;
|
||||||
|
|
||||||
|
this.props.jsFiles.forEach(jsFile => {
|
||||||
|
const fileName = escapeStringRegexp(jsFile.name);
|
||||||
|
const fileRegex = new RegExp(`<script.*?src=('|")((\.\/)|\/)?${fileName}('|").*?>([\s\S]*?)<\/script>`, 'gmi');
|
||||||
|
htmlFile = htmlFile.replace(fileRegex, `<script>\n${jsFile.content}\n</script>`);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.props.cssFiles.forEach(cssFile => {
|
||||||
|
const fileName = escapeStringRegexp(cssFile.name);
|
||||||
|
const fileRegex = new RegExp(`<link.*?href=('|")((\.\/)|\/)?${fileName}('|").*?>`, 'gmi');
|
||||||
|
htmlFile = htmlFile.replace(fileRegex, `<style>\n${cssFile.content}\n</style>`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// const htmlHead = htmlFile.match(/(?:<head.*?>)([\s\S]*?)(?:<\/head>)/gmi);
|
||||||
|
// const headRegex = new RegExp('head', 'i');
|
||||||
|
// let htmlHeadContents = htmlHead[0].split(headRegex)[1];
|
||||||
|
// htmlHeadContents = htmlHeadContents.slice(1, htmlHeadContents.length - 2);
|
||||||
|
// htmlHeadContents += '<link rel="stylesheet" type="text/css" href="/preview-styles.css" />\n';
|
||||||
|
// htmlFile = htmlFile.replace(/(?:<head.*?>)([\s\S]*?)(?:<\/head>)/gmi, `<head>\n${htmlHeadContents}\n</head>`);
|
||||||
|
|
||||||
|
return htmlFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSketch() {
|
renderSketch() {
|
||||||
const doc = ReactDOM.findDOMNode(this).contentDocument;
|
const doc = ReactDOM.findDOMNode(this);
|
||||||
this.clearPreview();
|
if (this.props.isPlaying) {
|
||||||
ReactDOM.render(this.props.head, doc.head);
|
// TODO add polyfill for this
|
||||||
const p5Script = doc.createElement('script');
|
// doc.srcdoc = this.injectLocalFiles();
|
||||||
p5Script.setAttribute('src', 'https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.0/p5.min.js');
|
srcDoc.set(doc, this.injectLocalFiles());
|
||||||
doc.body.appendChild(p5Script);
|
} else {
|
||||||
|
// doc.srcdoc = '';
|
||||||
const sketchScript = doc.createElement('script');
|
srcDoc.set(doc, '');
|
||||||
sketchScript.textContent = this.props.content;
|
doc.contentWindow.document.open();
|
||||||
doc.body.appendChild(sketchScript);
|
doc.contentWindow.document.write('');
|
||||||
|
doc.contentWindow.document.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFrameContents() {
|
renderFrameContents() {
|
||||||
|
@ -60,8 +84,8 @@ class PreviewFrame extends React.Component {
|
||||||
<iframe
|
<iframe
|
||||||
className="preview-frame"
|
className="preview-frame"
|
||||||
frameBorder="0"
|
frameBorder="0"
|
||||||
sandbox="allow-scripts allow-pointer-lock allow-same-origin allow-popups allow-modals allow-forms"
|
|
||||||
title="sketch output"
|
title="sketch output"
|
||||||
|
sandbox="allow-scripts allow-pointer-lock allow-same-origin allow-popups allow-modals allow-forms"
|
||||||
></iframe>
|
></iframe>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -70,7 +94,12 @@ class PreviewFrame extends React.Component {
|
||||||
PreviewFrame.propTypes = {
|
PreviewFrame.propTypes = {
|
||||||
isPlaying: PropTypes.bool.isRequired,
|
isPlaying: PropTypes.bool.isRequired,
|
||||||
head: PropTypes.object.isRequired,
|
head: PropTypes.object.isRequired,
|
||||||
content: PropTypes.string.isRequired
|
content: PropTypes.string.isRequired,
|
||||||
|
htmlFile: PropTypes.shape({
|
||||||
|
content: PropTypes.string.isRequired
|
||||||
|
}),
|
||||||
|
jsFiles: PropTypes.array.isRequired,
|
||||||
|
cssFiles: PropTypes.array.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PreviewFrame;
|
export default PreviewFrame;
|
||||||
|
|
34
client/modules/IDE/components/Sidebar.js
Normal file
34
client/modules/IDE/components/Sidebar.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import React, { PropTypes } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
function Sidebar(props) {
|
||||||
|
return (
|
||||||
|
<section className="sidebar">
|
||||||
|
<ul className="sidebar__file-list">
|
||||||
|
{props.files.map(file => {
|
||||||
|
let itemClass = classNames({
|
||||||
|
'sidebar__file-item': true,
|
||||||
|
'sidebar__file-item--selected': file.id === props.selectedFile.id
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className={itemClass}
|
||||||
|
key={file.id}
|
||||||
|
onClick={() => props.setSelectedFile(file.id)}
|
||||||
|
>{file.name}</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Sidebar.propTypes = {
|
||||||
|
files: PropTypes.array.isRequired,
|
||||||
|
selectedFile: PropTypes.shape({
|
||||||
|
id: PropTypes.string.isRequired
|
||||||
|
}),
|
||||||
|
setSelectedFile: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Sidebar;
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import Editor from '../components/Editor';
|
import Editor from '../components/Editor';
|
||||||
|
import Sidebar from '../components/Sidebar';
|
||||||
import PreviewFrame from '../components/PreviewFrame';
|
import PreviewFrame from '../components/PreviewFrame';
|
||||||
import Toolbar from '../components/Toolbar';
|
import Toolbar from '../components/Toolbar';
|
||||||
import Preferences from '../components/Preferences';
|
import Preferences from '../components/Preferences';
|
||||||
|
@ -10,6 +11,7 @@ import * as FileActions from '../actions/files';
|
||||||
import * as IDEActions from '../actions/ide';
|
import * as IDEActions from '../actions/ide';
|
||||||
import * as PreferencesActions from '../actions/preferences';
|
import * as PreferencesActions from '../actions/preferences';
|
||||||
import * as ProjectActions from '../actions/project';
|
import * as ProjectActions from '../actions/project';
|
||||||
|
import { getFile, getHTMLFile, getJSFiles, getCSSFiles } from '../reducers/files';
|
||||||
|
|
||||||
class IDEView extends React.Component {
|
class IDEView extends React.Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -52,15 +54,25 @@ class IDEView extends React.Component {
|
||||||
indentWithSpace={this.props.indentWithSpace}
|
indentWithSpace={this.props.indentWithSpace}
|
||||||
indentWithTab={this.props.indentWithTab}
|
indentWithTab={this.props.indentWithTab}
|
||||||
/>
|
/>
|
||||||
|
<Sidebar
|
||||||
|
files={this.props.files}
|
||||||
|
selectedFile={this.props.selectedFile}
|
||||||
|
setSelectedFile={this.props.setSelectedFile}
|
||||||
|
/>
|
||||||
<Editor
|
<Editor
|
||||||
content={this.props.file.content}
|
file={this.props.selectedFile}
|
||||||
updateFile={this.props.updateFile}
|
updateFileContent={this.props.updateFileContent}
|
||||||
fontSize={this.props.preferences.fontSize}
|
fontSize={this.props.preferences.fontSize}
|
||||||
indentationAmount={this.props.preferences.indentationAmount}
|
indentationAmount={this.props.preferences.indentationAmount}
|
||||||
isTabIndent={this.props.preferences.isTabIndent}
|
isTabIndent={this.props.preferences.isTabIndent}
|
||||||
|
files={this.props.files}
|
||||||
/>
|
/>
|
||||||
<PreviewFrame
|
<PreviewFrame
|
||||||
content={this.props.file.content}
|
htmlFile={this.props.htmlFile}
|
||||||
|
jsFiles={this.props.jsFiles}
|
||||||
|
cssFiles={this.props.cssFiles}
|
||||||
|
files={this.props.files}
|
||||||
|
content={this.props.selectedFile.content}
|
||||||
head={
|
head={
|
||||||
<link type="text/css" rel="stylesheet" href="/preview-styles.css" />
|
<link type="text/css" rel="stylesheet" href="/preview-styles.css" />
|
||||||
}
|
}
|
||||||
|
@ -98,6 +110,7 @@ IDEView.propTypes = {
|
||||||
closePreferences: PropTypes.func.isRequired,
|
closePreferences: PropTypes.func.isRequired,
|
||||||
increaseFont: PropTypes.func.isRequired,
|
increaseFont: PropTypes.func.isRequired,
|
||||||
decreaseFont: PropTypes.func.isRequired,
|
decreaseFont: PropTypes.func.isRequired,
|
||||||
|
<<<<<<< HEAD
|
||||||
updateFont: PropTypes.func.isRequired,
|
updateFont: PropTypes.func.isRequired,
|
||||||
increaseIndentation: PropTypes.func.isRequired,
|
increaseIndentation: PropTypes.func.isRequired,
|
||||||
decreaseIndentation: PropTypes.func.isRequired,
|
decreaseIndentation: PropTypes.func.isRequired,
|
||||||
|
@ -105,14 +118,27 @@ IDEView.propTypes = {
|
||||||
indentWithSpace: PropTypes.func.isRequired,
|
indentWithSpace: PropTypes.func.isRequired,
|
||||||
indentWithTab: PropTypes.func.isRequired,
|
indentWithTab: PropTypes.func.isRequired,
|
||||||
file: PropTypes.shape({
|
file: PropTypes.shape({
|
||||||
|
=======
|
||||||
|
files: PropTypes.array.isRequired,
|
||||||
|
updateFileContent: PropTypes.func.isRequired,
|
||||||
|
selectedFile: PropTypes.shape({
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
>>>>>>> b89a1103b9c5616820f4b6b005c118d9f1f9bf13
|
||||||
content: PropTypes.string.isRequired
|
content: PropTypes.string.isRequired
|
||||||
}).isRequired,
|
}),
|
||||||
updateFile: PropTypes.func.isRequired
|
setSelectedFile: PropTypes.func.isRequired,
|
||||||
|
htmlFile: PropTypes.object.isRequired,
|
||||||
|
jsFiles: PropTypes.array.isRequired,
|
||||||
|
cssFiles: PropTypes.array.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
file: state.file,
|
files: state.files,
|
||||||
|
selectedFile: getFile(state.files, state.ide.selectedFile),
|
||||||
|
htmlFile: getHTMLFile(state.files),
|
||||||
|
jsFiles: getJSFiles(state.files),
|
||||||
|
cssFiles: getCSSFiles(state.files),
|
||||||
ide: state.ide,
|
ide: state.ide,
|
||||||
preferences: state.preferences,
|
preferences: state.preferences,
|
||||||
user: state.user,
|
user: state.user,
|
||||||
|
|
|
@ -1,50 +1,75 @@
|
||||||
import * as ActionTypes from '../../../constants';
|
import * as ActionTypes from '../../../constants';
|
||||||
|
|
||||||
const initialState = {
|
const defaultSketch = `function setup() {
|
||||||
name: 'sketch.js',
|
|
||||||
content: `function setup() {
|
|
||||||
createCanvas(400, 400);
|
createCanvas(400, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw() {
|
function draw() {
|
||||||
background(220);
|
background(220);
|
||||||
}`
|
}`;
|
||||||
};
|
|
||||||
|
|
||||||
const file = (state = initialState, action) => {
|
const defaultHTML =
|
||||||
|
`<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.0/p5.min.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="sketch.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const defaultCSS =
|
||||||
|
`html, body {
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// if the project has never been saved,
|
||||||
|
const initialState = [
|
||||||
|
{
|
||||||
|
name: 'sketch.js',
|
||||||
|
content: defaultSketch,
|
||||||
|
id: '1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'index.html',
|
||||||
|
content: defaultHTML,
|
||||||
|
id: '2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'style.css',
|
||||||
|
content: defaultCSS,
|
||||||
|
id: '3'
|
||||||
|
}];
|
||||||
|
|
||||||
|
|
||||||
|
const files = (state = initialState, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ActionTypes.CHANGE_SELECTED_FILE:
|
case ActionTypes.UPDATE_FILE_CONTENT:
|
||||||
return {
|
return state.map(file => {
|
||||||
name: action.name,
|
if (file.name !== action.name) {
|
||||||
content: action.content
|
return file;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
return Object.assign({}, file, { content: action.content });
|
||||||
|
});
|
||||||
case ActionTypes.NEW_PROJECT:
|
case ActionTypes.NEW_PROJECT:
|
||||||
return {
|
return [...action.files];
|
||||||
name: action.file.name,
|
|
||||||
content: action.file.content
|
|
||||||
};
|
|
||||||
case ActionTypes.SET_PROJECT:
|
case ActionTypes.SET_PROJECT:
|
||||||
return {
|
return [...action.files];
|
||||||
name: action.file.name,
|
|
||||||
content: action.file.content
|
|
||||||
};
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default file;
|
export const getFile = (state, id) => state.filter(file => file.id === id)[0];
|
||||||
|
export const getHTMLFile = (state) => state.filter(file => file.name.match(/.*\.html$/))[0];
|
||||||
|
export const getJSFiles = (state) => state.filter(file => file.name.match(/.*\.js$/));
|
||||||
|
export const getCSSFiles = (state) => state.filter(file => file.name.match(/.*\.css$/));
|
||||||
|
|
||||||
// i'll add this in when there are multiple files
|
export default files;
|
||||||
// const files = (state = [], action) => {
|
|
||||||
// switch (action.type) {
|
|
||||||
// case ActionTypes.CHANGE_SELECTED_FILE:
|
|
||||||
// //find the file with the name
|
|
||||||
// //update it
|
|
||||||
// //put in into the new array of files
|
|
||||||
// default:
|
|
||||||
// return state
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export default files
|
|
||||||
|
|
|
@ -1,23 +1,22 @@
|
||||||
import * as ActionTypes from '../../../constants';
|
import * as ActionTypes from '../../../constants';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
isPlaying: false
|
isPlaying: false,
|
||||||
|
selectedFile: '1'
|
||||||
};
|
};
|
||||||
|
|
||||||
const ide = (state = initialState, action) => {
|
const ide = (state = initialState, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ActionTypes.TOGGLE_SKETCH:
|
case ActionTypes.TOGGLE_SKETCH:
|
||||||
return {
|
return Object.assign({}, state, { isPlaying: !state.isPlaying });
|
||||||
isPlaying: !state.isPlaying
|
|
||||||
};
|
|
||||||
case ActionTypes.START_SKETCH:
|
case ActionTypes.START_SKETCH:
|
||||||
return {
|
return Object.assign({}, state, { isPlaying: true });
|
||||||
isPlaying: true
|
|
||||||
};
|
|
||||||
case ActionTypes.STOP_SKETCH:
|
case ActionTypes.STOP_SKETCH:
|
||||||
return {
|
return Object.assign({}, state, { isPlaying: false });
|
||||||
isPlaying: false
|
case ActionTypes.SET_SELECTED_FILE:
|
||||||
};
|
case ActionTypes.SET_PROJECT:
|
||||||
|
case ActionTypes.NEW_PROJECT:
|
||||||
|
return Object.assign({}, state, { selectedFile: action.selectedFile });
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
20
client/modules/Sketch/actions.js
Normal file
20
client/modules/Sketch/actions.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import * as ActionTypes from '../../constants';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const ROOT_URL = location.href.indexOf('localhost') > 0 ? 'http://localhost:8000/api' : '/api';
|
||||||
|
|
||||||
|
export function getProjects() {
|
||||||
|
return (dispatch) => {
|
||||||
|
axios.get(`${ROOT_URL}/projects`, { withCredentials: true })
|
||||||
|
.then(response => {
|
||||||
|
dispatch({
|
||||||
|
type: ActionTypes.SET_PROJECTS,
|
||||||
|
projects: response.data
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(response => dispatch({
|
||||||
|
type: ActionTypes.ERROR,
|
||||||
|
error: response.data
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
}
|
63
client/modules/Sketch/pages/SketchListView.js
Normal file
63
client/modules/Sketch/pages/SketchListView.js
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import React, { PropTypes } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Link } from 'react-router';
|
||||||
|
import Nav from '../../../components/Nav';
|
||||||
|
import * as SketchActions from '../actions';
|
||||||
|
import * as ProjectActions from '../../IDE/actions/project';
|
||||||
|
|
||||||
|
class SketchListView extends React.Component {
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.getProjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="sketch-list">
|
||||||
|
<Nav
|
||||||
|
user={this.props.user}
|
||||||
|
createProject={this.props.createProject}
|
||||||
|
saveProject={this.props.saveProject}
|
||||||
|
/>
|
||||||
|
<table className="sketches-table">
|
||||||
|
<thead>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Created</th>
|
||||||
|
<th>Last Updated</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{this.props.sketches.map(sketch =>
|
||||||
|
<tr className="sketches-table__row">
|
||||||
|
<td><Link to={`/projects/${sketch._id}`}>{sketch.name}</Link></td>
|
||||||
|
<td>{moment(sketch.createdAt).format('MMM D, YYYY')}</td>
|
||||||
|
<td>{moment(sketch.updatedAt).format('MMM D, YYYY')}</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SketchListView.propTypes = {
|
||||||
|
user: PropTypes.object.isRequired,
|
||||||
|
createProject: PropTypes.func.isRequired,
|
||||||
|
saveProject: PropTypes.func.isRequired,
|
||||||
|
getProjects: PropTypes.func.isRequired,
|
||||||
|
sketches: PropTypes.array.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
user: state.user,
|
||||||
|
sketches: state.sketches
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch) {
|
||||||
|
return bindActionCreators(Object.assign({}, SketchActions, ProjectActions), dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(SketchListView);
|
12
client/modules/Sketch/reducers.js
Normal file
12
client/modules/Sketch/reducers.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import * as ActionTypes from '../../constants';
|
||||||
|
|
||||||
|
const sketches = (state = [], action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case ActionTypes.SET_PROJECTS:
|
||||||
|
return action.projects;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default sketches;
|
|
@ -1,18 +1,20 @@
|
||||||
import { combineReducers } from 'redux';
|
import { combineReducers } from 'redux';
|
||||||
import file 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';
|
||||||
import project from './modules/IDE/reducers/project';
|
import project from './modules/IDE/reducers/project';
|
||||||
import user from './modules/User/reducers';
|
import user from './modules/User/reducers';
|
||||||
|
import sketches from './modules/Sketch/reducers';
|
||||||
import { reducer as form } from 'redux-form';
|
import { reducer as form } from 'redux-form';
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
form,
|
form,
|
||||||
ide,
|
ide,
|
||||||
file,
|
files,
|
||||||
preferences,
|
preferences,
|
||||||
user,
|
user,
|
||||||
project
|
project,
|
||||||
|
sketches
|
||||||
});
|
});
|
||||||
|
|
||||||
export default rootReducer;
|
export default rootReducer;
|
||||||
|
|
|
@ -4,6 +4,7 @@ import App from './modules/App/App';
|
||||||
import IDEView from './modules/IDE/pages/IDEView';
|
import IDEView from './modules/IDE/pages/IDEView';
|
||||||
import LoginView from './modules/User/pages/LoginView';
|
import LoginView from './modules/User/pages/LoginView';
|
||||||
import SignupView from './modules/User/pages/SignupView';
|
import SignupView from './modules/User/pages/SignupView';
|
||||||
|
import SketchListView from './modules/Sketch/pages/SketchListView';
|
||||||
import { getUser } from './modules/User/actions';
|
import { getUser } from './modules/User/actions';
|
||||||
|
|
||||||
const checkAuth = (store) => {
|
const checkAuth = (store) => {
|
||||||
|
@ -17,6 +18,7 @@ const routes = (store) =>
|
||||||
<Route path="/login" component={LoginView} />
|
<Route path="/login" component={LoginView} />
|
||||||
<Route path="/signup" component={SignupView} />
|
<Route path="/signup" component={SignupView} />
|
||||||
<Route path="/projects/:project_id" component={IDEView} />
|
<Route path="/projects/:project_id" component={IDEView} />
|
||||||
|
<Route path="/sketches" component={SketchListView} />
|
||||||
</Route>
|
</Route>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
%fake-link {
|
%fake-link {
|
||||||
color: $light-secondary-text-color;
|
color: $light-inactive-text-color;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $light-primary-text-color;
|
color: $light-primary-text-color;
|
||||||
|
|
|
@ -36,6 +36,6 @@ $dark-button-background-active-color: #f10046;
|
||||||
$dark-button-hover-color: $white;
|
$dark-button-hover-color: $white;
|
||||||
$dark-button-active-color: $white;
|
$dark-button-active-color: $white;
|
||||||
|
|
||||||
$editor-border-color: #f4f4f4;
|
$ide-border-color: #f4f4f4;
|
||||||
$editor-selected-line-color: #f3f3f3;
|
$editor-selected-line-color: #f3f3f3;
|
||||||
$input-border-color: #979797;
|
$input-border-color: #979797;
|
||||||
|
|
|
@ -18,7 +18,7 @@ body, input, button {
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: $light-secondary-text-color;
|
color: $light-inactive-text-color;
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: $light-primary-text-color;
|
color: $light-primary-text-color;
|
||||||
|
@ -46,11 +46,12 @@ h2 {
|
||||||
h3 {
|
h3 {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
h6 {
|
h6 {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
thead {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
|
@ -14,4 +14,8 @@ ul, p {
|
||||||
|
|
||||||
h2, h3 {
|
h2, h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
.CodeMirror {
|
.CodeMirror {
|
||||||
font-family: Inconsolata, monospace;
|
font-family: Inconsolata, monospace;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border: 1px solid $editor-border-color;
|
border: 1px solid $ide-border-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror-linenumbers {
|
.CodeMirror-linenumbers {
|
||||||
|
|
12
client/styles/components/_sidebar.scss
Normal file
12
client/styles/components/_sidebar.scss
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
.sidebar__file-list {
|
||||||
|
border-top: 1px solid $ide-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__file-item {
|
||||||
|
padding: #{8 / $base-font-size}rem #{20 / $base-font-size}rem;
|
||||||
|
color: $light-inactive-text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__file-item--selected {
|
||||||
|
background-color: $ide-border-color;
|
||||||
|
}
|
9
client/styles/components/_sketch-list.scss
Normal file
9
client/styles/components/_sketch-list.scss
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
.sketches-table {
|
||||||
|
width: 100%;
|
||||||
|
padding: #{10 / $base-font-size}rem 0;
|
||||||
|
padding-left: #{170 / $base-font-size}rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sketches-table__row {
|
||||||
|
margin: #{10 / $base-font-size}rem;
|
||||||
|
}
|
|
@ -45,12 +45,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar__project-name {
|
.toolbar__project-name {
|
||||||
color: $light-secondary-text-color;
|
color: $light-inactive-text-color;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $light-primary-text-color;
|
color: $light-primary-text-color;
|
||||||
}
|
}
|
||||||
&:focus {
|
&:focus {
|
||||||
color: $light-secondary-text-color;
|
color: $light-inactive-text-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,18 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor-holder {
|
.editor-holder {
|
||||||
width: 50%;
|
flex-grow: 1;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-frame {
|
.preview-frame {
|
||||||
width: 50%;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar {
|
.toolbar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
width: #{140 / $base-font-size}rem;
|
||||||
|
}
|
||||||
|
|
6
client/styles/layout/_sketch-list.scss
Normal file
6
client/styles/layout/_sketch-list.scss
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
.sketch-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
height: 100%;
|
||||||
|
flex-flow: column;
|
||||||
|
}
|
|
@ -13,5 +13,8 @@
|
||||||
@import 'components/preferences';
|
@import 'components/preferences';
|
||||||
@import 'components/signup';
|
@import 'components/signup';
|
||||||
@import 'components/login';
|
@import 'components/login';
|
||||||
|
@import 'components/sketch-list';
|
||||||
|
@import 'components/sidebar';
|
||||||
|
|
||||||
@import 'layout/ide';
|
@import 'layout/ide';
|
||||||
|
@import 'layout/sketch-list';
|
||||||
|
|
|
@ -60,14 +60,17 @@
|
||||||
"babel-core": "^6.8.0",
|
"babel-core": "^6.8.0",
|
||||||
"bcrypt-nodejs": "0.0.3",
|
"bcrypt-nodejs": "0.0.3",
|
||||||
"body-parser": "^1.15.1",
|
"body-parser": "^1.15.1",
|
||||||
|
"bson-objectid": "^1.1.4",
|
||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.5",
|
||||||
"codemirror": "^5.14.2",
|
"codemirror": "^5.14.2",
|
||||||
"connect-mongo": "^1.2.0",
|
"connect-mongo": "^1.2.0",
|
||||||
"cookie-parser": "^1.4.1",
|
"cookie-parser": "^1.4.1",
|
||||||
"dotenv": "^2.0.0",
|
"dotenv": "^2.0.0",
|
||||||
|
"escape-string-regexp": "^1.0.5",
|
||||||
"eslint-loader": "^1.3.0",
|
"eslint-loader": "^1.3.0",
|
||||||
"express": "^4.13.4",
|
"express": "^4.13.4",
|
||||||
"express-session": "^1.13.0",
|
"express-session": "^1.13.0",
|
||||||
|
"moment": "^2.14.1",
|
||||||
"mongoose": "^4.4.16",
|
"mongoose": "^4.4.16",
|
||||||
"passport": "^0.3.2",
|
"passport": "^0.3.2",
|
||||||
"passport-github": "^1.1.0",
|
"passport-github": "^1.1.0",
|
||||||
|
@ -80,6 +83,7 @@
|
||||||
"redux": "^3.5.2",
|
"redux": "^3.5.2",
|
||||||
"redux-form": "^5.2.5",
|
"redux-form": "^5.2.5",
|
||||||
"redux-thunk": "^2.1.0",
|
"redux-thunk": "^2.1.0",
|
||||||
"shortid": "^2.2.6"
|
"shortid": "^2.2.6",
|
||||||
|
"srcdoc-polyfill": "^0.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,22 +2,14 @@ import Project from '../models/project';
|
||||||
|
|
||||||
export function createProject(req, res) {
|
export function createProject(req, res) {
|
||||||
const projectValues = {
|
const projectValues = {
|
||||||
user: req.user ? req.user._id : undefined, // eslint-disable-line no-underscore-dangle
|
user: req.user ? req.user._id : undefined // eslint-disable-line no-underscore-dangle
|
||||||
file: {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.assign(projectValues, req.body);
|
Object.assign(projectValues, req.body);
|
||||||
|
|
||||||
Project.create(projectValues, (err, newProject) => {
|
Project.create(projectValues, (err, newProject) => {
|
||||||
if (err) { return res.json({ success: false }); }
|
if (err) { return res.json({ success: false }); }
|
||||||
return res.json({
|
return res.json(newProject);
|
||||||
id: newProject._id, // eslint-disable-line no-underscore-dangle
|
|
||||||
name: newProject.name,
|
|
||||||
file: {
|
|
||||||
name: newProject.file.name,
|
|
||||||
content: newProject.file.content
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,14 +19,7 @@ export function updateProject(req, res) {
|
||||||
$set: req.body
|
$set: req.body
|
||||||
}, (err, updatedProject) => {
|
}, (err, updatedProject) => {
|
||||||
if (err) { return res.json({ success: false }); }
|
if (err) { return res.json({ success: false }); }
|
||||||
return res.json({
|
return res.json(updatedProject);
|
||||||
id: updatedProject._id, // eslint-disable-line no-underscore-dangle
|
|
||||||
name: updatedProject.name,
|
|
||||||
file: {
|
|
||||||
name: updatedProject.file.name,
|
|
||||||
content: updatedProject.file.content
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,13 +29,21 @@ export function getProject(req, res) {
|
||||||
return res.status(404).send({ message: 'Project with that id does not exist' });
|
return res.status(404).send({ message: 'Project with that id does not exist' });
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json({
|
return res.json(project);
|
||||||
id: project._id, // eslint-disable-line no-underscore-dangle
|
|
||||||
name: project.name,
|
|
||||||
file: {
|
|
||||||
name: project.file.name,
|
|
||||||
content: project.file.content
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getProjects(req, res) {
|
||||||
|
if (req.user) {
|
||||||
|
Project.find({user: req.user._id}) // eslint-disable-line no-underscore-dangle
|
||||||
|
.sort('-createdAt')
|
||||||
|
.select('name file _id createdAt updatedAt')
|
||||||
|
.exec((err, projects) => {
|
||||||
|
res.json(projects);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// could just move this to client side
|
||||||
|
return res.json([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,24 +1,73 @@
|
||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
const Schema = mongoose.Schema;
|
const Schema = mongoose.Schema;
|
||||||
|
const ObjectIdSchema = Schema.ObjectId;
|
||||||
|
const ObjectId = mongoose.Types.ObjectId;
|
||||||
import shortid from 'shortid';
|
import shortid from 'shortid';
|
||||||
|
|
||||||
const defaultSketch = `setup() {
|
const defaultSketch = `function setup() {
|
||||||
createCanvas(400, 400);
|
createCanvas(400, 400);
|
||||||
}
|
}
|
||||||
draw() {
|
function draw() {
|
||||||
background(220);
|
background(220);
|
||||||
}`
|
}`
|
||||||
|
|
||||||
|
const defaultHTML =
|
||||||
|
`<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.0/p5.min.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="sketch.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
const defaultCSS =
|
||||||
|
`html, body {
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const fileSchema = new Schema({
|
const fileSchema = new Schema({
|
||||||
name: { type: String, default: 'sketch.js' },
|
name: { type: String, default: 'sketch.js' },
|
||||||
content: { type: String, default: defaultSketch }
|
content: { type: String, default: defaultSketch }
|
||||||
}, { timestamps: true });
|
}, { timestamps: true, _id: true });
|
||||||
|
|
||||||
|
fileSchema.virtual('id').get(function(){
|
||||||
|
return this._id.toHexString();
|
||||||
|
});
|
||||||
|
|
||||||
|
fileSchema.set('toJSON', {
|
||||||
|
virtuals: true
|
||||||
|
});
|
||||||
|
|
||||||
const projectSchema = new Schema({
|
const projectSchema = new Schema({
|
||||||
name: { type: String, default: "Hello p5.js, it's the server" },
|
name: { type: String, default: "Hello p5.js, it's the server" },
|
||||||
user: { type: Schema.Types.ObjectId, ref: 'User' },
|
user: { type: Schema.Types.ObjectId, ref: 'User' },
|
||||||
file: { type: fileSchema },
|
files: { type: [ fileSchema ], default: [{ name: 'sketch.js', content: defaultSketch, _id: new ObjectId() },
|
||||||
_id: { type: String, default: shortid.generate }
|
{ name: 'index.html', content: defaultHTML, _id: new ObjectId() },
|
||||||
|
{ name: 'style.css', content: defaultCSS, _id: new ObjectId() }]},
|
||||||
|
_id: { type: String, default: shortid.generate },
|
||||||
|
selectedFile: Schema.Types.ObjectId
|
||||||
}, { timestamps: true });
|
}, { timestamps: true });
|
||||||
|
|
||||||
|
projectSchema.virtual('id').get(function(){
|
||||||
|
return this._id;
|
||||||
|
});
|
||||||
|
|
||||||
|
projectSchema.set('toJSON', {
|
||||||
|
virtuals: true
|
||||||
|
});
|
||||||
|
|
||||||
|
projectSchema.pre('save', function createSelectedFile(next) {
|
||||||
|
const project = this;
|
||||||
|
if (!project.selectedFile) {
|
||||||
|
project.selectedFile = project.files[0]._id; // eslint-disable-line no-underscore-dangle
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default mongoose.model('Project', projectSchema);
|
export default mongoose.model('Project', projectSchema);
|
||||||
|
|
|
@ -9,4 +9,6 @@ router.route('/projects/:project_id').put(ProjectController.updateProject);
|
||||||
|
|
||||||
router.route('/projects/:project_id').get(ProjectController.getProject);
|
router.route('/projects/:project_id').get(ProjectController.getProject);
|
||||||
|
|
||||||
|
router.route('/projects').get(ProjectController.getProjects);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -21,4 +21,8 @@ router.route('/login').get((req, res) => {
|
||||||
res.sendFile(path.resolve(`${__dirname}/../../index.html`));
|
res.sendFile(path.resolve(`${__dirname}/../../index.html`));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.route('/sketches').get((req, res) => {
|
||||||
|
res.sendFile(path.resolve(`${__dirname}/../../index.html`));
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
Loading…
Reference in a new issue