fix merge conflicts
This commit is contained in:
commit
038d290577
34 changed files with 665 additions and 32 deletions
|
@ -28,6 +28,16 @@ function Nav(props) {
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
|
<li className="nav__item">
|
||||||
|
<a className="nav__export" onClick={props.exportProjectAsZip}>
|
||||||
|
Export (zip)
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li className="nav__item" onClick={props.cloneProject}>
|
||||||
|
<a className="nav__clone">
|
||||||
|
Clone
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul className="nav__items-right" title="user-menu">
|
<ul className="nav__items-right" title="user-menu">
|
||||||
<li className="nav__item">
|
<li className="nav__item">
|
||||||
|
@ -42,6 +52,8 @@ function Nav(props) {
|
||||||
Nav.propTypes = {
|
Nav.propTypes = {
|
||||||
createProject: PropTypes.func.isRequired,
|
createProject: PropTypes.func.isRequired,
|
||||||
saveProject: PropTypes.func.isRequired,
|
saveProject: PropTypes.func.isRequired,
|
||||||
|
exportProjectAsZip: PropTypes.func.isRequired,
|
||||||
|
cloneProject: PropTypes.func.isRequired,
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
authenticated: PropTypes.bool.isRequired,
|
authenticated: PropTypes.bool.isRequired,
|
||||||
username: PropTypes.string
|
username: PropTypes.string
|
||||||
|
|
|
@ -30,6 +30,12 @@ export const SET_PROJECT = 'SET_PROJECT';
|
||||||
export const SET_PROJECTS = 'SET_PROJECTS';
|
export const SET_PROJECTS = 'SET_PROJECTS';
|
||||||
|
|
||||||
export const SET_SELECTED_FILE = 'SET_SELECTED_FILE';
|
export const SET_SELECTED_FILE = 'SET_SELECTED_FILE';
|
||||||
|
export const SHOW_MODAL = 'SHOW_MODAL';
|
||||||
|
export const HIDE_MODAL = 'HIDE_MODAL';
|
||||||
|
export const CREATE_FILE = 'CREATE_FILE';
|
||||||
|
|
||||||
|
export const EXPAND_SIDEBAR = 'EXPAND_SIDEBAR';
|
||||||
|
export const COLLAPSE_SIDEBAR = 'COLLAPSE_SIDEBAR';
|
||||||
|
|
||||||
// eventually, handle errors more specifically and better
|
// eventually, handle errors more specifically and better
|
||||||
export const ERROR = 'ERROR';
|
export const ERROR = 'ERROR';
|
||||||
|
|
12
client/images/left-arrow.svg
Normal file
12
client/images/left-arrow.svg
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="10px" height="15px" viewBox="0 0 10 15" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>arrow shape copy</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="IDEs" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0.539999962">
|
||||||
|
<g id="p5js-IDE-styles-foundation-pt-2" transform="translate(-529.000000, -1165.000000)" fill="#333333">
|
||||||
|
<polygon id="arrow-shape-copy" transform="translate(534.000000, 1172.198314) rotate(-90.000000) translate(-534.000000, -1172.198314) " points="535.4 1169.39663 541 1174.99663 539.6 1176.39663 534 1170.79663 528.4 1176.39663 527 1174.99663 532.6 1169.39663 533.996628 1168"></polygon>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 916 B |
16
client/images/plus-icon.svg
Normal file
16
client/images/plus-icon.svg
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>close shape</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="IDEs" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0.539999962">
|
||||||
|
<g id="p5js-IDE-styles-foundation-pt-2" transform="translate(-558.000000, -1166.000000)" fill="#333333">
|
||||||
|
<g id="Icons" transform="translate(16.000000, 1063.000000)">
|
||||||
|
<g id="close-copy-3" transform="translate(499.500000, 110.000000) scale(1, -1) translate(-499.500000, -110.000000) translate(438.000000, 98.000000)">
|
||||||
|
<polygon id="close-shape" transform="translate(113.000000, 10.000000) rotate(45.000000) translate(-113.000000, -10.000000) " points="120 15.6 118.6 17 113 11.4 107.4 17 106 15.6 111.6 10 106 4.4 107.4 3 113 8.6 118.6 3 120 4.4 114.4 10"></polygon>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
12
client/images/right-arrow.svg
Normal file
12
client/images/right-arrow.svg
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="10px" height="15px" viewBox="0 0 10 15" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>arrow shape copy</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="IDEs" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0.539999962">
|
||||||
|
<g id="p5js-IDE-styles-foundation-pt-2" transform="translate(-496.000000, -1165.000000)" fill="#333333">
|
||||||
|
<polygon id="arrow-shape-copy" transform="translate(501.000000, 1172.198314) rotate(90.000000) translate(-501.000000, -1172.198314) " points="502.4 1169.39663 508 1174.99663 506.6 1176.39663 501 1170.79663 495.4 1176.39663 494 1174.99663 499.6 1169.39663 500.996628 1168"></polygon>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 915 B |
|
@ -1,4 +1,7 @@
|
||||||
import * as ActionTypes from '../../../constants';
|
import * as ActionTypes from '../../../constants';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const ROOT_URL = location.href.indexOf('localhost') > 0 ? 'http://localhost:8000/api' : '/api';
|
||||||
|
|
||||||
export function updateFileContent(name, content) {
|
export function updateFileContent(name, content) {
|
||||||
return {
|
return {
|
||||||
|
@ -7,3 +10,41 @@ export function updateFileContent(name, content) {
|
||||||
content
|
content
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO make req to server
|
||||||
|
export function createFile(formProps) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
if (state.project.id) {
|
||||||
|
const postParams = {
|
||||||
|
name: formProps.name
|
||||||
|
};
|
||||||
|
axios.post(`${ROOT_URL}/projects/${state.project.id}/files`, postParams, { withCredentials: true })
|
||||||
|
.then(response => {
|
||||||
|
dispatch({
|
||||||
|
type: ActionTypes.CREATE_FILE,
|
||||||
|
...response.data
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(response => dispatch({
|
||||||
|
type: ActionTypes.ERROR,
|
||||||
|
error: response.data
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
let maxFileId = 0;
|
||||||
|
state.files.forEach(file => {
|
||||||
|
if (parseInt(file.id, 10) > maxFileId) {
|
||||||
|
maxFileId = parseInt(file.id, 10);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dispatch({
|
||||||
|
type: ActionTypes.CREATE_FILE,
|
||||||
|
name: formProps.name,
|
||||||
|
id: `${maxFileId + 1}`
|
||||||
|
});
|
||||||
|
dispatch({
|
||||||
|
type: ActionTypes.HIDE_MODAL
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -24,3 +24,27 @@ export function setSelectedFile(fileId) {
|
||||||
selectedFile: fileId
|
selectedFile: fileId
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function newFile() {
|
||||||
|
return {
|
||||||
|
type: ActionTypes.SHOW_MODAL
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function closeNewFileModal() {
|
||||||
|
return {
|
||||||
|
type: ActionTypes.HIDE_MODAL
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function expandSidebar() {
|
||||||
|
return {
|
||||||
|
type: ActionTypes.EXPAND_SIDEBAR
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function collapseSidebar() {
|
||||||
|
return {
|
||||||
|
type: ActionTypes.COLLAPSE_SIDEBAR
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import * as ActionTypes from '../../../constants';
|
import * as ActionTypes from '../../../constants';
|
||||||
import { browserHistory } from 'react-router';
|
import { browserHistory } from 'react-router';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import JSZip from 'jszip';
|
||||||
|
import { saveAs } from 'file-saver';
|
||||||
|
|
||||||
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';
|
||||||
|
|
||||||
|
@ -8,12 +10,14 @@ export function getProject(id) {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
axios.get(`${ROOT_URL}/projects/${id}`, { withCredentials: true })
|
axios.get(`${ROOT_URL}/projects/${id}`, { withCredentials: true })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
console.log(response.data);
|
||||||
browserHistory.push(`/projects/${id}`);
|
browserHistory.push(`/projects/${id}`);
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ActionTypes.SET_PROJECT,
|
type: ActionTypes.SET_PROJECT,
|
||||||
project: response.data,
|
project: response.data,
|
||||||
files: response.data.files,
|
files: response.data.files,
|
||||||
selectedFile: response.data.selectedFile
|
selectedFile: response.data.selectedFile,
|
||||||
|
owner: response.data.user
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(response => dispatch({
|
.catch(response => dispatch({
|
||||||
|
@ -61,6 +65,7 @@ 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,
|
||||||
|
owner: response.data.user,
|
||||||
selectedFile: response.data.selectedFile,
|
selectedFile: response.data.selectedFile,
|
||||||
files: response.data.files
|
files: response.data.files
|
||||||
});
|
});
|
||||||
|
@ -83,6 +88,7 @@ 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,
|
||||||
|
owner: response.data.user,
|
||||||
selectedFile: response.data.selectedFile,
|
selectedFile: response.data.selectedFile,
|
||||||
files: response.data.files
|
files: response.data.files
|
||||||
});
|
});
|
||||||
|
@ -93,3 +99,42 @@ export function createProject() {
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function exportProjectAsZip() {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
console.log('exporting project!');
|
||||||
|
const state = getState();
|
||||||
|
const zip = new JSZip();
|
||||||
|
state.files.forEach(file => {
|
||||||
|
zip.file(file.name, file.content);
|
||||||
|
});
|
||||||
|
|
||||||
|
zip.generateAsync({ type: 'blob' }).then((content) => {
|
||||||
|
saveAs(content, `${state.project.name}.zip`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cloneProject() {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
const formParams = Object.assign({}, { name: state.project.name }, { files: state.files });
|
||||||
|
axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true })
|
||||||
|
.then(response => {
|
||||||
|
browserHistory.push(`/projects/${response.data.id}`);
|
||||||
|
dispatch({
|
||||||
|
type: ActionTypes.NEW_PROJECT,
|
||||||
|
name: response.data.name,
|
||||||
|
id: response.data.id,
|
||||||
|
owner: response.data.user,
|
||||||
|
selectedFile: response.data.selectedFile,
|
||||||
|
files: response.data.files
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(response => dispatch({
|
||||||
|
type: ActionTypes.PROJECT_SAVE_FAIL,
|
||||||
|
error: response.data
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,21 @@
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import CodeMirror from 'codemirror';
|
import CodeMirror from 'codemirror';
|
||||||
import 'codemirror/mode/javascript/javascript';
|
import 'codemirror/mode/javascript/javascript';
|
||||||
|
import 'codemirror/mode/css/css';
|
||||||
|
import 'codemirror/mode/htmlmixed/htmlmixed';
|
||||||
import 'codemirror/addon/selection/active-line';
|
import 'codemirror/addon/selection/active-line';
|
||||||
|
import 'codemirror/addon/lint/lint';
|
||||||
|
import 'codemirror/addon/lint/javascript-lint';
|
||||||
|
import 'codemirror/addon/lint/css-lint';
|
||||||
|
import 'codemirror/addon/lint/html-lint';
|
||||||
|
import { JSHINT } from 'jshint';
|
||||||
|
window.JSHINT = JSHINT;
|
||||||
|
import { CSSLint } from 'csslint';
|
||||||
|
window.CSSLint = CSSLint;
|
||||||
|
import { HTMLHint } from 'htmlhint';
|
||||||
|
window.HTMLHint = HTMLHint;
|
||||||
|
|
||||||
|
import { debounce } from 'throttle-debounce';
|
||||||
|
|
||||||
class Editor extends React.Component {
|
class Editor extends React.Component {
|
||||||
|
|
||||||
|
@ -13,12 +27,18 @@ class Editor extends React.Component {
|
||||||
styleActiveLine: true,
|
styleActiveLine: true,
|
||||||
inputStyle: 'contenteditable',
|
inputStyle: 'contenteditable',
|
||||||
mode: 'javascript',
|
mode: 'javascript',
|
||||||
lineWrapping: true
|
lineWrapping: true,
|
||||||
|
gutters: ['CodeMirror-lint-markers'],
|
||||||
|
lint: true
|
||||||
});
|
});
|
||||||
this._cm.on('change', () => { // eslint-disable-line
|
this._cm.on('change', debounce(200, () => {
|
||||||
// this.props.updateFileContent('sketch.js', this._cm.getValue());
|
|
||||||
this.props.updateFileContent(this.props.file.name, this._cm.getValue());
|
this.props.updateFileContent(this.props.file.name, this._cm.getValue());
|
||||||
});
|
}));
|
||||||
|
// this._cm.on('change', () => { // eslint-disable-line
|
||||||
|
// // this.props.updateFileContent('sketch.js', this._cm.getValue());
|
||||||
|
// throttle(1000, () => console.log('debounce is working!'));
|
||||||
|
// 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);
|
||||||
this._cm.setOption('tabSize', this.props.indentationAmount);
|
this._cm.setOption('tabSize', this.props.indentationAmount);
|
||||||
|
@ -38,6 +58,15 @@ class Editor extends React.Component {
|
||||||
if (this.props.isTabIndent !== prevProps.isTabIndent) {
|
if (this.props.isTabIndent !== prevProps.isTabIndent) {
|
||||||
this._cm.setOption('indentWithTabs', this.props.isTabIndent);
|
this._cm.setOption('indentWithTabs', this.props.isTabIndent);
|
||||||
}
|
}
|
||||||
|
if (this.props.file.name !== prevProps.name) {
|
||||||
|
if (this.props.file.name.match(/.+\.js$/)) {
|
||||||
|
this._cm.setOption('mode', 'javascript');
|
||||||
|
} else if (this.props.file.name.match(/.+\.css$/)) {
|
||||||
|
this._cm.setOption('mode', 'css');
|
||||||
|
} else if (this.props.file.name.match(/.+\.html$/)) {
|
||||||
|
this._cm.setOption('mode', 'htmlmixed');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
|
28
client/modules/IDE/components/NewFileForm.js
Normal file
28
client/modules/IDE/components/NewFileForm.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import React, { PropTypes } from 'react';
|
||||||
|
|
||||||
|
function NewFileForm(props) {
|
||||||
|
const { fields: { name }, handleSubmit } = props;
|
||||||
|
return (
|
||||||
|
<form className="new-file-form" onSubmit={handleSubmit(props.createFile.bind(this))}>
|
||||||
|
<label className="new-file-form__name-label" htmlFor="name">Name:</label>
|
||||||
|
<input
|
||||||
|
className="new-file-form__name-input"
|
||||||
|
id="name"
|
||||||
|
type="text"
|
||||||
|
placeholder="Name"
|
||||||
|
{...name}
|
||||||
|
/>
|
||||||
|
<input type="submit" value="Add File" />
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
NewFileForm.propTypes = {
|
||||||
|
fields: PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired
|
||||||
|
}).isRequired,
|
||||||
|
handleSubmit: PropTypes.func.isRequired,
|
||||||
|
createFile: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NewFileForm;
|
66
client/modules/IDE/components/NewFileModal.js
Normal file
66
client/modules/IDE/components/NewFileModal.js
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import React, { PropTypes } from 'react';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
import { reduxForm } from 'redux-form';
|
||||||
|
import NewFileForm from './NewFileForm';
|
||||||
|
import * as FileActions from '../actions/files';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import InlineSVG from 'react-inlinesvg';
|
||||||
|
const exitUrl = require('../../../images/exit.svg');
|
||||||
|
|
||||||
|
// At some point this will probably be generalized to a generic modal
|
||||||
|
// in which you can insert different content
|
||||||
|
// but for now, let's just make this work
|
||||||
|
function NewFileModal(props) {
|
||||||
|
const modalClass = classNames({
|
||||||
|
modal: true,
|
||||||
|
'modal--hidden': !props.isVisible
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={modalClass}>
|
||||||
|
<div className="modal-content">
|
||||||
|
<div className="modal__header">
|
||||||
|
<h2 className="modal__title">Add File</h2>
|
||||||
|
<button className="modal__exit-button" onClick={props.closeModal}>
|
||||||
|
<InlineSVG src={exitUrl} alt="Close New File Modal" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<NewFileForm {...props} />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
NewFileModal.propTypes = {
|
||||||
|
isVisible: PropTypes.bool.isRequired,
|
||||||
|
closeModal: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
file: state.files
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch) {
|
||||||
|
return bindActionCreators(FileActions, dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validate(formProps) {
|
||||||
|
const errors = {};
|
||||||
|
|
||||||
|
if (!formProps.name) {
|
||||||
|
errors.name = 'Please enter a name';
|
||||||
|
} else if (!formProps.name.match(/(.+\.js$|.+\.css$)/)) {
|
||||||
|
errors.name = 'File must be of type JavaScript or CSS.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default reduxForm({
|
||||||
|
form: 'new-file',
|
||||||
|
fields: ['name'],
|
||||||
|
validate
|
||||||
|
}, mapStateToProps, mapDispatchToProps)(NewFileModal);
|
|
@ -1,10 +1,41 @@
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import InlineSVG from 'react-inlinesvg';
|
||||||
|
const rightArrowUrl = require('../../../images/right-arrow.svg');
|
||||||
|
const leftArrowUrl = require('../../../images/left-arrow.svg');
|
||||||
|
|
||||||
function Sidebar(props) {
|
function Sidebar(props) {
|
||||||
|
const sidebarClass = classNames({
|
||||||
|
sidebar: true,
|
||||||
|
'sidebar--contracted': !props.isExpanded
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="sidebar" role="navigation" title="file-navigation">
|
<nav className={sidebarClass} title="file-navigation">
|
||||||
<ul className="sidebar__file-list" title="file-list">
|
<div className="sidebar__header">
|
||||||
|
<h3 className="sidebar__title">Sketch Files</h3>
|
||||||
|
<div className="sidebar__icons">
|
||||||
|
<a
|
||||||
|
className="sidebar__add"
|
||||||
|
onClick={props.newFile}
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
className="sidebar__contract"
|
||||||
|
onClick={props.collapseSidebar}
|
||||||
|
>
|
||||||
|
<InlineSVG src={leftArrowUrl} />
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
className="sidebar__expand"
|
||||||
|
onClick={props.expandSidebar}
|
||||||
|
>
|
||||||
|
<InlineSVG src={rightArrowUrl} />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul className="sidebar__file-list">
|
||||||
{props.files.map(file => {
|
{props.files.map(file => {
|
||||||
let itemClass = classNames({
|
let itemClass = classNames({
|
||||||
'sidebar__file-item': true,
|
'sidebar__file-item': true,
|
||||||
|
|
|
@ -46,6 +46,13 @@ function Toolbar(props) {
|
||||||
>
|
>
|
||||||
{props.projectName}
|
{props.projectName}
|
||||||
</span>
|
</span>
|
||||||
|
{(() => { // eslint-disable-line
|
||||||
|
if (props.owner) {
|
||||||
|
return (
|
||||||
|
<p className="toolbar__project-owner">by <span>{props.owner.username}</span></p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
className={preferencesButtonClass}
|
className={preferencesButtonClass}
|
||||||
|
@ -68,7 +75,10 @@ Toolbar.propTypes = {
|
||||||
stopSketch: PropTypes.func.isRequired,
|
stopSketch: PropTypes.func.isRequired,
|
||||||
setProjectName: PropTypes.func.isRequired,
|
setProjectName: PropTypes.func.isRequired,
|
||||||
projectName: PropTypes.string.isRequired,
|
projectName: PropTypes.string.isRequired,
|
||||||
openPreferences: PropTypes.func.isRequired
|
openPreferences: PropTypes.func.isRequired,
|
||||||
|
owner: PropTypes.shape({
|
||||||
|
username: PropTypes.string
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Toolbar;
|
export default Toolbar;
|
||||||
|
|
|
@ -4,6 +4,7 @@ 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';
|
||||||
|
import NewFileModal from '../components/NewFileModal';
|
||||||
import Nav from '../../../components/Nav';
|
import Nav from '../../../components/Nav';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
@ -28,6 +29,8 @@ class IDEView extends React.Component {
|
||||||
user={this.props.user}
|
user={this.props.user}
|
||||||
createProject={this.props.createProject}
|
createProject={this.props.createProject}
|
||||||
saveProject={this.props.saveProject}
|
saveProject={this.props.saveProject}
|
||||||
|
exportProjectAsZip={this.props.exportProjectAsZip}
|
||||||
|
cloneProject={this.props.cloneProject}
|
||||||
/>
|
/>
|
||||||
<Toolbar
|
<Toolbar
|
||||||
className="Toolbar"
|
className="Toolbar"
|
||||||
|
@ -38,6 +41,7 @@ class IDEView extends React.Component {
|
||||||
setProjectName={this.props.setProjectName}
|
setProjectName={this.props.setProjectName}
|
||||||
openPreferences={this.props.openPreferences}
|
openPreferences={this.props.openPreferences}
|
||||||
isPreferencesVisible={this.props.preferences.isVisible}
|
isPreferencesVisible={this.props.preferences.isVisible}
|
||||||
|
owner={this.props.project.owner}
|
||||||
/>
|
/>
|
||||||
<Preferences
|
<Preferences
|
||||||
isVisible={this.props.preferences.isVisible}
|
isVisible={this.props.preferences.isVisible}
|
||||||
|
@ -58,6 +62,10 @@ class IDEView extends React.Component {
|
||||||
files={this.props.files}
|
files={this.props.files}
|
||||||
selectedFile={this.props.selectedFile}
|
selectedFile={this.props.selectedFile}
|
||||||
setSelectedFile={this.props.setSelectedFile}
|
setSelectedFile={this.props.setSelectedFile}
|
||||||
|
newFile={this.props.newFile}
|
||||||
|
isExpanded={this.props.ide.sidebarIsExpanded}
|
||||||
|
expandSidebar={this.props.expandSidebar}
|
||||||
|
collapseSidebar={this.props.collapseSidebar}
|
||||||
/>
|
/>
|
||||||
<Editor
|
<Editor
|
||||||
file={this.props.selectedFile}
|
file={this.props.selectedFile}
|
||||||
|
@ -78,6 +86,10 @@ class IDEView extends React.Component {
|
||||||
}
|
}
|
||||||
isPlaying={this.props.ide.isPlaying}
|
isPlaying={this.props.ide.isPlaying}
|
||||||
/>
|
/>
|
||||||
|
<NewFileModal
|
||||||
|
isVisible={this.props.ide.modalIsVisible}
|
||||||
|
closeModal={this.props.closeNewFileModal}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -92,12 +104,17 @@ IDEView.propTypes = {
|
||||||
createProject: PropTypes.func.isRequired,
|
createProject: PropTypes.func.isRequired,
|
||||||
saveProject: PropTypes.func.isRequired,
|
saveProject: PropTypes.func.isRequired,
|
||||||
ide: PropTypes.shape({
|
ide: PropTypes.shape({
|
||||||
isPlaying: PropTypes.bool.isRequired
|
isPlaying: PropTypes.bool.isRequired,
|
||||||
|
modalIsVisible: PropTypes.bool.isRequired,
|
||||||
|
sidebarIsExpanded: PropTypes.bool.isRequired
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
startSketch: PropTypes.func.isRequired,
|
startSketch: PropTypes.func.isRequired,
|
||||||
stopSketch: PropTypes.func.isRequired,
|
stopSketch: PropTypes.func.isRequired,
|
||||||
project: PropTypes.shape({
|
project: PropTypes.shape({
|
||||||
name: PropTypes.string.isRequired
|
name: PropTypes.string.isRequired,
|
||||||
|
owner: PropTypes.shape({
|
||||||
|
username: PropTypes.string
|
||||||
|
})
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
setProjectName: PropTypes.func.isRequired,
|
setProjectName: PropTypes.func.isRequired,
|
||||||
openPreferences: PropTypes.func.isRequired,
|
openPreferences: PropTypes.func.isRequired,
|
||||||
|
@ -125,7 +142,13 @@ IDEView.propTypes = {
|
||||||
setSelectedFile: PropTypes.func.isRequired,
|
setSelectedFile: PropTypes.func.isRequired,
|
||||||
htmlFile: PropTypes.object.isRequired,
|
htmlFile: PropTypes.object.isRequired,
|
||||||
jsFiles: PropTypes.array.isRequired,
|
jsFiles: PropTypes.array.isRequired,
|
||||||
cssFiles: PropTypes.array.isRequired
|
cssFiles: PropTypes.array.isRequired,
|
||||||
|
newFile: PropTypes.func.isRequired,
|
||||||
|
closeNewFileModal: PropTypes.func.isRequired,
|
||||||
|
expandSidebar: PropTypes.func.isRequired,
|
||||||
|
collapseSidebar: PropTypes.func.isRequired,
|
||||||
|
exportProjectAsZip: PropTypes.func.isRequired,
|
||||||
|
cloneProject: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
|
|
|
@ -64,6 +64,8 @@ const files = (state = initialState, action) => {
|
||||||
return [...action.files];
|
return [...action.files];
|
||||||
case ActionTypes.SET_PROJECT:
|
case ActionTypes.SET_PROJECT:
|
||||||
return [...action.files];
|
return [...action.files];
|
||||||
|
case ActionTypes.CREATE_FILE:
|
||||||
|
return [...state, { name: action.name, id: action.id, content: '' }];
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,9 @@ import * as ActionTypes from '../../../constants';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
isPlaying: false,
|
isPlaying: false,
|
||||||
selectedFile: '1'
|
selectedFile: '1',
|
||||||
|
modalIsVisible: false,
|
||||||
|
sidebarIsExpanded: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const ide = (state = initialState, action) => {
|
const ide = (state = initialState, action) => {
|
||||||
|
@ -17,6 +19,14 @@ const ide = (state = initialState, action) => {
|
||||||
case ActionTypes.SET_PROJECT:
|
case ActionTypes.SET_PROJECT:
|
||||||
case ActionTypes.NEW_PROJECT:
|
case ActionTypes.NEW_PROJECT:
|
||||||
return Object.assign({}, state, { selectedFile: action.selectedFile });
|
return Object.assign({}, state, { selectedFile: action.selectedFile });
|
||||||
|
case ActionTypes.SHOW_MODAL:
|
||||||
|
return Object.assign({}, state, { modalIsVisible: true });
|
||||||
|
case ActionTypes.HIDE_MODAL:
|
||||||
|
return Object.assign({}, state, { modalIsVisible: false });
|
||||||
|
case ActionTypes.COLLAPSE_SIDEBAR:
|
||||||
|
return Object.assign({}, state, { sidebarIsExpanded: false });
|
||||||
|
case ActionTypes.EXPAND_SIDEBAR:
|
||||||
|
return Object.assign({}, state, { sidebarIsExpanded: true });
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,18 +7,18 @@ const initialState = {
|
||||||
const project = (state = initialState, action) => {
|
const project = (state = initialState, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ActionTypes.SET_PROJECT_NAME:
|
case ActionTypes.SET_PROJECT_NAME:
|
||||||
return {
|
return Object.assign({}, { ...state }, { name: action.name });
|
||||||
name: action.name
|
|
||||||
};
|
|
||||||
case ActionTypes.NEW_PROJECT:
|
case ActionTypes.NEW_PROJECT:
|
||||||
return {
|
return {
|
||||||
id: action.id,
|
id: action.id,
|
||||||
name: action.name
|
name: action.name,
|
||||||
|
owner: action.owner
|
||||||
};
|
};
|
||||||
case ActionTypes.SET_PROJECT:
|
case ActionTypes.SET_PROJECT:
|
||||||
return {
|
return {
|
||||||
id: action.project.id,
|
id: action.project.id,
|
||||||
name: action.project.name
|
name: action.project.name,
|
||||||
|
owner: action.owner
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $light-icon-hover-color;
|
color: $light-icon-hover-color;
|
||||||
& g {
|
& g {
|
||||||
|
opacity: 1;
|
||||||
fill: $light-icon-hover-color;
|
fill: $light-icon-hover-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ $light-button-background-active-color: #f10046;
|
||||||
$light-button-hover-color: $white;
|
$light-button-hover-color: $white;
|
||||||
$light-button-active-color: $white;
|
$light-button-active-color: $white;
|
||||||
$light-modal-button-background-color: #e6e6e6;
|
$light-modal-button-background-color: #e6e6e6;
|
||||||
|
$light-modal-border-color: #B9D0E1;
|
||||||
$light-icon-color: #8b8b8b;
|
$light-icon-color: #8b8b8b;
|
||||||
$light-icon-hover-color: $light-primary-text-color;
|
$light-icon-hover-color: $light-primary-text-color;
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ body, input, button {
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: $light-inactive-text-color;
|
color: $light-inactive-text-color;
|
||||||
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: $light-primary-text-color;
|
color: $light-primary-text-color;
|
||||||
|
@ -31,8 +32,9 @@ input, button {
|
||||||
|
|
||||||
input {
|
input {
|
||||||
padding: #{5 / $base-font-size}rem;
|
padding: #{5 / $base-font-size}rem;
|
||||||
border-radius: 2px;
|
// border-radius: 2px;
|
||||||
border: 1px solid $input-border-color;
|
border: 1px solid $input-border-color;
|
||||||
|
padding: #{10 / $base-font-size}rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="submit"] {
|
input[type="submit"] {
|
||||||
|
|
|
@ -19,3 +19,44 @@
|
||||||
.CodeMirror-line {
|
.CodeMirror-line {
|
||||||
padding-left: #{5 / $base-font-size}rem;
|
padding-left: #{5 / $base-font-size}rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.CodeMirror-gutter-wrapper {
|
||||||
|
right: 100%;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-marker-warning, .CodeMirror-lint-marker-error, .CodeMirror-lint-marker-multiple {
|
||||||
|
background-image: none;
|
||||||
|
width: 70px;
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-message-error, .CodeMirror-lint-message-warning {
|
||||||
|
background-image: none;
|
||||||
|
padding-left: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-marker-warning {
|
||||||
|
background-color: rgb(255, 190, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-marker-error {
|
||||||
|
background-color: rgb(255, 95, 82);
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-gutter-elt:not(.CodeMirror-linenumber) {
|
||||||
|
opacity: 0.3;
|
||||||
|
width: 70px !important;
|
||||||
|
height: 100%;
|
||||||
|
// background-color: rgb(255, 95, 82);
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-tooltip {
|
||||||
|
font-family: Montserrat, sans-serif;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid $light-modal-border-color;
|
||||||
|
background-color: $light-button-background-color;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
36
client/styles/components/_modal.scss
Normal file
36
client/styles/components/_modal.scss
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
.modal {
|
||||||
|
position: absolute;
|
||||||
|
top: #{66 / $base-font-size}rem;
|
||||||
|
right: #{400 / $base-font-size}rem;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal--hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
border: 1px solid $light-modal-border-color;
|
||||||
|
background-color: $light-button-background-color;
|
||||||
|
height: #{150 / $base-font-size}rem;
|
||||||
|
width: #{400 / $base-font-size}rem;
|
||||||
|
padding: #{20 / $base-font-size}rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__exit-button {
|
||||||
|
@extend %icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: #{20 / $base-font-size}rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-file-form__name-label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-file-form__name-input {
|
||||||
|
margin-right: #{10 / $base-font-size}rem;
|
||||||
|
}
|
|
@ -27,6 +27,7 @@
|
||||||
.cm-s-p5-widget span.cm-error { color: #f00; }
|
.cm-s-p5-widget span.cm-error { color: #f00; }
|
||||||
|
|
||||||
.cm-s-p5-widget .CodeMirror-activeline-background { background-color: #e8f2ff; }
|
.cm-s-p5-widget .CodeMirror-activeline-background { background-color: #e8f2ff; }
|
||||||
|
// .cm-s-p5-widget .CodeMirror-activeline-gutter { background-color: #e8f2ff; }
|
||||||
.cm-s-p5-widget .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; }
|
.cm-s-p5-widget .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; }
|
||||||
|
|
||||||
/* These styles don't seem to be set by CodeMirror's javascript mode. */
|
/* These styles don't seem to be set by CodeMirror's javascript mode. */
|
||||||
|
|
|
@ -1,8 +1,39 @@
|
||||||
|
.sidebar__header {
|
||||||
|
padding: #{10 / $base-font-size}rem #{6 / $base-font-size}rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-top: 1px solid $ide-border-color;
|
||||||
|
align-items: center;
|
||||||
|
height: #{47 / $base-font-size}rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__title {
|
||||||
|
font-size: #{16 / $base-font-size}rem;
|
||||||
|
display: inline-block;
|
||||||
|
.sidebar--contracted & {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__add {
|
||||||
|
cursor: pointer;
|
||||||
|
height: #{26 / $base-font-size}rem;
|
||||||
|
margin-right: #{16 / $base-font-size}rem;
|
||||||
|
font-size: #{24 / $base-font-size}rem;
|
||||||
|
.sidebar--contracted & {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar__file-list {
|
.sidebar__file-list {
|
||||||
border-top: 1px solid $ide-border-color;
|
border-top: 1px solid $ide-border-color;
|
||||||
|
.sidebar--contracted & {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__file-item {
|
.sidebar__file-item {
|
||||||
|
font-size: #{16 / $base-font-size}rem;
|
||||||
padding: #{8 / $base-font-size}rem #{20 / $base-font-size}rem;
|
padding: #{8 / $base-font-size}rem #{20 / $base-font-size}rem;
|
||||||
color: $light-inactive-text-color;
|
color: $light-inactive-text-color;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -10,3 +41,31 @@
|
||||||
background-color: $ide-border-color;
|
background-color: $ide-border-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar__contract {
|
||||||
|
@extend %icon;
|
||||||
|
height: #{14 / $base-font-size}rem;
|
||||||
|
& svg {
|
||||||
|
height: #{14 / $base-font-size}rem;
|
||||||
|
}
|
||||||
|
.sidebar--contracted & {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__expand {
|
||||||
|
@extend %icon;
|
||||||
|
height: #{14 / $base-font-size}rem;
|
||||||
|
& svg {
|
||||||
|
height: #{14 / $base-font-size}rem;
|
||||||
|
}
|
||||||
|
display: none;
|
||||||
|
.sidebar--contracted & {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__icons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
|
@ -58,3 +58,6 @@
|
||||||
.toolbar__button-label {
|
.toolbar__button-label {
|
||||||
@extend %hidden-label
|
@extend %hidden-label
|
||||||
}
|
}
|
||||||
|
.toolbar__project-owner {
|
||||||
|
margin-left: #{5 / $base-font-size}rem;
|
||||||
|
}
|
||||||
|
|
|
@ -20,5 +20,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
width: #{140 / $base-font-size}rem;
|
width: #{180 / $base-font-size}rem;
|
||||||
|
&.sidebar--contracted {
|
||||||
|
width: #{20 / $base-font-size}rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
@import 'base/base';
|
@import 'base/base';
|
||||||
|
|
||||||
@import 'vendors/codemirror';
|
@import 'vendors/codemirror';
|
||||||
|
@import 'vendors/lint';
|
||||||
|
|
||||||
@import 'components/p5-widget-codemirror-theme';
|
@import 'components/p5-widget-codemirror-theme';
|
||||||
@import 'components/editor';
|
@import 'components/editor';
|
||||||
|
@ -15,6 +16,7 @@
|
||||||
@import 'components/login';
|
@import 'components/login';
|
||||||
@import 'components/sketch-list';
|
@import 'components/sketch-list';
|
||||||
@import 'components/sidebar';
|
@import 'components/sidebar';
|
||||||
|
@import 'components/modal';
|
||||||
|
|
||||||
@import 'layout/ide';
|
@import 'layout/ide';
|
||||||
@import 'layout/sketch-list';
|
@import 'layout/sketch-list';
|
||||||
|
|
73
client/styles/vendors/_lint.scss
vendored
Normal file
73
client/styles/vendors/_lint.scss
vendored
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/* The lint marker gutter */
|
||||||
|
.CodeMirror-lint-markers {
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-tooltip {
|
||||||
|
background-color: infobackground;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 4px 4px 4px 4px;
|
||||||
|
color: infotext;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 10pt;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 2px 5px;
|
||||||
|
position: fixed;
|
||||||
|
white-space: pre;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
z-index: 100;
|
||||||
|
max-width: 600px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity .4s;
|
||||||
|
-moz-transition: opacity .4s;
|
||||||
|
-webkit-transition: opacity .4s;
|
||||||
|
-o-transition: opacity .4s;
|
||||||
|
-ms-transition: opacity .4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning {
|
||||||
|
background-position: left bottom;
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-mark-error {
|
||||||
|
background-image:
|
||||||
|
url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==")
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-mark-warning {
|
||||||
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII=");
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning {
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
vertical-align: middle;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-message-error, .CodeMirror-lint-message-warning {
|
||||||
|
padding-left: 18px;
|
||||||
|
background-position: top left;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error {
|
||||||
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=");
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning {
|
||||||
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=");
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-marker-multiple {
|
||||||
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right bottom;
|
||||||
|
width: 100%; height: 100%;
|
||||||
|
}
|
|
@ -65,11 +65,16 @@
|
||||||
"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",
|
||||||
|
"csslint": "^0.10.0",
|
||||||
"dotenv": "^2.0.0",
|
"dotenv": "^2.0.0",
|
||||||
"escape-string-regexp": "^1.0.5",
|
"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",
|
||||||
|
"file-saver": "^1.3.2",
|
||||||
|
"htmlhint": "^0.9.13",
|
||||||
|
"jshint": "^2.9.2",
|
||||||
|
"jszip": "^3.0.0",
|
||||||
"moment": "^2.14.1",
|
"moment": "^2.14.1",
|
||||||
"mongoose": "^4.4.16",
|
"mongoose": "^4.4.16",
|
||||||
"passport": "^0.3.2",
|
"passport": "^0.3.2",
|
||||||
|
@ -84,6 +89,7 @@
|
||||||
"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"
|
"srcdoc-polyfill": "^0.2.0",
|
||||||
|
"throttle-debounce": "^1.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
19
server/controllers/file.controller.js
Normal file
19
server/controllers/file.controller.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import Project from '../models/Project'
|
||||||
|
|
||||||
|
// Bug -> timestamps don't get created, but it seems like this will
|
||||||
|
// be fixed in mongoose soon
|
||||||
|
// https://github.com/Automattic/mongoose/issues/4049
|
||||||
|
export function createFile(req, res) {
|
||||||
|
Project.findByIdAndUpdate(req.params.project_id,
|
||||||
|
{
|
||||||
|
$push: {
|
||||||
|
'files': req.body
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}, (err, updatedProject) => {
|
||||||
|
if (err) { return res.json({ success: false }); }
|
||||||
|
return res.json(updatedProject.files[updatedProject.files.length - 1]);
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,15 +1,20 @@
|
||||||
import Project from '../models/project';
|
import Project from '../models/project';
|
||||||
|
|
||||||
export function createProject(req, res) {
|
export function createProject(req, res) {
|
||||||
const projectValues = {
|
let 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
|
||||||
};
|
};
|
||||||
|
|
||||||
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) { return res.json({ success: false }); }
|
||||||
return res.json(newProject);
|
Project.populate(newProject,
|
||||||
|
{path: 'user', select: 'username'},
|
||||||
|
(innerErr, newProjectWithUser) => {
|
||||||
|
if (innerErr) { return res.json({ success: false }); }
|
||||||
|
return res.json(newProjectWithUser);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,27 +22,31 @@ export function updateProject(req, res) {
|
||||||
Project.findByIdAndUpdate(req.params.project_id,
|
Project.findByIdAndUpdate(req.params.project_id,
|
||||||
{
|
{
|
||||||
$set: req.body
|
$set: req.body
|
||||||
}, (err, updatedProject) => {
|
})
|
||||||
|
.populate('user', 'username')
|
||||||
|
.exec((err, updatedProject) => {
|
||||||
if (err) { return res.json({ success: false }); }
|
if (err) { return res.json({ success: false }); }
|
||||||
return res.json(updatedProject);
|
return res.json(updatedProject);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProject(req, res) {
|
export function getProject(req, res) {
|
||||||
Project.findById(req.params.project_id, (err, project) => {
|
Project.findById(req.params.project_id)
|
||||||
if (err) {
|
.populate('user', 'username')
|
||||||
return res.status(404).send({ message: 'Project with that id does not exist' });
|
.exec((err, project) => {
|
||||||
}
|
if (err) {
|
||||||
|
return res.status(404).send({ message: 'Project with that id does not exist' });
|
||||||
|
}
|
||||||
|
|
||||||
return res.json(project);
|
return res.json(project);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProjects(req, res) {
|
export function getProjects(req, res) {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
Project.find({user: req.user._id}) // eslint-disable-line no-underscore-dangle
|
Project.find({user: req.user._id}) // eslint-disable-line no-underscore-dangle
|
||||||
.sort('-createdAt')
|
.sort('-createdAt')
|
||||||
.select('name file _id createdAt updatedAt')
|
.select('name files _id createdAt updatedAt')
|
||||||
.exec((err, projects) => {
|
.exec((err, projects) => {
|
||||||
res.json(projects);
|
res.json(projects);
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,7 +11,7 @@ function draw() {
|
||||||
background(220);
|
background(220);
|
||||||
}`
|
}`
|
||||||
|
|
||||||
const defaultHTML =
|
const defaultHTML =
|
||||||
`<!DOCTYPE html>
|
`<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
|
8
server/routes/file.routes.js
Normal file
8
server/routes/file.routes.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { Router } from 'express';
|
||||||
|
import * as FileController from '../controllers/file.controller';
|
||||||
|
|
||||||
|
const router = new Router();
|
||||||
|
|
||||||
|
router.route('/projects/:project_id/files').post(FileController.createFile);
|
||||||
|
|
||||||
|
export default router;
|
|
@ -27,6 +27,7 @@ import serverConfig from './config';
|
||||||
import users from './routes/user.routes';
|
import users from './routes/user.routes';
|
||||||
import sessions from './routes/session.routes';
|
import sessions from './routes/session.routes';
|
||||||
import projects from './routes/project.routes';
|
import projects from './routes/project.routes';
|
||||||
|
import files from './routes/file.routes';
|
||||||
import serverRoutes from './routes/server.routes';
|
import serverRoutes from './routes/server.routes';
|
||||||
|
|
||||||
// Body parser, cookie parser, sessions, serve public assets
|
// Body parser, cookie parser, sessions, serve public assets
|
||||||
|
@ -55,6 +56,7 @@ app.use(passport.session());
|
||||||
app.use('/api', users);
|
app.use('/api', users);
|
||||||
app.use('/api', sessions);
|
app.use('/api', sessions);
|
||||||
app.use('/api', projects);
|
app.use('/api', projects);
|
||||||
|
app.use('/api', files);
|
||||||
// this is supposed to be TEMPORARY -- until i figure out
|
// this is supposed to be TEMPORARY -- until i figure out
|
||||||
// isomorphic rendering
|
// isomorphic rendering
|
||||||
app.use('/', serverRoutes);
|
app.use('/', serverRoutes);
|
||||||
|
|
Loading…
Reference in a new issue