diff --git a/client/constants.js b/client/constants.js
index b69e22b2..967df39b 100644
--- a/client/constants.js
+++ b/client/constants.js
@@ -8,6 +8,12 @@ export const OPEN_PREFERENCES = 'OPEN_PREFERENCES';
export const CLOSE_PREFERENCES = 'CLOSE_PREFERENCES';
export const INCREASE_FONTSIZE = 'INCREASE_FONTSIZE';
export const DECREASE_FONTSIZE = 'DECREASE_FONTSIZE';
+export const UPDATE_FONTSIZE = 'UPDATE_FONTSIZE';
+export const INCREASE_INDENTATION = 'INCREASE_INDENTATION';
+export const DECREASE_INDENTATION = 'DECREASE_INDENTATION';
+export const UPDATE_INDENTATION = 'UPDATE_INDENTATION';
+export const INDENT_WITH_SPACE = 'INDENT_WITH_SPACE';
+export const INDENT_WITH_TAB = 'INDENT_WITH_TAB';
export const AUTH_USER = 'AUTH_USER';
export const UNAUTH_USER = 'UNAUTH_USER';
@@ -23,5 +29,7 @@ export const NEW_PROJECT = 'NEW_PROJECT';
export const SET_PROJECT = 'SET_PROJECT';
export const SET_PROJECTS = 'SET_PROJECTS';
+export const SET_SELECTED_FILE = 'SET_SELECTED_FILE';
+
// eventually, handle errors more specifically and better
export const ERROR = 'ERROR';
diff --git a/client/modules/IDE/actions/ide.js b/client/modules/IDE/actions/ide.js
index 1d40ed12..0bcbc414 100644
--- a/client/modules/IDE/actions/ide.js
+++ b/client/modules/IDE/actions/ide.js
@@ -17,3 +17,10 @@ export function stopSketch() {
type: ActionTypes.STOP_SKETCH
};
}
+
+export function setSelectedFile(fileId) {
+ return {
+ type: ActionTypes.SET_SELECTED_FILE,
+ selectedFile: fileId
+ };
+}
diff --git a/client/modules/IDE/actions/preferences.js b/client/modules/IDE/actions/preferences.js
index 2cfafcf4..4a3d0c9c 100644
--- a/client/modules/IDE/actions/preferences.js
+++ b/client/modules/IDE/actions/preferences.js
@@ -23,3 +23,43 @@ export function decreaseFont() {
type: ActionTypes.DECREASE_FONTSIZE
};
}
+
+export function updateFont(event) {
+ const value = event.target.value;
+ return {
+ type: ActionTypes.UPDATE_FONTSIZE,
+ value
+ };
+}
+
+export function increaseIndentation() {
+ return {
+ type: ActionTypes.INCREASE_INDENTATION
+ };
+}
+
+export function decreaseIndentation() {
+ return {
+ type: ActionTypes.DECREASE_INDENTATION
+ };
+}
+
+export function updateIndentation() {
+ const value = event.target.value;
+ return {
+ type: ActionTypes.UPDATE_INDENTATION,
+ value
+ };
+}
+
+export function indentWithTab() {
+ return {
+ type: ActionTypes.INDENT_WITH_TAB
+ };
+}
+
+export function indentWithSpace() {
+ return {
+ type: ActionTypes.INDENT_WITH_SPACE
+ };
+}
diff --git a/client/modules/IDE/actions/project.js b/client/modules/IDE/actions/project.js
index 8d4e1e56..8f820c30 100644
--- a/client/modules/IDE/actions/project.js
+++ b/client/modules/IDE/actions/project.js
@@ -12,7 +12,8 @@ export function getProject(id) {
dispatch({
type: ActionTypes.SET_PROJECT,
project: response.data,
- files: response.data.files
+ files: response.data.files,
+ selectedFile: response.data.selectedFile
});
})
.catch(response => dispatch({
@@ -34,7 +35,7 @@ export function saveProject() {
return (dispatch, getState) => {
const state = getState();
const formParams = Object.assign({}, state.project);
- formParams.files = state.files;
+ formParams.files = [...state.files];
if (state.project.id) {
axios.put(`${ROOT_URL}/projects/${state.project.id}`, formParams, { withCredentials: true })
.then(() => {
@@ -47,6 +48,12 @@ export function saveProject() {
error: response.data
}));
} else {
+ // this might be unnecessary, but to prevent collisions in mongodb
+ formParams.files.map(file => {
+ const newFile = Object.assign({}, file);
+ delete newFile.id;
+ return newFile;
+ });
axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true })
.then(response => {
browserHistory.push(`/projects/${response.data.id}`);
@@ -54,6 +61,7 @@ export function saveProject() {
type: ActionTypes.NEW_PROJECT,
name: response.data.name,
id: response.data.id,
+ selectedFile: response.data.selectedFile,
files: response.data.files
});
})
@@ -75,6 +83,7 @@ export function createProject() {
type: ActionTypes.NEW_PROJECT,
name: response.data.name,
id: response.data.id,
+ selectedFile: response.data.selectedFile,
files: response.data.files
});
})
diff --git a/client/modules/IDE/components/Editor.js b/client/modules/IDE/components/Editor.js
index b028d705..fa4fe305 100644
--- a/client/modules/IDE/components/Editor.js
+++ b/client/modules/IDE/components/Editor.js
@@ -8,25 +8,34 @@ class Editor extends React.Component {
componentDidMount() {
this._cm = CodeMirror(this.refs.container, { // eslint-disable-line
theme: 'p5-widget',
- value: this.props.content,
+ value: this.props.file.content,
lineNumbers: true,
styleActiveLine: true,
mode: 'javascript'
});
this._cm.on('change', () => { // eslint-disable-line
- this.props.updateFileContent('sketch.js', this._cm.getValue());
+ // this.props.updateFileContent('sketch.js', this._cm.getValue());
+ this.props.updateFileContent(this.props.file.name, this._cm.getValue());
});
this._cm.getWrapperElement().style['font-size'] = `${this.props.fontSize}px`;
+ this._cm.setOption('indentWithTabs', this.props.isTabIndent);
+ this._cm.setOption('tabSize', this.props.indentationAmount);
}
componentDidUpdate(prevProps) {
- if (this.props.content !== prevProps.content &&
- this.props.content !== this._cm.getValue()) {
- this._cm.setValue(this.props.content); // eslint-disable-line no-underscore-dangle
+ if (this.props.file.content !== prevProps.file.content &&
+ this.props.file.content !== this._cm.getValue()) {
+ this._cm.setValue(this.props.file.content); // eslint-disable-line no-underscore-dangle
}
if (this.props.fontSize !== prevProps.fontSize) {
this._cm.getWrapperElement().style['font-size'] = `${this.props.fontSize}px`;
}
+ if (this.props.indentationAmount !== prevProps.indentationAmount) {
+ this._cm.setOption('tabSize', this.props.indentationAmount);
+ }
+ if (this.props.isTabIndent !== prevProps.isTabIndent) {
+ this._cm.setOption('indentWithTabs', this.props.isTabIndent);
+ }
}
componentWillUnmount() {
@@ -41,9 +50,14 @@ class Editor extends React.Component {
}
Editor.propTypes = {
- content: PropTypes.string.isRequired,
+ indentationAmount: PropTypes.number.isRequired,
+ isTabIndent: PropTypes.bool.isRequired,
updateFileContent: PropTypes.func.isRequired,
- fontSize: PropTypes.number.isRequired
+ fontSize: PropTypes.number.isRequired,
+ file: PropTypes.shape({
+ name: PropTypes.string.isRequired,
+ content: PropTypes.string.isRequired
+ })
};
export default Editor;
diff --git a/client/modules/IDE/components/Preferences.js b/client/modules/IDE/components/Preferences.js
index fb4bdffd..961f4101 100644
--- a/client/modules/IDE/components/Preferences.js
+++ b/client/modules/IDE/components/Preferences.js
@@ -11,6 +11,14 @@ function Preferences(props) {
preferences: true,
'preferences--selected': props.isVisible
});
+ let preferencesTabOptionClass = classNames({
+ preference__option: true,
+ 'preference__option--selected': props.isTabIndent
+ });
+ let preferencesSpaceOptionClass = classNames({
+ preference__option: true,
+ 'preference__option--selected': !props.isTabIndent
+ });
return (
@@ -19,16 +27,37 @@ function Preferences(props) {
+
-
Text Size
+
Text Size
-
{props.fontSize}
+
+
+
+
+
Indentation Amount
+
+
+
+
+
+
+
+
);
}
@@ -37,8 +66,16 @@ Preferences.propTypes = {
isVisible: PropTypes.bool.isRequired,
closePreferences: PropTypes.func.isRequired,
decreaseFont: PropTypes.func.isRequired,
+ updateFont: PropTypes.func.isRequired,
fontSize: PropTypes.number.isRequired,
- increaseFont: PropTypes.func.isRequired
+ increaseFont: PropTypes.func.isRequired,
+ indentationAmount: PropTypes.number.isRequired,
+ decreaseIndentation: PropTypes.func.isRequired,
+ increaseIndentation: PropTypes.func.isRequired,
+ updateIndentation: PropTypes.func.isRequired,
+ indentWithSpace: PropTypes.func.isRequired,
+ indentWithTab: PropTypes.func.isRequired,
+ isTabIndent: PropTypes.bool.isRequired
};
export default Preferences;
diff --git a/client/modules/IDE/components/PreviewFrame.js b/client/modules/IDE/components/PreviewFrame.js
index 08b916c0..b0313441 100644
--- a/client/modules/IDE/components/PreviewFrame.js
+++ b/client/modules/IDE/components/PreviewFrame.js
@@ -1,5 +1,7 @@
import React, { PropTypes } from 'react';
import ReactDOM from 'react-dom';
+import escapeStringRegexp from 'escape-string-regexp';
+import srcDoc from 'srcdoc-polyfill';
class PreviewFrame extends React.Component {
@@ -13,11 +15,7 @@ class PreviewFrame extends React.Component {
componentDidUpdate(prevProps) {
if (this.props.isPlaying !== prevProps.isPlaying) {
- if (this.props.isPlaying) {
- this.renderSketch();
- } else {
- this.clearPreview();
- }
+ this.renderSketch();
}
if (this.props.isPlaying && this.props.content !== prevProps.content) {
@@ -30,9 +28,33 @@ class PreviewFrame extends React.Component {
}
clearPreview() {
- const doc = ReactDOM.findDOMNode(this).contentDocument;
- doc.write('');
- doc.close();
+ const doc = ReactDOM.findDOMNode(this);
+ doc.srcDoc = '';
+ }
+
+ injectLocalFiles() {
+ let htmlFile = this.props.htmlFile.content;
+
+ this.props.jsFiles.forEach(jsFile => {
+ const fileName = escapeStringRegexp(jsFile.name);
+ const fileRegex = new RegExp(`([\s\S]*?)<\/script>`, 'gmi');
+ htmlFile = htmlFile.replace(fileRegex, ``);
+ });
+
+ this.props.cssFiles.forEach(cssFile => {
+ const fileName = escapeStringRegexp(cssFile.name);
+ const fileRegex = new RegExp(``, 'gmi');
+ htmlFile = htmlFile.replace(fileRegex, ``);
+ });
+
+ // const htmlHead = htmlFile.match(/(?:)([\s\S]*?)(?:<\/head>)/gmi);
+ // const headRegex = new RegExp('head', 'i');
+ // let htmlHeadContents = htmlHead[0].split(headRegex)[1];
+ // htmlHeadContents = htmlHeadContents.slice(1, htmlHeadContents.length - 2);
+ // htmlHeadContents += '\n';
+ // htmlFile = htmlFile.replace(/(?:)([\s\S]*?)(?:<\/head>)/gmi, `\n${htmlHeadContents}\n`);
+
+ return htmlFile;
}
hijackConsole() {
@@ -55,16 +77,18 @@ class PreviewFrame extends React.Component {
}
renderSketch() {
- const doc = ReactDOM.findDOMNode(this).contentDocument;
- this.clearPreview();
- ReactDOM.render(this.props.head, doc.head);
- const p5Script = doc.createElement('script');
- p5Script.setAttribute('src', 'https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.0/p5.min.js');
- doc.body.appendChild(p5Script);
-
- const sketchScript = doc.createElement('script');
- sketchScript.textContent = this.props.content;
- doc.body.appendChild(sketchScript);
+ const doc = ReactDOM.findDOMNode(this);
+ if (this.props.isPlaying) {
+ // TODO add polyfill for this
+ // doc.srcdoc = this.injectLocalFiles();
+ srcDoc.set(doc, this.injectLocalFiles());
+ } else {
+ // doc.srcdoc = '';
+ srcDoc.set(doc, '');
+ doc.contentWindow.document.open();
+ doc.contentWindow.document.write('');
+ doc.contentWindow.document.close();
+ }
}
renderFrameContents() {
@@ -81,8 +105,8 @@ class PreviewFrame extends React.Component {
);
}
@@ -91,7 +115,12 @@ class PreviewFrame extends React.Component {
PreviewFrame.propTypes = {
isPlaying: PropTypes.bool.isRequired,
head: PropTypes.object.isRequired,
- content: PropTypes.string.isRequired
+ content: PropTypes.string.isRequired,
+ htmlFile: PropTypes.shape({
+ content: PropTypes.string.isRequired
+ }),
+ jsFiles: PropTypes.array.isRequired,
+ cssFiles: PropTypes.array.isRequired
};
export default PreviewFrame;
diff --git a/client/modules/IDE/components/Sidebar.js b/client/modules/IDE/components/Sidebar.js
index 3c5d0fe6..cd75d0a7 100644
--- a/client/modules/IDE/components/Sidebar.js
+++ b/client/modules/IDE/components/Sidebar.js
@@ -1,22 +1,34 @@
import React, { PropTypes } from 'react';
+import classNames from 'classnames';
function Sidebar(props) {
return (
- {props.files.map(file =>
- - {file.name}
- )}
+ {props.files.map(file => {
+ let itemClass = classNames({
+ 'sidebar__file-item': true,
+ 'sidebar__file-item--selected': file.id === props.selectedFile.id
+ });
+ return (
+ - props.setSelectedFile(file.id)}
+ >{file.name}
+ );
+ })}
);
}
Sidebar.propTypes = {
- files: PropTypes.array.isRequired
+ files: PropTypes.array.isRequired,
+ selectedFile: PropTypes.shape({
+ id: PropTypes.string.isRequired
+ }),
+ setSelectedFile: PropTypes.func.isRequired
};
export default Sidebar;
diff --git a/client/modules/IDE/pages/IDEView.js b/client/modules/IDE/pages/IDEView.js
index 2278a095..c7308fa9 100644
--- a/client/modules/IDE/pages/IDEView.js
+++ b/client/modules/IDE/pages/IDEView.js
@@ -11,6 +11,7 @@ import * as FileActions from '../actions/files';
import * as IDEActions from '../actions/ide';
import * as PreferencesActions from '../actions/preferences';
import * as ProjectActions from '../actions/project';
+import { getFile, getHTMLFile, getJSFiles, getCSSFiles } from '../reducers/files';
class IDEView extends React.Component {
componentDidMount() {
@@ -43,16 +44,35 @@ class IDEView extends React.Component {
closePreferences={this.props.closePreferences}
increaseFont={this.props.increaseFont}
decreaseFont={this.props.decreaseFont}
+ updateFont={this.props.updateFont}
fontSize={this.props.preferences.fontSize}
+ increaseIndentation={this.props.increaseIndentation}
+ decreaseIndentation={this.props.decreaseIndentation}
+ updateIndentation={this.props.updateIndentation}
+ indentationAmount={this.props.preferences.indentationAmount}
+ isTabIndent={this.props.preferences.isTabIndent}
+ indentWithSpace={this.props.indentWithSpace}
+ indentWithTab={this.props.indentWithTab}
+ />
+
-
}
@@ -83,18 +103,38 @@ IDEView.propTypes = {
openPreferences: PropTypes.func.isRequired,
preferences: PropTypes.shape({
isVisible: PropTypes.bool.isRequired,
- fontSize: PropTypes.number.isRequired
+ fontSize: PropTypes.number.isRequired,
+ indentationAmount: PropTypes.number.isRequired,
+ isTabIndent: PropTypes.bool.isRequired
}).isRequired,
closePreferences: PropTypes.func.isRequired,
increaseFont: PropTypes.func.isRequired,
decreaseFont: PropTypes.func.isRequired,
+ updateFont: PropTypes.func.isRequired,
+ increaseIndentation: PropTypes.func.isRequired,
+ decreaseIndentation: PropTypes.func.isRequired,
+ updateIndentation: PropTypes.func.isRequired,
+ indentWithSpace: PropTypes.func.isRequired,
+ indentWithTab: PropTypes.func.isRequired,
files: PropTypes.array.isRequired,
- updateFileContent: PropTypes.func.isRequired
+ updateFileContent: PropTypes.func.isRequired,
+ selectedFile: PropTypes.shape({
+ id: PropTypes.string.isRequired,
+ content: PropTypes.string.isRequired
+ }),
+ setSelectedFile: PropTypes.func.isRequired,
+ htmlFile: PropTypes.object.isRequired,
+ jsFiles: PropTypes.array.isRequired,
+ cssFiles: PropTypes.array.isRequired
};
function mapStateToProps(state) {
return {
files: state.files,
+ selectedFile: getFile(state.files, state.ide.selectedFile),
+ htmlFile: getHTMLFile(state.files),
+ jsFiles: getJSFiles(state.files),
+ cssFiles: getCSSFiles(state.files),
ide: state.ide,
preferences: state.preferences,
user: state.user,
diff --git a/client/modules/IDE/reducers/files.js b/client/modules/IDE/reducers/files.js
index 3a1a03d5..db53dbc3 100644
--- a/client/modules/IDE/reducers/files.js
+++ b/client/modules/IDE/reducers/files.js
@@ -9,10 +9,13 @@ function draw() {
}`;
const defaultHTML =
-`
-
+`
+
+
+
+
@@ -20,14 +23,30 @@ const defaultHTML =
`;
+const defaultCSS =
+`html, body {
+ overflow: hidden;
+ margin: 0;
+ padding: 0;
+}
+`;
+
+// if the project has never been saved,
const initialState = [
{
name: 'sketch.js',
- content: defaultSketch
+ content: defaultSketch,
+ id: '1'
},
{
name: 'index.html',
- content: defaultHTML
+ content: defaultHTML,
+ id: '2'
+ },
+ {
+ name: 'style.css',
+ content: defaultCSS,
+ id: '3'
}];
@@ -50,4 +69,9 @@ const files = (state = initialState, action) => {
}
};
+export const getFile = (state, id) => state.filter(file => file.id === id)[0];
+export const getHTMLFile = (state) => state.filter(file => file.name.match(/.*\.html$/))[0];
+export const getJSFiles = (state) => state.filter(file => file.name.match(/.*\.js$/));
+export const getCSSFiles = (state) => state.filter(file => file.name.match(/.*\.css$/));
+
export default files;
diff --git a/client/modules/IDE/reducers/ide.js b/client/modules/IDE/reducers/ide.js
index e89ffdee..9c4c5b88 100644
--- a/client/modules/IDE/reducers/ide.js
+++ b/client/modules/IDE/reducers/ide.js
@@ -1,23 +1,22 @@
import * as ActionTypes from '../../../constants';
const initialState = {
- isPlaying: false
+ isPlaying: false,
+ selectedFile: '1'
};
const ide = (state = initialState, action) => {
switch (action.type) {
case ActionTypes.TOGGLE_SKETCH:
- return {
- isPlaying: !state.isPlaying
- };
+ return Object.assign({}, state, { isPlaying: !state.isPlaying });
case ActionTypes.START_SKETCH:
- return {
- isPlaying: true
- };
+ return Object.assign({}, state, { isPlaying: true });
case ActionTypes.STOP_SKETCH:
- return {
- isPlaying: false
- };
+ return Object.assign({}, state, { isPlaying: false });
+ case ActionTypes.SET_SELECTED_FILE:
+ case ActionTypes.SET_PROJECT:
+ case ActionTypes.NEW_PROJECT:
+ return Object.assign({}, state, { selectedFile: action.selectedFile });
default:
return state;
}
diff --git a/client/modules/IDE/reducers/preferences.js b/client/modules/IDE/reducers/preferences.js
index 9735251a..0f7b084c 100644
--- a/client/modules/IDE/reducers/preferences.js
+++ b/client/modules/IDE/reducers/preferences.js
@@ -2,31 +2,53 @@ import * as ActionTypes from '../../../constants';
const initialState = {
isVisible: false,
- fontSize: 18
+ fontSize: 18,
+ indentationAmount: 2,
+ isTabIndent: true
};
const preferences = (state = initialState, action) => {
switch (action.type) {
case ActionTypes.OPEN_PREFERENCES:
- return {
- isVisible: true,
- fontSize: state.fontSize
- };
+ return Object.assign({}, state, {
+ isVisible: true
+ });
case ActionTypes.CLOSE_PREFERENCES:
- return {
- isVisible: false,
- fontSize: state.fontSize
- };
+ return Object.assign({}, state, {
+ isVisible: false
+ });
case ActionTypes.INCREASE_FONTSIZE:
- return {
- isVisible: state.isVisible,
+ return Object.assign({}, state, {
fontSize: state.fontSize + 2
- };
+ });
case ActionTypes.DECREASE_FONTSIZE:
- return {
- isVisible: state.isVisible,
+ return Object.assign({}, state, {
fontSize: state.fontSize - 2
- };
+ });
+ case ActionTypes.UPDATE_FONTSIZE:
+ return Object.assign({}, state, {
+ fontSize: action.value
+ });
+ case ActionTypes.INCREASE_INDENTATION:
+ return Object.assign({}, state, {
+ indentationAmount: state.indentationAmount + 2
+ });
+ case ActionTypes.DECREASE_INDENTATION:
+ return Object.assign({}, state, {
+ indentationAmount: state.indentationAmount - 2
+ });
+ case ActionTypes.UPDATE_INDENTATION:
+ return Object.assign({}, state, {
+ indentationAmount: action.value
+ });
+ case ActionTypes.INDENT_WITH_TAB:
+ return Object.assign({}, state, {
+ isTabIndent: true
+ });
+ case ActionTypes.INDENT_WITH_SPACE:
+ return Object.assign({}, state, {
+ isTabIndent: false
+ });
default:
return state;
}
diff --git a/client/styles/abstracts/_placeholders.scss b/client/styles/abstracts/_placeholders.scss
index 021d3a3f..4b89cf7c 100644
--- a/client/styles/abstracts/_placeholders.scss
+++ b/client/styles/abstracts/_placeholders.scss
@@ -69,6 +69,8 @@
@extend %toolbar-button;
color: $light-primary-text-color;
background-color: $light-modal-button-background-color;
+ padding: 0;
+ line-height: #{50 / $base-font-size}rem;
& g {
fill: $light-primary-text-color;
}
@@ -82,9 +84,25 @@
}
%fake-link {
- color: $light-secondary-text-color;
+ color: $light-inactive-text-color;
cursor: pointer;
&:hover {
color: $light-primary-text-color;
}
}
+
+%preference-option {
+ background-color: $light-button-background-color;
+ color: $light-inactive-text-color;
+ font-size: #{14 / $base-font-size}rem;
+ cursor: pointer;
+ text-align: left;
+ margin-bottom: #{5 / $base-font-size}rem;
+ border: 0px;
+ &:hover {
+ color: $light-primary-text-color;
+ }
+ &--selected {
+ color: $light-primary-text-color;
+ }
+}
diff --git a/client/styles/abstracts/_variables.scss b/client/styles/abstracts/_variables.scss
index e07d9e55..a41a2097 100644
--- a/client/styles/abstracts/_variables.scss
+++ b/client/styles/abstracts/_variables.scss
@@ -18,7 +18,7 @@ $light-button-background-hover-color: $p5js-pink;
$light-button-background-active-color: #f10046;
$light-button-hover-color: $white;
$light-button-active-color: $white;
-$light-modal-button-background-color: #e6e6e6;
+$light-modal-button-background-color: #e6e6e6;
$light-icon-color: #8b8b8b;
$light-icon-hover-color: $light-primary-text-color;
diff --git a/client/styles/base/_base.scss b/client/styles/base/_base.scss
index f11b8d96..fda0b662 100644
--- a/client/styles/base/_base.scss
+++ b/client/styles/base/_base.scss
@@ -18,7 +18,7 @@ body, input, button {
a {
text-decoration: none;
- color: $light-secondary-text-color;
+ color: $light-inactive-text-color;
&:hover {
text-decoration: none;
color: $light-primary-text-color;
@@ -46,7 +46,12 @@ h2 {
h3 {
font-weight: normal;
}
-
+h4 {
+ font-weight: normal;
+}
+h6 {
+ font-weight: normal;
+}
thead {
text-align: left;
-}
\ No newline at end of file
+}
diff --git a/client/styles/components/_preferences.scss b/client/styles/components/_preferences.scss
index 96224827..35371645 100644
--- a/client/styles/components/_preferences.scss
+++ b/client/styles/components/_preferences.scss
@@ -2,7 +2,7 @@
position: absolute;
top: #{66 / $base-font-size}rem;
right: #{40 / $base-font-size}rem;
- width: #{276 / $base-font-size}rem;
+ width: #{332 / $base-font-size}rem;
background-color: $light-button-background-color;
display: none;
padding: #{16 / $base-font-size}rem #{26 / $base-font-size}rem;
@@ -35,7 +35,10 @@
.preference {
display: flex;
flex-wrap: wrap;
- justify-content: space-between;
+ padding-bottom: #{40 / $base-font-size}rem;
+ & + & {
+ border-top: 2px dashed $light-button-border-color;
+ }
}
.preference__title {
@@ -44,8 +47,35 @@
}
.preference__value {
- border: 1px solid $light-button-border-color;
+ border: 2px solid $light-button-border-color;
text-align: center;
- line-height: #{48 / $base-font-size}rem;
+ border-radius: 0%;
width: #{48 / $base-font-size}rem;
+ height: #{44 / $base-font-size}rem;
+ margin: 0 #{28 / $base-font-size}rem;
+ padding: 0;
+ background-color: $light-button-background-color;
+}
+
+.preference__label {
+ margin: 0;
+ line-height: #{20 / $base-font-size}rem;
+ color: $light-inactive-text-color;
+ &:hover {
+ color: $light-inactive-text-color;
+ }
+}
+
+.preference__vertical-list {
+ display: flex;
+ flex-direction: column;
+}
+
+.preference__option {
+ @extend %preference-option;
+ list-style-type: none;
+ padding-left: #{28 / $base-font-size}rem;
+ &--selected {
+ @extend %preference-option--selected;
+ }
}
diff --git a/client/styles/components/_sidebar.scss b/client/styles/components/_sidebar.scss
index afa53bc9..f7fb4ff8 100644
--- a/client/styles/components/_sidebar.scss
+++ b/client/styles/components/_sidebar.scss
@@ -1,10 +1,9 @@
.sidebar__file-list {
- padding: #{4 / $base-font-size}rem #{20 / $base-font-size}rem;
border-top: 1px solid $ide-border-color;
}
.sidebar__file-item {
- padding: #{4 / $base-font-size}rem 0;
+ padding: #{8 / $base-font-size}rem #{20 / $base-font-size}rem;
color: $light-inactive-text-color;
}
diff --git a/client/styles/components/_toolbar.scss b/client/styles/components/_toolbar.scss
index 30cabff6..1bad6fd1 100644
--- a/client/styles/components/_toolbar.scss
+++ b/client/styles/components/_toolbar.scss
@@ -45,12 +45,12 @@
}
.toolbar__project-name {
- color: $light-secondary-text-color;
+ color: $light-inactive-text-color;
cursor: pointer;
&:hover {
color: $light-primary-text-color;
}
&:focus {
- color: $light-secondary-text-color;
+ color: $light-inactive-text-color;
}
}
diff --git a/package.json b/package.json
index 53f5d57d..dca229de 100644
--- a/package.json
+++ b/package.json
@@ -60,11 +60,13 @@
"babel-core": "^6.8.0",
"bcrypt-nodejs": "0.0.3",
"body-parser": "^1.15.1",
+ "bson-objectid": "^1.1.4",
"classnames": "^2.2.5",
"codemirror": "^5.14.2",
"connect-mongo": "^1.2.0",
"cookie-parser": "^1.4.1",
"dotenv": "^2.0.0",
+ "escape-string-regexp": "^1.0.5",
"eslint-loader": "^1.3.0",
"express": "^4.13.4",
"express-session": "^1.13.0",
@@ -81,6 +83,7 @@
"redux": "^3.5.2",
"redux-form": "^5.2.5",
"redux-thunk": "^2.1.0",
- "shortid": "^2.2.6"
+ "shortid": "^2.2.6",
+ "srcdoc-polyfill": "^0.2.0"
}
}
diff --git a/server/controllers/project.controller.js b/server/controllers/project.controller.js
index 03baa4e5..6c8f55c4 100644
--- a/server/controllers/project.controller.js
+++ b/server/controllers/project.controller.js
@@ -2,8 +2,7 @@ import Project from '../models/project';
export function createProject(req, res) {
const projectValues = {
- user: req.user ? req.user._id : undefined, // eslint-disable-line no-underscore-dangle
- file: {}
+ user: req.user ? req.user._id : undefined // eslint-disable-line no-underscore-dangle
};
Object.assign(projectValues, req.body);
diff --git a/server/models/project.js b/server/models/project.js
index 709ea58d..5927a3b7 100644
--- a/server/models/project.js
+++ b/server/models/project.js
@@ -12,16 +12,24 @@ function draw() {
}`
const defaultHTML =
-`
-
+`
+
+
`
+const defaultCSS =
+`html, body {
+ overflow: hidden;
+ margin: 0;
+ padding: 0;
+}
+`;
const fileSchema = new Schema({
name: { type: String, default: 'sketch.js' },
@@ -39,8 +47,11 @@ fileSchema.set('toJSON', {
const projectSchema = new Schema({
name: { type: String, default: "Hello p5.js, it's the server" },
user: { type: Schema.Types.ObjectId, ref: 'User' },
- files: {type: [ fileSchema ], default: [{ name: 'sketch.js', content: defaultSketch, _id: new ObjectId() }, { name: 'index.html', content: defaultHTML, _id: new ObjectId() }]},
- _id: { type: String, default: shortid.generate }
+ files: { type: [ fileSchema ], default: [{ name: 'sketch.js', content: defaultSketch, _id: new ObjectId() },
+ { name: 'index.html', content: defaultHTML, _id: new ObjectId() },
+ { name: 'style.css', content: defaultCSS, _id: new ObjectId() }]},
+ _id: { type: String, default: shortid.generate },
+ selectedFile: Schema.Types.ObjectId
}, { timestamps: true });
projectSchema.virtual('id').get(function(){
@@ -51,4 +62,12 @@ projectSchema.set('toJSON', {
virtuals: true
});
+projectSchema.pre('save', function createSelectedFile(next) {
+ const project = this;
+ if (!project.selectedFile) {
+ project.selectedFile = project.files[0]._id; // eslint-disable-line no-underscore-dangle
+ return next();
+ }
+});
+
export default mongoose.model('Project', projectSchema);
diff --git a/server/server.js b/server/server.js
index e0e97fe2..99879244 100644
--- a/server/server.js
+++ b/server/server.js
@@ -83,4 +83,3 @@ app.listen(serverConfig.port, (error) => {
});
export default app;
-