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; -