add Console component, gets postMessage from previewFrame

This commit is contained in:
therewasaguy 2016-07-17 19:06:43 -04:00
parent fae5ea086e
commit 7f0b7afac1
7 changed files with 151 additions and 26 deletions

View file

@ -31,5 +31,7 @@ export const SET_PROJECTS = 'SET_PROJECTS';
export const SET_SELECTED_FILE = 'SET_SELECTED_FILE';
export const CONSOLE_EVENT = 'CONSOLE_EVENT';
// eventually, handle errors more specifically and better
export const ERROR = 'ERROR';

View file

@ -24,3 +24,10 @@ export function setSelectedFile(fileId) {
selectedFile: fileId
};
}
export function dispatchConsoleEvent(...args) {
return {
type: ActionTypes.CONSOLE_EVENT,
event: args[0].data
};
}

View file

@ -0,0 +1,49 @@
import React, { PropTypes } from 'react';
const consoleMax = 5;
class Console extends React.Component {
constructor(props) {
super(props);
this.children = [];
}
shouldComponentUpdate(nextProps) {
// clear children if paused, but only update when new consoleEvent happens
if (!nextProps.isPlaying) {
this.children = [];
}
return nextProps.consoleEvent !== this.props.consoleEvent;
}
render() {
const args = this.props.consoleEvent.arguments;
const method = this.props.consoleEvent.method;
const nextChild = (
<div key={this.children.length} className={method}>
{Object.keys(args).map((key) => <span key={`${this.children.length}-${key}`}>{args[key]}</span>)}
</div>
);
this.children.push(nextChild);
if (this.children.length > consoleMax) {
this.children = this.children.slice(0, 1);
}
return (
<div ref="console" className="preview-console">
{this.children}
</div>
);
}
}
Console.propTypes = {
consoleEvent: PropTypes.object,
isPlaying: PropTypes.bool.isRequired
};
export default Console;

View file

@ -3,14 +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() {
this.hijackConsole();
if (this.props.isPlaying) {
this.renderFrameContents();
}
window.addEventListener('message', (msg) => {
if (msg.data.source === 'sketch') {
this.props.dispatchConsoleEvent(msg);
}
});
}
componentDidUpdate(prevProps) {
@ -53,29 +107,11 @@ 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;
}
hijackConsole() {
const iframeWindow = ReactDOM.findDOMNode(this).contentWindow;
const originalConsole = iframeWindow.console;
iframeWindow.console = {};
const methods = [
'debug', 'clear', 'error', 'info', 'log', 'warn'
];
methods.forEach((method) => {
iframeWindow.console[method] = (...theArgs) => {
originalConsole[method].apply(originalConsole, theArgs);
// TO DO: do something with the arguments
// window.alert(JSON.stringify(theArgs));
};
});
}
renderSketch() {
const doc = ReactDOM.findDOMNode(this);
if (this.props.isPlaying) {
@ -107,7 +143,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>
/>
);
}
}
@ -120,7 +156,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;

View file

@ -5,6 +5,7 @@ import PreviewFrame from '../components/PreviewFrame';
import Toolbar from '../components/Toolbar';
import Preferences from '../components/Preferences';
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';
@ -77,8 +78,14 @@ 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}
/>
</div>
);
}
}
@ -92,7 +99,8 @@ IDEView.propTypes = {
createProject: PropTypes.func.isRequired,
saveProject: PropTypes.func.isRequired,
ide: PropTypes.shape({
isPlaying: PropTypes.bool.isRequired
isPlaying: PropTypes.bool.isRequired,
consoleEvent: PropTypes.object
}).isRequired,
startSketch: PropTypes.func.isRequired,
stopSketch: PropTypes.func.isRequired,
@ -125,7 +133,8 @@ IDEView.propTypes = {
setSelectedFile: PropTypes.func.isRequired,
htmlFile: PropTypes.object.isRequired,
jsFiles: PropTypes.array.isRequired,
cssFiles: PropTypes.array.isRequired
cssFiles: PropTypes.array.isRequired,
dispatchConsoleEvent: PropTypes.func.isRequired
};
function mapStateToProps(state) {

View file

@ -2,7 +2,11 @@ import * as ActionTypes from '../../../constants';
const initialState = {
isPlaying: false,
selectedFile: '1'
selectedFile: '1',
consoleEvent: {
method: undefined,
arguments: []
}
};
const ide = (state = initialState, action) => {
@ -17,6 +21,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 });
default:
return state;
}

View file

@ -0,0 +1,14 @@
.preview-console {
position: fixed;
width:100%;
height:60px;
right:0px;
bottom: 0px;
background:grey;
z-index:1000;
& > {
position:relative;
text-align:left;
}
}