Skip to content
2 changes: 2 additions & 0 deletions client/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,7 @@ export const CREATE_FILE = 'CREATE_FILE';
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';
7 changes: 7 additions & 0 deletions client/modules/IDE/actions/ide.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
57 changes: 57 additions & 0 deletions client/modules/IDE/components/Console.js
Original file line number Diff line number Diff line change
@@ -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 = (
<div key={this.children.length} className={`preview-console__${method}`}>
{Object.keys(args).map((key) => <span key={`${this.children.length}-${key}`}>{args[key]}</span>)}
</div>
);
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 (
<div ref="console" className="preview-console">
{childrenToDisplay}
</div>
);
}

}

Console.propTypes = {
consoleEvent: PropTypes.object,
isPlaying: PropTypes.bool.isRequired
};

export default Console;
72 changes: 63 additions & 9 deletions client/modules/IDE/components/PreviewFrame.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,68 @@ import ReactDOM from 'react-dom';
import escapeStringRegexp from 'escape-string-regexp';
import srcDoc from 'srcdoc-polyfill';

const hijackConsoleScript = `<script>
document.addEventListener('DOMContentLoaded', function() {
var iframeWindow = window;
var originalConsole = iframeWindow.console;
iframeWindow.console = {};

var methods = [
'debug', 'clear', 'error', 'info', 'log', 'warn'
];

methods.forEach( function(method) {
iframeWindow.console[method] = function() {
originalConsole[method].apply(originalConsole, arguments);

var args = Array.from(arguments);
args = args.map(function(i) {
// catch objects
return (typeof i === 'string') ? i : JSON.stringify(i);
});

// post message to parent window
window.parent.postMessage({
method: method,
arguments: args,
source: 'sketch'
}, '*');
};
});

// catch reference errors, via http://stackoverflow.com/a/12747364/2994108
window.onerror = function (msg, url, lineNo, columnNo, error) {
var string = msg.toLowerCase();
var substring = "script error";
var data = {};

if (string.indexOf(substring) > -1){
data = 'Script Error: See Browser Console for Detail';
} else {
data = msg + ' Line: ' + lineNo + 'column: ' + columnNo;
}
window.parent.postMessage({
method: 'error',
arguments: data,
source: 'sketch'
}, '*');
return false;
};
});
</script>`;

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) {
Expand Down Expand Up @@ -51,22 +107,18 @@ class PreviewFrame extends React.Component {
// htmlHeadContents = htmlHeadContents.slice(1, htmlHeadContents.length - 2);
// htmlHeadContents += '<link rel="stylesheet" type="text/css" href="/preview-styles.css" />\n';
// htmlFile = htmlFile.replace(/(?:<head.*?>)([\s\S]*?)(?:<\/head>)/gmi, `<head>\n${htmlHeadContents}\n</head>`);
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, ' ');
}
}

Expand All @@ -88,7 +140,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"
></iframe>
/>
);
}
}
Expand All @@ -101,7 +153,9 @@ PreviewFrame.propTypes = {
content: PropTypes.string.isRequired
}),
jsFiles: PropTypes.array.isRequired,
cssFiles: PropTypes.array.isRequired
cssFiles: PropTypes.array.isRequired,
dispatchConsoleEvent: PropTypes.func.isRequired,
children: PropTypes.element
};

export default PreviewFrame;
9 changes: 9 additions & 0 deletions client/modules/IDE/pages/IDEView.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -85,12 +86,18 @@ class IDEView extends React.Component {
<link type="text/css" rel="stylesheet" href="/preview-styles.css" />
}
isPlaying={this.props.ide.isPlaying}
dispatchConsoleEvent={this.props.dispatchConsoleEvent}
/>
<Console
consoleEvent={this.props.ide.consoleEvent}
isPlaying={this.props.ide.isPlaying}
/>
<NewFileModal
isVisible={this.props.ide.modalIsVisible}
closeModal={this.props.closeNewFileModal}
/>
</div>

);
}
}
Expand All @@ -105,6 +112,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,
Expand Down Expand Up @@ -143,6 +151,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,
Expand Down
6 changes: 6 additions & 0 deletions client/modules/IDE/reducers/ide.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import * as ActionTypes from '../../../constants';
const initialState = {
isPlaying: false,
selectedFile: '1',
consoleEvent: {
method: undefined,
arguments: []
},
modalIsVisible: false,
sidebarIsExpanded: true
};
Expand All @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions client/styles/abstracts/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
27 changes: 27 additions & 0 deletions client/styles/components/_console.scss
Original file line number Diff line number Diff line change
@@ -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;
}
}
1 change: 1 addition & 0 deletions client/styles/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
@import 'components/sketch-list';
@import 'components/sidebar';
@import 'components/modal';
@import 'components/console';

@import 'layout/ide';
@import 'layout/sketch-list';