diff --git a/client/constants.js b/client/constants.js
index 8c617f06..2a2c1858 100644
--- a/client/constants.js
+++ b/client/constants.js
@@ -38,5 +38,7 @@ export const SET_BLOB_URL = 'SET_BLOB_URL';
export const EXPAND_SIDEBAR = 'EXPAND_SIDEBAR';
export const COLLAPSE_SIDEBAR = 'COLLAPSE_SIDEBAR';
+export const CONSOLE_EVENT = 'CONSOLE_EVENT';
+
// 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 a0b48434..4d01d50b 100644
--- a/client/modules/IDE/actions/ide.js
+++ b/client/modules/IDE/actions/ide.js
@@ -25,6 +25,13 @@ export function setSelectedFile(fileId) {
};
}
+export function dispatchConsoleEvent(...args) {
+ return {
+ type: ActionTypes.CONSOLE_EVENT,
+ event: args[0].data
+ };
+}
+
export function newFile() {
return {
type: ActionTypes.SHOW_MODAL
diff --git a/client/modules/IDE/components/Console.js b/client/modules/IDE/components/Console.js
new file mode 100644
index 00000000..e05809db
--- /dev/null
+++ b/client/modules/IDE/components/Console.js
@@ -0,0 +1,57 @@
+import React, { PropTypes } from 'react';
+
+/**
+ * How many console messages to store
+ * @type {Number}
+ */
+const consoleMax = 5;
+
+class Console extends React.Component {
+
+ constructor(props) {
+ super(props);
+
+ /**
+ * An array of React Elements that include previous console messages
+ * @type {Array}
+ */
+ this.children = [];
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.isPlaying && !this.props.isPlaying) {
+ this.children = [];
+ } else if (nextProps.consoleEvent !== this.props.consoleEvent) {
+ const args = nextProps.consoleEvent.arguments;
+ const method = nextProps.consoleEvent.method;
+ const nextChild = (
+
+ {Object.keys(args).map((key) => {args[key]})}
+
+ );
+ this.children.push(nextChild);
+ }
+ }
+
+ shouldComponentUpdate(nextProps) {
+ return (nextProps.consoleEvent !== this.props.consoleEvent) || (nextProps.isPlaying && !this.props.isPlaying);
+ }
+
+ render() {
+ const childrenToDisplay = this.children.slice(-consoleMax);
+
+ return (
+
+ {childrenToDisplay}
+
+ );
+ }
+
+}
+
+Console.propTypes = {
+ consoleEvent: PropTypes.object,
+ isPlaying: PropTypes.bool.isRequired
+};
+
+export default Console;
diff --git a/client/modules/IDE/components/PreviewFrame.js b/client/modules/IDE/components/PreviewFrame.js
index 01545a14..d0e50298 100644
--- a/client/modules/IDE/components/PreviewFrame.js
+++ b/client/modules/IDE/components/PreviewFrame.js
@@ -3,12 +3,68 @@ import ReactDOM from 'react-dom';
import escapeStringRegexp from 'escape-string-regexp';
import srcDoc from 'srcdoc-polyfill';
+const hijackConsoleScript = ``;
+
class PreviewFrame extends React.Component {
componentDidMount() {
if (this.props.isPlaying) {
this.renderFrameContents();
}
+
+ window.addEventListener('message', (msg) => {
+ if (msg.data.source === 'sketch') {
+ this.props.dispatchConsoleEvent(msg);
+ }
+ });
}
componentDidUpdate(prevProps) {
@@ -70,21 +126,24 @@ class PreviewFrame extends React.Component {
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`);
+ htmlFile += hijackConsoleScript;
+
return htmlFile;
}
renderSketch() {
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();
+ doc.srcdoc = '';
+ srcDoc.set(doc, ' ');
}
}
@@ -106,7 +165,7 @@ class PreviewFrame extends React.Component {
frameBorder="0"
title="sketch output"
sandbox="allow-scripts allow-pointer-lock allow-same-origin allow-popups allow-modals allow-forms"
- >
+ />
);
}
}
@@ -120,7 +179,9 @@ PreviewFrame.propTypes = {
}),
jsFiles: PropTypes.array.isRequired,
cssFiles: PropTypes.array.isRequired,
- files: PropTypes.array.isRequired
+ files: PropTypes.array.isRequired,
+ dispatchConsoleEvent: PropTypes.func.isRequired,
+ children: PropTypes.element
};
export default PreviewFrame;
diff --git a/client/modules/IDE/pages/IDEView.js b/client/modules/IDE/pages/IDEView.js
index b29bd5ea..236f3629 100644
--- a/client/modules/IDE/pages/IDEView.js
+++ b/client/modules/IDE/pages/IDEView.js
@@ -6,6 +6,7 @@ import Toolbar from '../components/Toolbar';
import Preferences from '../components/Preferences';
import NewFileModal from '../components/NewFileModal';
import Nav from '../../../components/Nav';
+import Console from '../components/Console';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as FileActions from '../actions/files';
@@ -85,6 +86,11 @@ class IDEView extends React.Component {
}
isPlaying={this.props.ide.isPlaying}
+ dispatchConsoleEvent={this.props.dispatchConsoleEvent}
+ />
+
{(() => {
if (this.props.ide.modalIsVisible) {
@@ -98,6 +104,7 @@ class IDEView extends React.Component {
return '';
})()}
+
);
}
}
@@ -114,6 +121,7 @@ IDEView.propTypes = {
saveProject: PropTypes.func.isRequired,
ide: PropTypes.shape({
isPlaying: PropTypes.bool.isRequired,
+ consoleEvent: PropTypes.object,
modalIsVisible: PropTypes.bool.isRequired,
sidebarIsExpanded: PropTypes.bool.isRequired
}).isRequired,
@@ -152,6 +160,7 @@ IDEView.propTypes = {
htmlFile: PropTypes.object.isRequired,
jsFiles: PropTypes.array.isRequired,
cssFiles: PropTypes.array.isRequired,
+ dispatchConsoleEvent: PropTypes.func.isRequired,
newFile: PropTypes.func.isRequired,
closeNewFileModal: PropTypes.func.isRequired,
expandSidebar: PropTypes.func.isRequired,
diff --git a/client/modules/IDE/reducers/ide.js b/client/modules/IDE/reducers/ide.js
index bdd646d6..de6ff414 100644
--- a/client/modules/IDE/reducers/ide.js
+++ b/client/modules/IDE/reducers/ide.js
@@ -3,6 +3,10 @@ import * as ActionTypes from '../../../constants';
const initialState = {
isPlaying: false,
selectedFile: '1',
+ consoleEvent: {
+ method: undefined,
+ arguments: []
+ },
modalIsVisible: false,
sidebarIsExpanded: true
};
@@ -19,6 +23,8 @@ const ide = (state = initialState, action) => {
case ActionTypes.SET_PROJECT:
case ActionTypes.NEW_PROJECT:
return Object.assign({}, state, { selectedFile: action.selectedFile });
+ case ActionTypes.CONSOLE_EVENT:
+ return Object.assign({}, state, { consoleEvent: action.event });
case ActionTypes.SHOW_MODAL:
return Object.assign({}, state, { modalIsVisible: true });
case ActionTypes.HIDE_MODAL:
diff --git a/client/styles/abstracts/_variables.scss b/client/styles/abstracts/_variables.scss
index aea3f2fb..a006ff62 100644
--- a/client/styles/abstracts/_variables.scss
+++ b/client/styles/abstracts/_variables.scss
@@ -39,3 +39,6 @@ $dark-button-active-color: $white;
$ide-border-color: #f4f4f4;
$editor-selected-line-color: #f3f3f3;
$input-border-color: #979797;
+
+$console-warn-color: #ffbe05;
+$console-error-color: #ff5f52;
diff --git a/client/styles/components/_console.scss b/client/styles/components/_console.scss
new file mode 100644
index 00000000..8dd0e6ba
--- /dev/null
+++ b/client/styles/components/_console.scss
@@ -0,0 +1,27 @@
+.preview-console {
+ position: fixed;
+ width:100%;
+ height:60px;
+ right:0px;
+ bottom: 0px;
+ background:$dark-background-color;
+ z-index:1000;
+
+ & > {
+ position:relative;
+ text-align:left;
+ }
+
+ // assign styles to different types of console messages
+ .preview-console__log {
+ color: $dark-secondary-text-color;
+ }
+
+ .preview-console__error {
+ color: $console-error-color;
+ }
+
+ .preview-console__warn {
+ color: $console-warn-color;
+ }
+}
\ No newline at end of file
diff --git a/client/styles/main.scss b/client/styles/main.scss
index f93a6f35..170d84fb 100644
--- a/client/styles/main.scss
+++ b/client/styles/main.scss
@@ -18,6 +18,7 @@
@import 'components/sketch-list';
@import 'components/sidebar';
@import 'components/modal';
+@import 'components/console';
@import 'layout/ide';
@import 'layout/sketch-list';