From bc69995fb1c2c474cd31956071baa2db34b73d41 Mon Sep 17 00:00:00 2001
From: catarak
Date: Tue, 12 Jul 2016 17:38:24 -0400
Subject: [PATCH 01/11] super ugly, but added jslinting
---
client/modules/IDE/components/Editor.js | 8 ++-
client/styles/main.scss | 1 +
client/styles/vendors/_lint.scss | 73 +++++++++++++++++++++++++
package.json | 1 +
4 files changed, 82 insertions(+), 1 deletion(-)
create mode 100644 client/styles/vendors/_lint.scss
diff --git a/client/modules/IDE/components/Editor.js b/client/modules/IDE/components/Editor.js
index 70ee4030..9be67dff 100644
--- a/client/modules/IDE/components/Editor.js
+++ b/client/modules/IDE/components/Editor.js
@@ -2,6 +2,10 @@ import React, { PropTypes } from 'react';
import CodeMirror from 'codemirror';
import 'codemirror/mode/javascript/javascript';
import 'codemirror/addon/selection/active-line';
+import 'codemirror/addon/lint/lint';
+import 'codemirror/addon/lint/javascript-lint';
+import { JSHINT } from 'jshint';
+window.JSHINT = JSHINT;
class Editor extends React.Component {
@@ -12,7 +16,9 @@ class Editor extends React.Component {
lineNumbers: true,
styleActiveLine: true,
mode: 'javascript',
- lineWrapping: true
+ lineWrapping: true,
+ gutters: ['CodeMirror-lint-markers'],
+ lint: true
});
this._cm.on('change', () => { // eslint-disable-line
// this.props.updateFileContent('sketch.js', this._cm.getValue());
diff --git a/client/styles/main.scss b/client/styles/main.scss
index c16cff9d..4a20ba94 100644
--- a/client/styles/main.scss
+++ b/client/styles/main.scss
@@ -5,6 +5,7 @@
@import 'base/base';
@import 'vendors/codemirror';
+@import 'vendors/lint';
@import 'components/p5-widget-codemirror-theme';
@import 'components/editor';
diff --git a/client/styles/vendors/_lint.scss b/client/styles/vendors/_lint.scss
new file mode 100644
index 00000000..414a9a0e
--- /dev/null
+++ b/client/styles/vendors/_lint.scss
@@ -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%;
+}
diff --git a/package.json b/package.json
index dca229de..1623aa62 100644
--- a/package.json
+++ b/package.json
@@ -70,6 +70,7 @@
"eslint-loader": "^1.3.0",
"express": "^4.13.4",
"express-session": "^1.13.0",
+ "jshint": "^2.9.2",
"moment": "^2.14.1",
"mongoose": "^4.4.16",
"passport": "^0.3.2",
From cbdf68cbd07f4b22d4748f8a39e99ef3e546507d Mon Sep 17 00:00:00 2001
From: catarak
Date: Tue, 12 Jul 2016 19:11:07 -0400
Subject: [PATCH 02/11] add styling for error/warning, not for tooltip
---
client/styles/components/_editor.scss | 29 +++++++++++++++++++
.../_p5-widget-codemirror-theme.scss | 1 +
2 files changed, 30 insertions(+)
diff --git a/client/styles/components/_editor.scss b/client/styles/components/_editor.scss
index 5209e517..d75fbfbc 100644
--- a/client/styles/components/_editor.scss
+++ b/client/styles/components/_editor.scss
@@ -19,3 +19,32 @@
.CodeMirror-line {
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-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);
+}
diff --git a/client/styles/components/_p5-widget-codemirror-theme.scss b/client/styles/components/_p5-widget-codemirror-theme.scss
index 1c7fab3e..fe3be0b1 100644
--- a/client/styles/components/_p5-widget-codemirror-theme.scss
+++ b/client/styles/components/_p5-widget-codemirror-theme.scss
@@ -27,6 +27,7 @@
.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-gutter { background-color: #e8f2ff; }
.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. */
From 30992ac2de854de698a68ec2f7ba93b9cb9ccaa0 Mon Sep 17 00:00:00 2001
From: catarak
Date: Tue, 12 Jul 2016 19:24:57 -0400
Subject: [PATCH 03/11] fix tooltip styling
---
client/styles/components/_editor.scss | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/client/styles/components/_editor.scss b/client/styles/components/_editor.scss
index d75fbfbc..0964019c 100644
--- a/client/styles/components/_editor.scss
+++ b/client/styles/components/_editor.scss
@@ -26,7 +26,6 @@
bottom: 0;
}
-
.CodeMirror-lint-marker-warning, .CodeMirror-lint-marker-error, .CodeMirror-lint-marker-multiple {
background-image: none;
width: 70px;
@@ -34,6 +33,11 @@
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);
}
@@ -48,3 +52,11 @@
height: 100%;
// background-color: rgb(255, 95, 82);
}
+
+.CodeMirror-lint-tooltip {
+ font-family: Montserrat, sans-serif;
+ border-radius: 2px;
+ border: 1px solid #B9D0E1;
+ background-color: $light-button-background-color;
+
+}
From 70588cd4876d1868172402f2baf88b35d52c4ca0 Mon Sep 17 00:00:00 2001
From: catarak
Date: Wed, 13 Jul 2016 16:13:28 -0400
Subject: [PATCH 04/11] add new file popup, not tested with redux
---
client/constants.js | 3 +
client/modules/IDE/actions/files.js | 8 +++
client/modules/IDE/actions/ide.js | 12 ++++
client/modules/IDE/components/NewFileForm.js | 28 ++++++++
client/modules/IDE/components/NewFileModal.js | 68 +++++++++++++++++++
client/modules/IDE/components/Sidebar.js | 9 +++
client/modules/IDE/pages/IDEView.js | 13 +++-
client/modules/IDE/reducers/files.js | 2 +
client/modules/IDE/reducers/ide.js | 7 +-
client/styles/abstracts/_variables.scss | 1 +
client/styles/base/_base.scss | 3 +-
client/styles/components/_editor.scss | 2 +-
client/styles/components/_modal.scss | 36 ++++++++++
client/styles/components/_sidebar.scss | 17 +++++
client/styles/main.scss | 1 +
15 files changed, 205 insertions(+), 5 deletions(-)
create mode 100644 client/modules/IDE/components/NewFileForm.js
create mode 100644 client/modules/IDE/components/NewFileModal.js
create mode 100644 client/styles/components/_modal.scss
diff --git a/client/constants.js b/client/constants.js
index 967df39b..6f5833fd 100644
--- a/client/constants.js
+++ b/client/constants.js
@@ -30,6 +30,9 @@ export const SET_PROJECT = 'SET_PROJECT';
export const SET_PROJECTS = 'SET_PROJECTS';
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';
// eventually, handle errors more specifically and better
export const ERROR = 'ERROR';
diff --git a/client/modules/IDE/actions/files.js b/client/modules/IDE/actions/files.js
index f7ebd8f3..8bc0c6c3 100644
--- a/client/modules/IDE/actions/files.js
+++ b/client/modules/IDE/actions/files.js
@@ -7,3 +7,11 @@ export function updateFileContent(name, content) {
content
};
}
+
+// TODO make req to server
+export function createFile(name) {
+ return {
+ type: ActionTypes.CREATE_FILE,
+ name
+ };
+}
diff --git a/client/modules/IDE/actions/ide.js b/client/modules/IDE/actions/ide.js
index 0bcbc414..70363669 100644
--- a/client/modules/IDE/actions/ide.js
+++ b/client/modules/IDE/actions/ide.js
@@ -24,3 +24,15 @@ export function setSelectedFile(fileId) {
selectedFile: fileId
};
}
+
+export function newFile() {
+ return {
+ type: ActionTypes.SHOW_MODAL
+ };
+}
+
+export function closeNewFileModal() {
+ return {
+ type: ActionTypes.HIDE_MODAL
+ };
+}
diff --git a/client/modules/IDE/components/NewFileForm.js b/client/modules/IDE/components/NewFileForm.js
new file mode 100644
index 00000000..c46ca9e5
--- /dev/null
+++ b/client/modules/IDE/components/NewFileForm.js
@@ -0,0 +1,28 @@
+import React, { PropTypes } from 'react';
+
+function NewFileForm(props) {
+ const { fields: { name }, handleSubmit } = props;
+ return (
+
+ );
+}
+
+NewFileForm.propTypes = {
+ fields: PropTypes.shape({
+ name: PropTypes.string.isRequired
+ }).isRequired,
+ handleSubmit: PropTypes.func.isRequired,
+ createFile: PropTypes.func.isRequired
+};
+
+export default NewFileForm;
diff --git a/client/modules/IDE/components/NewFileModal.js b/client/modules/IDE/components/NewFileModal.js
new file mode 100644
index 00000000..1072348b
--- /dev/null
+++ b/client/modules/IDE/components/NewFileModal.js
@@ -0,0 +1,68 @@
+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 (
+
+ );
+}
+
+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';
+ }
+
+ // 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);
diff --git a/client/modules/IDE/components/Sidebar.js b/client/modules/IDE/components/Sidebar.js
index cb8c276f..ffeea432 100644
--- a/client/modules/IDE/components/Sidebar.js
+++ b/client/modules/IDE/components/Sidebar.js
@@ -4,6 +4,15 @@ import classNames from 'classnames';
function Sidebar(props) {
return (
+
{props.files.map(file => {
let itemClass = classNames({
diff --git a/client/modules/IDE/pages/IDEView.js b/client/modules/IDE/pages/IDEView.js
index c7308fa9..edc9043b 100644
--- a/client/modules/IDE/pages/IDEView.js
+++ b/client/modules/IDE/pages/IDEView.js
@@ -4,6 +4,7 @@ import Sidebar from '../components/Sidebar';
import PreviewFrame from '../components/PreviewFrame';
import Toolbar from '../components/Toolbar';
import Preferences from '../components/Preferences';
+import NewFileModal from '../components/NewFileModal';
import Nav from '../../../components/Nav';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
@@ -58,6 +59,7 @@ class IDEView extends React.Component {
files={this.props.files}
selectedFile={this.props.selectedFile}
setSelectedFile={this.props.setSelectedFile}
+ newFile={this.props.newFile}
/>
+
);
}
@@ -92,7 +98,8 @@ IDEView.propTypes = {
createProject: PropTypes.func.isRequired,
saveProject: PropTypes.func.isRequired,
ide: PropTypes.shape({
- isPlaying: PropTypes.bool.isRequired
+ isPlaying: PropTypes.bool.isRequired,
+ modalIsVisible: PropTypes.bool.isRequired
}).isRequired,
startSketch: PropTypes.func.isRequired,
stopSketch: PropTypes.func.isRequired,
@@ -125,7 +132,9 @@ IDEView.propTypes = {
setSelectedFile: PropTypes.func.isRequired,
htmlFile: PropTypes.object.isRequired,
jsFiles: PropTypes.array.isRequired,
- cssFiles: PropTypes.array.isRequired
+ cssFiles: PropTypes.array.isRequired,
+ newFile: PropTypes.func.isRequired,
+ closeNewFileModal: PropTypes.func.isRequired
};
function mapStateToProps(state) {
diff --git a/client/modules/IDE/reducers/files.js b/client/modules/IDE/reducers/files.js
index db53dbc3..6bc35e1b 100644
--- a/client/modules/IDE/reducers/files.js
+++ b/client/modules/IDE/reducers/files.js
@@ -64,6 +64,8 @@ const files = (state = initialState, action) => {
return [...action.files];
case ActionTypes.SET_PROJECT:
return [...action.files];
+ case ActionTypes.NEW_FILE:
+ return [{ name: action.name, content: '' }, ...state.files];
default:
return state;
}
diff --git a/client/modules/IDE/reducers/ide.js b/client/modules/IDE/reducers/ide.js
index 9c4c5b88..8b283ca0 100644
--- a/client/modules/IDE/reducers/ide.js
+++ b/client/modules/IDE/reducers/ide.js
@@ -2,7 +2,8 @@ import * as ActionTypes from '../../../constants';
const initialState = {
isPlaying: false,
- selectedFile: '1'
+ selectedFile: '1',
+ modalIsVisible: false
};
const ide = (state = initialState, action) => {
@@ -17,6 +18,10 @@ const ide = (state = initialState, action) => {
case ActionTypes.SET_PROJECT:
case ActionTypes.NEW_PROJECT:
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 });
default:
return state;
}
diff --git a/client/styles/abstracts/_variables.scss b/client/styles/abstracts/_variables.scss
index a41a2097..aea3f2fb 100644
--- a/client/styles/abstracts/_variables.scss
+++ b/client/styles/abstracts/_variables.scss
@@ -19,6 +19,7 @@ $light-button-background-active-color: #f10046;
$light-button-hover-color: $white;
$light-button-active-color: $white;
$light-modal-button-background-color: #e6e6e6;
+$light-modal-border-color: #B9D0E1;
$light-icon-color: #8b8b8b;
$light-icon-hover-color: $light-primary-text-color;
diff --git a/client/styles/base/_base.scss b/client/styles/base/_base.scss
index fda0b662..4f5358bb 100644
--- a/client/styles/base/_base.scss
+++ b/client/styles/base/_base.scss
@@ -31,8 +31,9 @@ input, button {
input {
padding: #{5 / $base-font-size}rem;
- border-radius: 2px;
+ // border-radius: 2px;
border: 1px solid $input-border-color;
+ padding: #{10 / $base-font-size}rem;
}
input[type="submit"] {
diff --git a/client/styles/components/_editor.scss b/client/styles/components/_editor.scss
index 0964019c..c1efcc43 100644
--- a/client/styles/components/_editor.scss
+++ b/client/styles/components/_editor.scss
@@ -56,7 +56,7 @@
.CodeMirror-lint-tooltip {
font-family: Montserrat, sans-serif;
border-radius: 2px;
- border: 1px solid #B9D0E1;
+ border: 1px solid $light-modal-border-color;
background-color: $light-button-background-color;
}
diff --git a/client/styles/components/_modal.scss b/client/styles/components/_modal.scss
new file mode 100644
index 00000000..22745606
--- /dev/null
+++ b/client/styles/components/_modal.scss
@@ -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;
+}
\ No newline at end of file
diff --git a/client/styles/components/_sidebar.scss b/client/styles/components/_sidebar.scss
index 38883386..b5f3d8e2 100644
--- a/client/styles/components/_sidebar.scss
+++ b/client/styles/components/_sidebar.scss
@@ -1,8 +1,25 @@
+.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;
+}
+
+.sidebar__title {
+ font-size: #{12 / $base-font-size}rem;
+ display: inline-block;
+}
+
+.sidebar__add {
+ cursor: pointer;
+}
+
.sidebar__file-list {
border-top: 1px solid $ide-border-color;
}
.sidebar__file-item {
+ font-size: #{12 / $base-font-size}rem;
padding: #{8 / $base-font-size}rem #{20 / $base-font-size}rem;
color: $light-inactive-text-color;
cursor: pointer;
diff --git a/client/styles/main.scss b/client/styles/main.scss
index 4a20ba94..64dc92ab 100644
--- a/client/styles/main.scss
+++ b/client/styles/main.scss
@@ -16,6 +16,7 @@
@import 'components/login';
@import 'components/sketch-list';
@import 'components/sidebar';
+@import 'components/modal';
@import 'layout/ide';
@import 'layout/sketch-list';
From 4d6e4857ba427d3256b55f22c1a652147c6989f5 Mon Sep 17 00:00:00 2001
From: catarak
Date: Wed, 13 Jul 2016 18:53:56 -0400
Subject: [PATCH 05/11] add files, server side, only css and js files
---
client/modules/IDE/actions/files.js | 41 +++++++++++++++++--
client/modules/IDE/components/NewFileModal.js | 6 +--
client/modules/IDE/reducers/files.js | 4 +-
client/styles/components/_modal.scss | 2 +-
server/controllers/file.controller.js | 19 +++++++++
server/routes/file.routes.js | 8 ++++
server/server.js | 2 +
7 files changed, 71 insertions(+), 11 deletions(-)
create mode 100644 server/controllers/file.controller.js
create mode 100644 server/routes/file.routes.js
diff --git a/client/modules/IDE/actions/files.js b/client/modules/IDE/actions/files.js
index 8bc0c6c3..1eead2ae 100644
--- a/client/modules/IDE/actions/files.js
+++ b/client/modules/IDE/actions/files.js
@@ -1,4 +1,7 @@
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) {
return {
@@ -9,9 +12,39 @@ export function updateFileContent(name, content) {
}
// TODO make req to server
-export function createFile(name) {
- return {
- type: ActionTypes.CREATE_FILE,
- name
+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
+ });
+ }
};
}
diff --git a/client/modules/IDE/components/NewFileModal.js b/client/modules/IDE/components/NewFileModal.js
index 1072348b..f24ae645 100644
--- a/client/modules/IDE/components/NewFileModal.js
+++ b/client/modules/IDE/components/NewFileModal.js
@@ -51,12 +51,10 @@ function validate(formProps) {
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.';
}
- // if (formProps.name.match(/(.+\.js$|.+\.css$)/)) {
- // errors.name = 'File must be of type JavaScript or CSS.';
- // }
-
return errors;
}
diff --git a/client/modules/IDE/reducers/files.js b/client/modules/IDE/reducers/files.js
index 6bc35e1b..2b32bc04 100644
--- a/client/modules/IDE/reducers/files.js
+++ b/client/modules/IDE/reducers/files.js
@@ -64,8 +64,8 @@ const files = (state = initialState, action) => {
return [...action.files];
case ActionTypes.SET_PROJECT:
return [...action.files];
- case ActionTypes.NEW_FILE:
- return [{ name: action.name, content: '' }, ...state.files];
+ case ActionTypes.CREATE_FILE:
+ return [...state, { name: action.name, id: action.id, content: '' }];
default:
return state;
}
diff --git a/client/styles/components/_modal.scss b/client/styles/components/_modal.scss
index 22745606..11f79db3 100644
--- a/client/styles/components/_modal.scss
+++ b/client/styles/components/_modal.scss
@@ -1,7 +1,7 @@
.modal {
position: absolute;
top: #{66 / $base-font-size}rem;
- right: #{400 / $base-font-size}rem;;
+ right: #{400 / $base-font-size}rem;
z-index: 100;
}
diff --git a/server/controllers/file.controller.js b/server/controllers/file.controller.js
new file mode 100644
index 00000000..73058b07
--- /dev/null
+++ b/server/controllers/file.controller.js
@@ -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]);
+ });
+}
\ No newline at end of file
diff --git a/server/routes/file.routes.js b/server/routes/file.routes.js
new file mode 100644
index 00000000..c7035ac6
--- /dev/null
+++ b/server/routes/file.routes.js
@@ -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;
\ No newline at end of file
diff --git a/server/server.js b/server/server.js
index 99879244..3d59d285 100644
--- a/server/server.js
+++ b/server/server.js
@@ -27,6 +27,7 @@ import serverConfig from './config';
import users from './routes/user.routes';
import sessions from './routes/session.routes';
import projects from './routes/project.routes';
+import files from './routes/file.routes';
import serverRoutes from './routes/server.routes';
// Body parser, cookie parser, sessions, serve public assets
@@ -55,6 +56,7 @@ app.use(passport.session());
app.use('/api', users);
app.use('/api', sessions);
app.use('/api', projects);
+app.use('/api', files);
// this is supposed to be TEMPORARY -- until i figure out
// isomorphic rendering
app.use('/', serverRoutes);
From c29f5aee6880236430e4eaab65791697cc12b3a2 Mon Sep 17 00:00:00 2001
From: catarak
Date: Wed, 13 Jul 2016 19:52:50 -0400
Subject: [PATCH 06/11] add syntax highlighting for different file types, and
linting for html and css
---
client/modules/IDE/components/Editor.js | 17 +++++++++++++++++
package.json | 2 ++
2 files changed, 19 insertions(+)
diff --git a/client/modules/IDE/components/Editor.js b/client/modules/IDE/components/Editor.js
index 9be67dff..ccb6d52e 100644
--- a/client/modules/IDE/components/Editor.js
+++ b/client/modules/IDE/components/Editor.js
@@ -1,11 +1,19 @@
import React, { PropTypes } from 'react';
import CodeMirror from 'codemirror';
import 'codemirror/mode/javascript/javascript';
+import 'codemirror/mode/css/css';
+import 'codemirror/mode/htmlmixed/htmlmixed';
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;
class Editor extends React.Component {
@@ -43,6 +51,15 @@ class Editor extends React.Component {
if (this.props.isTabIndent !== prevProps.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() {
diff --git a/package.json b/package.json
index 1623aa62..23b2dd8d 100644
--- a/package.json
+++ b/package.json
@@ -65,11 +65,13 @@
"codemirror": "^5.14.2",
"connect-mongo": "^1.2.0",
"cookie-parser": "^1.4.1",
+ "csslint": "^0.10.0",
"dotenv": "^2.0.0",
"escape-string-regexp": "^1.0.5",
"eslint-loader": "^1.3.0",
"express": "^4.13.4",
"express-session": "^1.13.0",
+ "htmlhint": "^0.9.13",
"jshint": "^2.9.2",
"moment": "^2.14.1",
"mongoose": "^4.4.16",
From d9a11cea4c09e1aab498c88d22a39a6cfe1dbad2 Mon Sep 17 00:00:00 2001
From: catarak
Date: Wed, 13 Jul 2016 21:50:59 -0400
Subject: [PATCH 07/11] add debounce to editor input
---
client/modules/IDE/components/Editor.js | 12 +++++++++---
package.json | 3 ++-
2 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/client/modules/IDE/components/Editor.js b/client/modules/IDE/components/Editor.js
index ccb6d52e..1e79bd21 100644
--- a/client/modules/IDE/components/Editor.js
+++ b/client/modules/IDE/components/Editor.js
@@ -15,6 +15,8 @@ window.CSSLint = CSSLint;
import { HTMLHint } from 'htmlhint';
window.HTMLHint = HTMLHint;
+import { debounce } from 'throttle-debounce';
+
class Editor extends React.Component {
componentDidMount() {
@@ -28,10 +30,14 @@ class Editor extends React.Component {
gutters: ['CodeMirror-lint-markers'],
lint: true
});
- this._cm.on('change', () => { // eslint-disable-line
- // this.props.updateFileContent('sketch.js', this._cm.getValue());
+ this._cm.on('change', debounce(200, () => {
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.setOption('indentWithTabs', this.props.isTabIndent);
this._cm.setOption('tabSize', this.props.indentationAmount);
diff --git a/package.json b/package.json
index 23b2dd8d..d55afffc 100644
--- a/package.json
+++ b/package.json
@@ -87,6 +87,7 @@
"redux-form": "^5.2.5",
"redux-thunk": "^2.1.0",
"shortid": "^2.2.6",
- "srcdoc-polyfill": "^0.2.0"
+ "srcdoc-polyfill": "^0.2.0",
+ "throttle-debounce": "^1.0.1"
}
}
From accf8e25040425fb0ae412537b517bc17340cbd5 Mon Sep 17 00:00:00 2001
From: catarak
Date: Thu, 14 Jul 2016 12:47:54 -0400
Subject: [PATCH 08/11] add expand/contract sidebar
---
client/constants.js | 3 ++
client/images/left-arrow.svg | 12 ++++++
client/images/plus-icon.svg | 16 ++++++++
client/images/right-arrow.svg | 12 ++++++
client/modules/IDE/actions/ide.js | 12 ++++++
client/modules/IDE/components/Sidebar.js | 36 +++++++++++++----
client/modules/IDE/pages/IDEView.js | 10 ++++-
client/modules/IDE/reducers/ide.js | 7 +++-
client/styles/abstracts/_placeholders.scss | 1 +
client/styles/components/_sidebar.scss | 46 +++++++++++++++++++++-
client/styles/layout/_ide.scss | 5 ++-
11 files changed, 147 insertions(+), 13 deletions(-)
create mode 100644 client/images/left-arrow.svg
create mode 100644 client/images/plus-icon.svg
create mode 100644 client/images/right-arrow.svg
diff --git a/client/constants.js b/client/constants.js
index 6f5833fd..ed64f2b0 100644
--- a/client/constants.js
+++ b/client/constants.js
@@ -34,5 +34,8 @@ 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
export const ERROR = 'ERROR';
diff --git a/client/images/left-arrow.svg b/client/images/left-arrow.svg
new file mode 100644
index 00000000..22f17dcb
--- /dev/null
+++ b/client/images/left-arrow.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/client/images/plus-icon.svg b/client/images/plus-icon.svg
new file mode 100644
index 00000000..0b9347f2
--- /dev/null
+++ b/client/images/plus-icon.svg
@@ -0,0 +1,16 @@
+
+
\ No newline at end of file
diff --git a/client/images/right-arrow.svg b/client/images/right-arrow.svg
new file mode 100644
index 00000000..6d62c369
--- /dev/null
+++ b/client/images/right-arrow.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/client/modules/IDE/actions/ide.js b/client/modules/IDE/actions/ide.js
index 70363669..a0b48434 100644
--- a/client/modules/IDE/actions/ide.js
+++ b/client/modules/IDE/actions/ide.js
@@ -36,3 +36,15 @@ export function closeNewFileModal() {
type: ActionTypes.HIDE_MODAL
};
}
+
+export function expandSidebar() {
+ return {
+ type: ActionTypes.EXPAND_SIDEBAR
+ };
+}
+
+export function collapseSidebar() {
+ return {
+ type: ActionTypes.COLLAPSE_SIDEBAR
+ };
+}
diff --git a/client/modules/IDE/components/Sidebar.js b/client/modules/IDE/components/Sidebar.js
index ffeea432..f1ccf179 100644
--- a/client/modules/IDE/components/Sidebar.js
+++ b/client/modules/IDE/components/Sidebar.js
@@ -1,17 +1,39 @@
import React, { PropTypes } from 'react';
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) {
+ const sidebarClass = classNames({
+ sidebar: true,
+ 'sidebar--contracted': !props.isExpanded
+ });
+
return (
-
+
{props.files.map(file => {
diff --git a/client/modules/IDE/pages/IDEView.js b/client/modules/IDE/pages/IDEView.js
index edc9043b..7a94a16d 100644
--- a/client/modules/IDE/pages/IDEView.js
+++ b/client/modules/IDE/pages/IDEView.js
@@ -60,6 +60,9 @@ class IDEView extends React.Component {
selectedFile={this.props.selectedFile}
setSelectedFile={this.props.setSelectedFile}
newFile={this.props.newFile}
+ isExpanded={this.props.ide.sidebarIsExpanded}
+ expandSidebar={this.props.expandSidebar}
+ collapseSidebar={this.props.collapseSidebar}
/>
{
@@ -22,6 +23,10 @@ const ide = (state = initialState, action) => {
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:
return state;
}
diff --git a/client/styles/abstracts/_placeholders.scss b/client/styles/abstracts/_placeholders.scss
index 4b89cf7c..f27caed9 100644
--- a/client/styles/abstracts/_placeholders.scss
+++ b/client/styles/abstracts/_placeholders.scss
@@ -41,6 +41,7 @@
&:hover {
color: $light-icon-hover-color;
& g {
+ opacity: 1;
fill: $light-icon-hover-color;
}
}
diff --git a/client/styles/components/_sidebar.scss b/client/styles/components/_sidebar.scss
index b5f3d8e2..7919678f 100644
--- a/client/styles/components/_sidebar.scss
+++ b/client/styles/components/_sidebar.scss
@@ -3,23 +3,37 @@
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: #{12 / $base-font-size}rem;
+ 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 {
border-top: 1px solid $ide-border-color;
+ .sidebar--contracted & {
+ display: none;
+ }
}
.sidebar__file-item {
- font-size: #{12 / $base-font-size}rem;
+ font-size: #{16 / $base-font-size}rem;
padding: #{8 / $base-font-size}rem #{20 / $base-font-size}rem;
color: $light-inactive-text-color;
cursor: pointer;
@@ -27,3 +41,31 @@
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;
+}
diff --git a/client/styles/layout/_ide.scss b/client/styles/layout/_ide.scss
index 7f78e871..a37c636d 100644
--- a/client/styles/layout/_ide.scss
+++ b/client/styles/layout/_ide.scss
@@ -20,5 +20,8 @@
}
.sidebar {
- width: #{140 / $base-font-size}rem;
+ width: #{180 / $base-font-size}rem;
+ &.sidebar--contracted {
+ width: #{20 / $base-font-size}rem;
+ }
}
From c3486af0311c0b41fc9007b607ca175b356803f4 Mon Sep 17 00:00:00 2001
From: catarak
Date: Fri, 15 Jul 2016 11:54:47 -0400
Subject: [PATCH 09/11] add author name to sketches
---
client/modules/IDE/actions/project.js | 7 +++++-
client/modules/IDE/components/Toolbar.js | 10 ++++++++-
client/modules/IDE/pages/IDEView.js | 6 +++++-
client/modules/IDE/reducers/project.js | 10 ++++-----
client/styles/components/_toolbar.scss | 4 ++++
server/controllers/project.controller.js | 27 ++++++++++++++++--------
6 files changed, 47 insertions(+), 17 deletions(-)
diff --git a/client/modules/IDE/actions/project.js b/client/modules/IDE/actions/project.js
index 8f820c30..913d979e 100644
--- a/client/modules/IDE/actions/project.js
+++ b/client/modules/IDE/actions/project.js
@@ -8,12 +8,14 @@ export function getProject(id) {
return (dispatch) => {
axios.get(`${ROOT_URL}/projects/${id}`, { withCredentials: true })
.then(response => {
+ console.log(response.data);
browserHistory.push(`/projects/${id}`);
dispatch({
type: ActionTypes.SET_PROJECT,
project: response.data,
files: response.data.files,
- selectedFile: response.data.selectedFile
+ selectedFile: response.data.selectedFile,
+ owner: response.data.user
});
})
.catch(response => dispatch({
@@ -61,6 +63,7 @@ export function saveProject() {
type: ActionTypes.NEW_PROJECT,
name: response.data.name,
id: response.data.id,
+ owner: response.data.user,
selectedFile: response.data.selectedFile,
files: response.data.files
});
@@ -78,11 +81,13 @@ export function createProject() {
return (dispatch) => {
axios.post(`${ROOT_URL}/projects`, {}, { withCredentials: true })
.then(response => {
+ console.log(response.data);
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
});
diff --git a/client/modules/IDE/components/Toolbar.js b/client/modules/IDE/components/Toolbar.js
index fd8d3289..1d4a66c7 100644
--- a/client/modules/IDE/components/Toolbar.js
+++ b/client/modules/IDE/components/Toolbar.js
@@ -40,6 +40,13 @@ function Toolbar(props) {
>
{props.projectName}
+ {(() => { // eslint-disable-line
+ if (props.owner) {
+ return (
+ by {props.owner.username}
+ );
+ }
+ })()}
+
+
+ Export (zip)
+
+
+
+
+ Clone
+
+
-
@@ -42,6 +52,7 @@ function Nav(props) {
Nav.propTypes = {
createProject: PropTypes.func.isRequired,
saveProject: PropTypes.func.isRequired,
+ exportProjectAsZip: PropTypes.func.isRequired,
user: PropTypes.shape({
authenticated: PropTypes.bool.isRequired,
username: PropTypes.string
diff --git a/client/modules/IDE/actions/project.js b/client/modules/IDE/actions/project.js
index 913d979e..a26d2e2f 100644
--- a/client/modules/IDE/actions/project.js
+++ b/client/modules/IDE/actions/project.js
@@ -1,6 +1,8 @@
import * as ActionTypes from '../../../constants';
import { browserHistory } from 'react-router';
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';
@@ -98,3 +100,19 @@ 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`);
+ });
+ };
+}
+
diff --git a/client/modules/IDE/components/Toolbar.js b/client/modules/IDE/components/Toolbar.js
index 1d4a66c7..4d3d00bd 100644
--- a/client/modules/IDE/components/Toolbar.js
+++ b/client/modules/IDE/components/Toolbar.js
@@ -63,7 +63,9 @@ Toolbar.propTypes = {
setProjectName: PropTypes.func.isRequired,
projectName: PropTypes.string.isRequired,
openPreferences: PropTypes.func.isRequired,
- owner: PropTypes.string.isRequired
+ owner: PropTypes.shape({
+ username: PropTypes.string
+ })
};
export default Toolbar;
diff --git a/client/modules/IDE/pages/IDEView.js b/client/modules/IDE/pages/IDEView.js
index d186d375..ec3842a6 100644
--- a/client/modules/IDE/pages/IDEView.js
+++ b/client/modules/IDE/pages/IDEView.js
@@ -29,6 +29,7 @@ class IDEView extends React.Component {
user={this.props.user}
createProject={this.props.createProject}
saveProject={this.props.saveProject}
+ exportProjectAsZip={this.props.exportProjectAsZip}
/>
Date: Fri, 15 Jul 2016 13:36:33 -0400
Subject: [PATCH 11/11] add cloning of projects
---
client/components/Nav.js | 3 ++-
client/modules/IDE/actions/project.js | 24 +++++++++++++++++++++++-
client/modules/IDE/pages/IDEView.js | 4 +++-
server/controllers/project.controller.js | 4 ++--
4 files changed, 30 insertions(+), 5 deletions(-)
diff --git a/client/components/Nav.js b/client/components/Nav.js
index 26068f49..a9b015df 100644
--- a/client/components/Nav.js
+++ b/client/components/Nav.js
@@ -33,7 +33,7 @@ function Nav(props) {
Export (zip)
- -
+
-
Clone
@@ -53,6 +53,7 @@ Nav.propTypes = {
createProject: PropTypes.func.isRequired,
saveProject: PropTypes.func.isRequired,
exportProjectAsZip: PropTypes.func.isRequired,
+ cloneProject: PropTypes.func.isRequired,
user: PropTypes.shape({
authenticated: PropTypes.bool.isRequired,
username: PropTypes.string
diff --git a/client/modules/IDE/actions/project.js b/client/modules/IDE/actions/project.js
index a26d2e2f..804963b8 100644
--- a/client/modules/IDE/actions/project.js
+++ b/client/modules/IDE/actions/project.js
@@ -83,7 +83,6 @@ export function createProject() {
return (dispatch) => {
axios.post(`${ROOT_URL}/projects`, {}, { withCredentials: true })
.then(response => {
- console.log(response.data);
browserHistory.push(`/projects/${response.data.id}`);
dispatch({
type: ActionTypes.NEW_PROJECT,
@@ -116,3 +115,26 @@ export function exportProjectAsZip() {
};
}
+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
+ }));
+ };
+}
+
diff --git a/client/modules/IDE/pages/IDEView.js b/client/modules/IDE/pages/IDEView.js
index ec3842a6..6987b726 100644
--- a/client/modules/IDE/pages/IDEView.js
+++ b/client/modules/IDE/pages/IDEView.js
@@ -30,6 +30,7 @@ class IDEView extends React.Component {
createProject={this.props.createProject}
saveProject={this.props.saveProject}
exportProjectAsZip={this.props.exportProjectAsZip}
+ cloneProject={this.props.cloneProject}
/>
{
if (err) { return res.json({ success: false }); }