Merge branch 'development' of https://github.com/catarak/p5.js-web-editor into console
This commit is contained in:
commit
fae5ea086e
22 changed files with 407 additions and 94 deletions
|
@ -8,6 +8,12 @@ export const OPEN_PREFERENCES = 'OPEN_PREFERENCES';
|
|||
export const CLOSE_PREFERENCES = 'CLOSE_PREFERENCES';
|
||||
export const INCREASE_FONTSIZE = 'INCREASE_FONTSIZE';
|
||||
export const DECREASE_FONTSIZE = 'DECREASE_FONTSIZE';
|
||||
export const UPDATE_FONTSIZE = 'UPDATE_FONTSIZE';
|
||||
export const INCREASE_INDENTATION = 'INCREASE_INDENTATION';
|
||||
export const DECREASE_INDENTATION = 'DECREASE_INDENTATION';
|
||||
export const UPDATE_INDENTATION = 'UPDATE_INDENTATION';
|
||||
export const INDENT_WITH_SPACE = 'INDENT_WITH_SPACE';
|
||||
export const INDENT_WITH_TAB = 'INDENT_WITH_TAB';
|
||||
|
||||
export const AUTH_USER = 'AUTH_USER';
|
||||
export const UNAUTH_USER = 'UNAUTH_USER';
|
||||
|
@ -23,5 +29,7 @@ export const NEW_PROJECT = 'NEW_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
|
||||
export const ERROR = 'ERROR';
|
||||
|
|
|
@ -17,3 +17,10 @@ export function stopSketch() {
|
|||
type: ActionTypes.STOP_SKETCH
|
||||
};
|
||||
}
|
||||
|
||||
export function setSelectedFile(fileId) {
|
||||
return {
|
||||
type: ActionTypes.SET_SELECTED_FILE,
|
||||
selectedFile: fileId
|
||||
};
|
||||
}
|
||||
|
|
|
@ -23,3 +23,43 @@ export function decreaseFont() {
|
|||
type: ActionTypes.DECREASE_FONTSIZE
|
||||
};
|
||||
}
|
||||
|
||||
export function updateFont(event) {
|
||||
const value = event.target.value;
|
||||
return {
|
||||
type: ActionTypes.UPDATE_FONTSIZE,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
export function increaseIndentation() {
|
||||
return {
|
||||
type: ActionTypes.INCREASE_INDENTATION
|
||||
};
|
||||
}
|
||||
|
||||
export function decreaseIndentation() {
|
||||
return {
|
||||
type: ActionTypes.DECREASE_INDENTATION
|
||||
};
|
||||
}
|
||||
|
||||
export function updateIndentation() {
|
||||
const value = event.target.value;
|
||||
return {
|
||||
type: ActionTypes.UPDATE_INDENTATION,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
export function indentWithTab() {
|
||||
return {
|
||||
type: ActionTypes.INDENT_WITH_TAB
|
||||
};
|
||||
}
|
||||
|
||||
export function indentWithSpace() {
|
||||
return {
|
||||
type: ActionTypes.INDENT_WITH_SPACE
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,7 +12,8 @@ export function getProject(id) {
|
|||
dispatch({
|
||||
type: ActionTypes.SET_PROJECT,
|
||||
project: response.data,
|
||||
files: response.data.files
|
||||
files: response.data.files,
|
||||
selectedFile: response.data.selectedFile
|
||||
});
|
||||
})
|
||||
.catch(response => dispatch({
|
||||
|
@ -34,7 +35,7 @@ export function saveProject() {
|
|||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const formParams = Object.assign({}, state.project);
|
||||
formParams.files = state.files;
|
||||
formParams.files = [...state.files];
|
||||
if (state.project.id) {
|
||||
axios.put(`${ROOT_URL}/projects/${state.project.id}`, formParams, { withCredentials: true })
|
||||
.then(() => {
|
||||
|
@ -47,6 +48,12 @@ export function saveProject() {
|
|||
error: response.data
|
||||
}));
|
||||
} 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 })
|
||||
.then(response => {
|
||||
browserHistory.push(`/projects/${response.data.id}`);
|
||||
|
@ -54,6 +61,7 @@ export function saveProject() {
|
|||
type: ActionTypes.NEW_PROJECT,
|
||||
name: response.data.name,
|
||||
id: response.data.id,
|
||||
selectedFile: response.data.selectedFile,
|
||||
files: response.data.files
|
||||
});
|
||||
})
|
||||
|
@ -75,6 +83,7 @@ export function createProject() {
|
|||
type: ActionTypes.NEW_PROJECT,
|
||||
name: response.data.name,
|
||||
id: response.data.id,
|
||||
selectedFile: response.data.selectedFile,
|
||||
files: response.data.files
|
||||
});
|
||||
})
|
||||
|
|
|
@ -8,25 +8,34 @@ class Editor extends React.Component {
|
|||
componentDidMount() {
|
||||
this._cm = CodeMirror(this.refs.container, { // eslint-disable-line
|
||||
theme: 'p5-widget',
|
||||
value: this.props.content,
|
||||
value: this.props.file.content,
|
||||
lineNumbers: true,
|
||||
styleActiveLine: true,
|
||||
mode: 'javascript'
|
||||
});
|
||||
this._cm.on('change', () => { // eslint-disable-line
|
||||
this.props.updateFileContent('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.setOption('indentWithTabs', this.props.isTabIndent);
|
||||
this._cm.setOption('tabSize', this.props.indentationAmount);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.content !== prevProps.content &&
|
||||
this.props.content !== this._cm.getValue()) {
|
||||
this._cm.setValue(this.props.content); // eslint-disable-line no-underscore-dangle
|
||||
if (this.props.file.content !== prevProps.file.content &&
|
||||
this.props.file.content !== this._cm.getValue()) {
|
||||
this._cm.setValue(this.props.file.content); // eslint-disable-line no-underscore-dangle
|
||||
}
|
||||
if (this.props.fontSize !== prevProps.fontSize) {
|
||||
this._cm.getWrapperElement().style['font-size'] = `${this.props.fontSize}px`;
|
||||
}
|
||||
if (this.props.indentationAmount !== prevProps.indentationAmount) {
|
||||
this._cm.setOption('tabSize', this.props.indentationAmount);
|
||||
}
|
||||
if (this.props.isTabIndent !== prevProps.isTabIndent) {
|
||||
this._cm.setOption('indentWithTabs', this.props.isTabIndent);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -41,9 +50,14 @@ class Editor extends React.Component {
|
|||
}
|
||||
|
||||
Editor.propTypes = {
|
||||
content: PropTypes.string.isRequired,
|
||||
indentationAmount: PropTypes.number.isRequired,
|
||||
isTabIndent: PropTypes.bool.isRequired,
|
||||
updateFileContent: PropTypes.func.isRequired,
|
||||
fontSize: PropTypes.number.isRequired
|
||||
fontSize: PropTypes.number.isRequired,
|
||||
file: PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
content: PropTypes.string.isRequired
|
||||
})
|
||||
};
|
||||
|
||||
export default Editor;
|
||||
|
|
|
@ -11,6 +11,14 @@ function Preferences(props) {
|
|||
preferences: true,
|
||||
'preferences--selected': props.isVisible
|
||||
});
|
||||
let preferencesTabOptionClass = classNames({
|
||||
preference__option: true,
|
||||
'preference__option--selected': props.isTabIndent
|
||||
});
|
||||
let preferencesSpaceOptionClass = classNames({
|
||||
preference__option: true,
|
||||
'preference__option--selected': !props.isTabIndent
|
||||
});
|
||||
return (
|
||||
<div className={preferencesContainerClass} tabIndex="0">
|
||||
<div className="preferences__heading">
|
||||
|
@ -19,16 +27,37 @@ function Preferences(props) {
|
|||
<Isvg src={exitUrl} alt="Exit Preferences" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="preference">
|
||||
<h3 className="preference__title">Text Size</h3>
|
||||
<h4 className="preference__title">Text Size</h4>
|
||||
<button className="preference__plus-button" onClick={props.decreaseFont}>
|
||||
<Isvg src={minusUrl} alt="Decrease Font Size" />
|
||||
<h6 className="preference__label">Decrease</h6>
|
||||
</button>
|
||||
<p className="preference__value">{props.fontSize}</p>
|
||||
|
||||
<input className="preference__value" value={props.fontSize} onChange={props.updateFont}></input>
|
||||
<button className="preference__minus-button" onClick={props.increaseFont}>
|
||||
<Isvg src={plusUrl} alt="Increase Font Size" />
|
||||
<h6 className="preference__label">Increase</h6>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="preference">
|
||||
<h4 className="preference__title">Indentation Amount</h4>
|
||||
<button className="preference__plus-button" onClick={props.decreaseIndentation}>
|
||||
<Isvg src={minusUrl} alt="DecreaseIndentation Amount" />
|
||||
<h6 className="preference__label">Decrease</h6>
|
||||
</button>
|
||||
<input className="preference__value" value={props.indentationAmount} onChange={props.updateIndentation}></input>
|
||||
<button className="preference__minus-button" onClick={props.increaseIndentation}>
|
||||
<Isvg src={plusUrl} alt="IncreaseIndentation Amount" />
|
||||
<h6 className="preference__label">Increase</h6>
|
||||
</button>
|
||||
<div className="preference__vertical-list">
|
||||
<button className={preferencesSpaceOptionClass} onClick={props.indentWithSpace}>Spaces</button>
|
||||
<button className={preferencesTabOptionClass} onClick={props.indentWithTab}>Tabs</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -37,8 +66,16 @@ Preferences.propTypes = {
|
|||
isVisible: PropTypes.bool.isRequired,
|
||||
closePreferences: PropTypes.func.isRequired,
|
||||
decreaseFont: PropTypes.func.isRequired,
|
||||
updateFont: PropTypes.func.isRequired,
|
||||
fontSize: PropTypes.number.isRequired,
|
||||
increaseFont: PropTypes.func.isRequired
|
||||
increaseFont: PropTypes.func.isRequired,
|
||||
indentationAmount: PropTypes.number.isRequired,
|
||||
decreaseIndentation: PropTypes.func.isRequired,
|
||||
increaseIndentation: PropTypes.func.isRequired,
|
||||
updateIndentation: PropTypes.func.isRequired,
|
||||
indentWithSpace: PropTypes.func.isRequired,
|
||||
indentWithTab: PropTypes.func.isRequired,
|
||||
isTabIndent: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default Preferences;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import escapeStringRegexp from 'escape-string-regexp';
|
||||
import srcDoc from 'srcdoc-polyfill';
|
||||
|
||||
class PreviewFrame extends React.Component {
|
||||
|
||||
|
@ -13,11 +15,7 @@ class PreviewFrame extends React.Component {
|
|||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.isPlaying !== prevProps.isPlaying) {
|
||||
if (this.props.isPlaying) {
|
||||
this.renderSketch();
|
||||
} else {
|
||||
this.clearPreview();
|
||||
}
|
||||
this.renderSketch();
|
||||
}
|
||||
|
||||
if (this.props.isPlaying && this.props.content !== prevProps.content) {
|
||||
|
@ -30,9 +28,33 @@ class PreviewFrame extends React.Component {
|
|||
}
|
||||
|
||||
clearPreview() {
|
||||
const doc = ReactDOM.findDOMNode(this).contentDocument;
|
||||
doc.write('');
|
||||
doc.close();
|
||||
const doc = ReactDOM.findDOMNode(this);
|
||||
doc.srcDoc = '';
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
hijackConsole() {
|
||||
|
@ -55,16 +77,18 @@ class PreviewFrame extends React.Component {
|
|||
}
|
||||
|
||||
renderSketch() {
|
||||
const doc = ReactDOM.findDOMNode(this).contentDocument;
|
||||
this.clearPreview();
|
||||
ReactDOM.render(this.props.head, doc.head);
|
||||
const p5Script = doc.createElement('script');
|
||||
p5Script.setAttribute('src', 'https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.0/p5.min.js');
|
||||
doc.body.appendChild(p5Script);
|
||||
|
||||
const sketchScript = doc.createElement('script');
|
||||
sketchScript.textContent = this.props.content;
|
||||
doc.body.appendChild(sketchScript);
|
||||
const doc = ReactDOM.findDOMNode(this);
|
||||
if (this.props.isPlaying) {
|
||||
// TODO add polyfill for this
|
||||
// doc.srcdoc = this.injectLocalFiles();
|
||||
srcDoc.set(doc, this.injectLocalFiles());
|
||||
} else {
|
||||
// doc.srcdoc = '';
|
||||
srcDoc.set(doc, '');
|
||||
doc.contentWindow.document.open();
|
||||
doc.contentWindow.document.write('');
|
||||
doc.contentWindow.document.close();
|
||||
}
|
||||
}
|
||||
|
||||
renderFrameContents() {
|
||||
|
@ -81,8 +105,8 @@ class PreviewFrame extends React.Component {
|
|||
<iframe
|
||||
className="preview-frame"
|
||||
frameBorder="0"
|
||||
sandbox="allow-scripts allow-pointer-lock allow-same-origin allow-popups allow-modals allow-forms"
|
||||
title="sketch output"
|
||||
sandbox="allow-scripts allow-pointer-lock allow-same-origin allow-popups allow-modals allow-forms"
|
||||
></iframe>
|
||||
);
|
||||
}
|
||||
|
@ -91,7 +115,12 @@ class PreviewFrame extends React.Component {
|
|||
PreviewFrame.propTypes = {
|
||||
isPlaying: PropTypes.bool.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;
|
||||
|
|
|
@ -1,22 +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 =>
|
||||
<li
|
||||
className="sidebar__file-item"
|
||||
key={file.id}
|
||||
>{file.name}</li>
|
||||
)}
|
||||
{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
|
||||
files: PropTypes.array.isRequired,
|
||||
selectedFile: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired
|
||||
}),
|
||||
setSelectedFile: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
|
|
|
@ -11,6 +11,7 @@ import * as FileActions from '../actions/files';
|
|||
import * as IDEActions from '../actions/ide';
|
||||
import * as PreferencesActions from '../actions/preferences';
|
||||
import * as ProjectActions from '../actions/project';
|
||||
import { getFile, getHTMLFile, getJSFiles, getCSSFiles } from '../reducers/files';
|
||||
|
||||
class IDEView extends React.Component {
|
||||
componentDidMount() {
|
||||
|
@ -43,16 +44,35 @@ class IDEView extends React.Component {
|
|||
closePreferences={this.props.closePreferences}
|
||||
increaseFont={this.props.increaseFont}
|
||||
decreaseFont={this.props.decreaseFont}
|
||||
updateFont={this.props.updateFont}
|
||||
fontSize={this.props.preferences.fontSize}
|
||||
increaseIndentation={this.props.increaseIndentation}
|
||||
decreaseIndentation={this.props.decreaseIndentation}
|
||||
updateIndentation={this.props.updateIndentation}
|
||||
indentationAmount={this.props.preferences.indentationAmount}
|
||||
isTabIndent={this.props.preferences.isTabIndent}
|
||||
indentWithSpace={this.props.indentWithSpace}
|
||||
indentWithTab={this.props.indentWithTab}
|
||||
/>
|
||||
<Sidebar
|
||||
files={this.props.files}
|
||||
selectedFile={this.props.selectedFile}
|
||||
setSelectedFile={this.props.setSelectedFile}
|
||||
/>
|
||||
<Sidebar files={this.props.files} />
|
||||
<Editor
|
||||
content={this.props.files[0].content}
|
||||
file={this.props.selectedFile}
|
||||
updateFileContent={this.props.updateFileContent}
|
||||
fontSize={this.props.preferences.fontSize}
|
||||
indentationAmount={this.props.preferences.indentationAmount}
|
||||
isTabIndent={this.props.preferences.isTabIndent}
|
||||
files={this.props.files}
|
||||
/>
|
||||
<PreviewFrame
|
||||
content={this.props.files[0].content}
|
||||
htmlFile={this.props.htmlFile}
|
||||
jsFiles={this.props.jsFiles}
|
||||
cssFiles={this.props.cssFiles}
|
||||
files={this.props.files}
|
||||
content={this.props.selectedFile.content}
|
||||
head={
|
||||
<link type="text/css" rel="stylesheet" href="/preview-styles.css" />
|
||||
}
|
||||
|
@ -83,18 +103,38 @@ IDEView.propTypes = {
|
|||
openPreferences: PropTypes.func.isRequired,
|
||||
preferences: PropTypes.shape({
|
||||
isVisible: PropTypes.bool.isRequired,
|
||||
fontSize: PropTypes.number.isRequired
|
||||
fontSize: PropTypes.number.isRequired,
|
||||
indentationAmount: PropTypes.number.isRequired,
|
||||
isTabIndent: PropTypes.bool.isRequired
|
||||
}).isRequired,
|
||||
closePreferences: PropTypes.func.isRequired,
|
||||
increaseFont: PropTypes.func.isRequired,
|
||||
decreaseFont: PropTypes.func.isRequired,
|
||||
updateFont: PropTypes.func.isRequired,
|
||||
increaseIndentation: PropTypes.func.isRequired,
|
||||
decreaseIndentation: PropTypes.func.isRequired,
|
||||
updateIndentation: PropTypes.func.isRequired,
|
||||
indentWithSpace: PropTypes.func.isRequired,
|
||||
indentWithTab: PropTypes.func.isRequired,
|
||||
files: PropTypes.array.isRequired,
|
||||
updateFileContent: PropTypes.func.isRequired
|
||||
updateFileContent: PropTypes.func.isRequired,
|
||||
selectedFile: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
content: PropTypes.string.isRequired
|
||||
}),
|
||||
setSelectedFile: PropTypes.func.isRequired,
|
||||
htmlFile: PropTypes.object.isRequired,
|
||||
jsFiles: PropTypes.array.isRequired,
|
||||
cssFiles: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
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,
|
||||
preferences: state.preferences,
|
||||
user: state.user,
|
||||
|
|
|
@ -9,10 +9,13 @@ function draw() {
|
|||
}`;
|
||||
|
||||
const defaultHTML =
|
||||
`
|
||||
<!DOCTYPE html>
|
||||
`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.2/p5.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.2/addons/p5.dom.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.2/addons/p5.sound.min.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<script src="sketch.js"></script>
|
||||
|
@ -20,14 +23,30 @@ const defaultHTML =
|
|||
</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
|
||||
content: defaultSketch,
|
||||
id: '1'
|
||||
},
|
||||
{
|
||||
name: 'index.html',
|
||||
content: defaultHTML
|
||||
content: defaultHTML,
|
||||
id: '2'
|
||||
},
|
||||
{
|
||||
name: 'style.css',
|
||||
content: defaultCSS,
|
||||
id: '3'
|
||||
}];
|
||||
|
||||
|
||||
|
@ -50,4 +69,9 @@ const files = (state = initialState, action) => {
|
|||
}
|
||||
};
|
||||
|
||||
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$/));
|
||||
|
||||
export default files;
|
||||
|
|
|
@ -1,23 +1,22 @@
|
|||
import * as ActionTypes from '../../../constants';
|
||||
|
||||
const initialState = {
|
||||
isPlaying: false
|
||||
isPlaying: false,
|
||||
selectedFile: '1'
|
||||
};
|
||||
|
||||
const ide = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.TOGGLE_SKETCH:
|
||||
return {
|
||||
isPlaying: !state.isPlaying
|
||||
};
|
||||
return Object.assign({}, state, { isPlaying: !state.isPlaying });
|
||||
case ActionTypes.START_SKETCH:
|
||||
return {
|
||||
isPlaying: true
|
||||
};
|
||||
return Object.assign({}, state, { isPlaying: true });
|
||||
case ActionTypes.STOP_SKETCH:
|
||||
return {
|
||||
isPlaying: false
|
||||
};
|
||||
return Object.assign({}, state, { isPlaying: false });
|
||||
case ActionTypes.SET_SELECTED_FILE:
|
||||
case ActionTypes.SET_PROJECT:
|
||||
case ActionTypes.NEW_PROJECT:
|
||||
return Object.assign({}, state, { selectedFile: action.selectedFile });
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -2,31 +2,53 @@ import * as ActionTypes from '../../../constants';
|
|||
|
||||
const initialState = {
|
||||
isVisible: false,
|
||||
fontSize: 18
|
||||
fontSize: 18,
|
||||
indentationAmount: 2,
|
||||
isTabIndent: true
|
||||
};
|
||||
|
||||
const preferences = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.OPEN_PREFERENCES:
|
||||
return {
|
||||
isVisible: true,
|
||||
fontSize: state.fontSize
|
||||
};
|
||||
return Object.assign({}, state, {
|
||||
isVisible: true
|
||||
});
|
||||
case ActionTypes.CLOSE_PREFERENCES:
|
||||
return {
|
||||
isVisible: false,
|
||||
fontSize: state.fontSize
|
||||
};
|
||||
return Object.assign({}, state, {
|
||||
isVisible: false
|
||||
});
|
||||
case ActionTypes.INCREASE_FONTSIZE:
|
||||
return {
|
||||
isVisible: state.isVisible,
|
||||
return Object.assign({}, state, {
|
||||
fontSize: state.fontSize + 2
|
||||
};
|
||||
});
|
||||
case ActionTypes.DECREASE_FONTSIZE:
|
||||
return {
|
||||
isVisible: state.isVisible,
|
||||
return Object.assign({}, state, {
|
||||
fontSize: state.fontSize - 2
|
||||
};
|
||||
});
|
||||
case ActionTypes.UPDATE_FONTSIZE:
|
||||
return Object.assign({}, state, {
|
||||
fontSize: action.value
|
||||
});
|
||||
case ActionTypes.INCREASE_INDENTATION:
|
||||
return Object.assign({}, state, {
|
||||
indentationAmount: state.indentationAmount + 2
|
||||
});
|
||||
case ActionTypes.DECREASE_INDENTATION:
|
||||
return Object.assign({}, state, {
|
||||
indentationAmount: state.indentationAmount - 2
|
||||
});
|
||||
case ActionTypes.UPDATE_INDENTATION:
|
||||
return Object.assign({}, state, {
|
||||
indentationAmount: action.value
|
||||
});
|
||||
case ActionTypes.INDENT_WITH_TAB:
|
||||
return Object.assign({}, state, {
|
||||
isTabIndent: true
|
||||
});
|
||||
case ActionTypes.INDENT_WITH_SPACE:
|
||||
return Object.assign({}, state, {
|
||||
isTabIndent: false
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -69,6 +69,8 @@
|
|||
@extend %toolbar-button;
|
||||
color: $light-primary-text-color;
|
||||
background-color: $light-modal-button-background-color;
|
||||
padding: 0;
|
||||
line-height: #{50 / $base-font-size}rem;
|
||||
& g {
|
||||
fill: $light-primary-text-color;
|
||||
}
|
||||
|
@ -82,9 +84,25 @@
|
|||
}
|
||||
|
||||
%fake-link {
|
||||
color: $light-secondary-text-color;
|
||||
color: $light-inactive-text-color;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: $light-primary-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
%preference-option {
|
||||
background-color: $light-button-background-color;
|
||||
color: $light-inactive-text-color;
|
||||
font-size: #{14 / $base-font-size}rem;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
margin-bottom: #{5 / $base-font-size}rem;
|
||||
border: 0px;
|
||||
&:hover {
|
||||
color: $light-primary-text-color;
|
||||
}
|
||||
&--selected {
|
||||
color: $light-primary-text-color;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ $light-button-background-hover-color: $p5js-pink;
|
|||
$light-button-background-active-color: #f10046;
|
||||
$light-button-hover-color: $white;
|
||||
$light-button-active-color: $white;
|
||||
$light-modal-button-background-color: #e6e6e6;
|
||||
$light-modal-button-background-color: #e6e6e6;
|
||||
$light-icon-color: #8b8b8b;
|
||||
$light-icon-hover-color: $light-primary-text-color;
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ body, input, button {
|
|||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: $light-secondary-text-color;
|
||||
color: $light-inactive-text-color;
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: $light-primary-text-color;
|
||||
|
@ -46,7 +46,12 @@ h2 {
|
|||
h3 {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-weight: normal;
|
||||
}
|
||||
h6 {
|
||||
font-weight: normal;
|
||||
}
|
||||
thead {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
position: absolute;
|
||||
top: #{66 / $base-font-size}rem;
|
||||
right: #{40 / $base-font-size}rem;
|
||||
width: #{276 / $base-font-size}rem;
|
||||
width: #{332 / $base-font-size}rem;
|
||||
background-color: $light-button-background-color;
|
||||
display: none;
|
||||
padding: #{16 / $base-font-size}rem #{26 / $base-font-size}rem;
|
||||
|
@ -35,7 +35,10 @@
|
|||
.preference {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
padding-bottom: #{40 / $base-font-size}rem;
|
||||
& + & {
|
||||
border-top: 2px dashed $light-button-border-color;
|
||||
}
|
||||
}
|
||||
|
||||
.preference__title {
|
||||
|
@ -44,8 +47,35 @@
|
|||
}
|
||||
|
||||
.preference__value {
|
||||
border: 1px solid $light-button-border-color;
|
||||
border: 2px solid $light-button-border-color;
|
||||
text-align: center;
|
||||
line-height: #{48 / $base-font-size}rem;
|
||||
border-radius: 0%;
|
||||
width: #{48 / $base-font-size}rem;
|
||||
height: #{44 / $base-font-size}rem;
|
||||
margin: 0 #{28 / $base-font-size}rem;
|
||||
padding: 0;
|
||||
background-color: $light-button-background-color;
|
||||
}
|
||||
|
||||
.preference__label {
|
||||
margin: 0;
|
||||
line-height: #{20 / $base-font-size}rem;
|
||||
color: $light-inactive-text-color;
|
||||
&:hover {
|
||||
color: $light-inactive-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.preference__vertical-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.preference__option {
|
||||
@extend %preference-option;
|
||||
list-style-type: none;
|
||||
padding-left: #{28 / $base-font-size}rem;
|
||||
&--selected {
|
||||
@extend %preference-option--selected;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
.sidebar__file-list {
|
||||
padding: #{4 / $base-font-size}rem #{20 / $base-font-size}rem;
|
||||
border-top: 1px solid $ide-border-color;
|
||||
}
|
||||
|
||||
.sidebar__file-item {
|
||||
padding: #{4 / $base-font-size}rem 0;
|
||||
padding: #{8 / $base-font-size}rem #{20 / $base-font-size}rem;
|
||||
color: $light-inactive-text-color;
|
||||
}
|
||||
|
||||
|
|
|
@ -45,12 +45,12 @@
|
|||
}
|
||||
|
||||
.toolbar__project-name {
|
||||
color: $light-secondary-text-color;
|
||||
color: $light-inactive-text-color;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: $light-primary-text-color;
|
||||
}
|
||||
&:focus {
|
||||
color: $light-secondary-text-color;
|
||||
color: $light-inactive-text-color;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,11 +60,13 @@
|
|||
"babel-core": "^6.8.0",
|
||||
"bcrypt-nodejs": "0.0.3",
|
||||
"body-parser": "^1.15.1",
|
||||
"bson-objectid": "^1.1.4",
|
||||
"classnames": "^2.2.5",
|
||||
"codemirror": "^5.14.2",
|
||||
"connect-mongo": "^1.2.0",
|
||||
"cookie-parser": "^1.4.1",
|
||||
"dotenv": "^2.0.0",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"eslint-loader": "^1.3.0",
|
||||
"express": "^4.13.4",
|
||||
"express-session": "^1.13.0",
|
||||
|
@ -81,6 +83,7 @@
|
|||
"redux": "^3.5.2",
|
||||
"redux-form": "^5.2.5",
|
||||
"redux-thunk": "^2.1.0",
|
||||
"shortid": "^2.2.6"
|
||||
"shortid": "^2.2.6",
|
||||
"srcdoc-polyfill": "^0.2.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,7 @@ import Project from '../models/project';
|
|||
|
||||
export function createProject(req, res) {
|
||||
const projectValues = {
|
||||
user: req.user ? req.user._id : undefined, // eslint-disable-line no-underscore-dangle
|
||||
file: {}
|
||||
user: req.user ? req.user._id : undefined // eslint-disable-line no-underscore-dangle
|
||||
};
|
||||
|
||||
Object.assign(projectValues, req.body);
|
||||
|
|
|
@ -12,16 +12,24 @@ function draw() {
|
|||
}`
|
||||
|
||||
const defaultHTML =
|
||||
`
|
||||
<!DOCTYPE html>
|
||||
`<!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({
|
||||
name: { type: String, default: 'sketch.js' },
|
||||
|
@ -39,8 +47,11 @@ fileSchema.set('toJSON', {
|
|||
const projectSchema = new Schema({
|
||||
name: { type: String, default: "Hello p5.js, it's the server" },
|
||||
user: { type: Schema.Types.ObjectId, ref: 'User' },
|
||||
files: {type: [ fileSchema ], default: [{ name: 'sketch.js', content: defaultSketch, _id: new ObjectId() }, { name: 'index.html', content: defaultHTML, _id: new ObjectId() }]},
|
||||
_id: { type: String, default: shortid.generate }
|
||||
files: { type: [ fileSchema ], default: [{ name: 'sketch.js', content: defaultSketch, _id: new ObjectId() },
|
||||
{ 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 });
|
||||
|
||||
projectSchema.virtual('id').get(function(){
|
||||
|
@ -51,4 +62,12 @@ 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);
|
||||
|
|
|
@ -83,4 +83,3 @@ app.listen(serverConfig.port, (error) => {
|
|||
});
|
||||
|
||||
export default app;
|
||||
|
||||
|
|
Loading…
Reference in a new issue