add Console component, gets postMessage from previewFrame
This commit is contained in:
parent
fae5ea086e
commit
7f0b7afac1
7 changed files with 151 additions and 26 deletions
|
@ -31,5 +31,7 @@ export const SET_PROJECTS = 'SET_PROJECTS';
|
||||||
|
|
||||||
export const SET_SELECTED_FILE = 'SET_SELECTED_FILE';
|
export const SET_SELECTED_FILE = 'SET_SELECTED_FILE';
|
||||||
|
|
||||||
|
export const CONSOLE_EVENT = 'CONSOLE_EVENT';
|
||||||
|
|
||||||
// eventually, handle errors more specifically and better
|
// eventually, handle errors more specifically and better
|
||||||
export const ERROR = 'ERROR';
|
export const ERROR = 'ERROR';
|
||||||
|
|
|
@ -24,3 +24,10 @@ export function setSelectedFile(fileId) {
|
||||||
selectedFile: fileId
|
selectedFile: fileId
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function dispatchConsoleEvent(...args) {
|
||||||
|
return {
|
||||||
|
type: ActionTypes.CONSOLE_EVENT,
|
||||||
|
event: args[0].data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
49
client/modules/IDE/components/Console.js
Normal file
49
client/modules/IDE/components/Console.js
Normal 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;
|
|
@ -3,14 +3,68 @@ import ReactDOM from 'react-dom';
|
||||||
import escapeStringRegexp from 'escape-string-regexp';
|
import escapeStringRegexp from 'escape-string-regexp';
|
||||||
import srcDoc from 'srcdoc-polyfill';
|
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 {
|
class PreviewFrame extends React.Component {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.hijackConsole();
|
|
||||||
|
|
||||||
if (this.props.isPlaying) {
|
if (this.props.isPlaying) {
|
||||||
this.renderFrameContents();
|
this.renderFrameContents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.addEventListener('message', (msg) => {
|
||||||
|
if (msg.data.source === 'sketch') {
|
||||||
|
this.props.dispatchConsoleEvent(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
|
@ -53,29 +107,11 @@ class PreviewFrame extends React.Component {
|
||||||
// htmlHeadContents = htmlHeadContents.slice(1, htmlHeadContents.length - 2);
|
// htmlHeadContents = htmlHeadContents.slice(1, htmlHeadContents.length - 2);
|
||||||
// htmlHeadContents += '<link rel="stylesheet" type="text/css" href="/preview-styles.css" />\n';
|
// 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 = htmlFile.replace(/(?:<head.*?>)([\s\S]*?)(?:<\/head>)/gmi, `<head>\n${htmlHeadContents}\n</head>`);
|
||||||
|
htmlFile += hijackConsoleScript;
|
||||||
|
|
||||||
return htmlFile;
|
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() {
|
renderSketch() {
|
||||||
const doc = ReactDOM.findDOMNode(this);
|
const doc = ReactDOM.findDOMNode(this);
|
||||||
if (this.props.isPlaying) {
|
if (this.props.isPlaying) {
|
||||||
|
@ -107,7 +143,7 @@ class PreviewFrame extends React.Component {
|
||||||
frameBorder="0"
|
frameBorder="0"
|
||||||
title="sketch output"
|
title="sketch output"
|
||||||
sandbox="allow-scripts allow-pointer-lock allow-same-origin allow-popups allow-modals allow-forms"
|
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
|
content: PropTypes.string.isRequired
|
||||||
}),
|
}),
|
||||||
jsFiles: PropTypes.array.isRequired,
|
jsFiles: PropTypes.array.isRequired,
|
||||||
cssFiles: PropTypes.array.isRequired
|
cssFiles: PropTypes.array.isRequired,
|
||||||
|
dispatchConsoleEvent: PropTypes.func.isRequired,
|
||||||
|
children: PropTypes.element
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PreviewFrame;
|
export default PreviewFrame;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import PreviewFrame from '../components/PreviewFrame';
|
||||||
import Toolbar from '../components/Toolbar';
|
import Toolbar from '../components/Toolbar';
|
||||||
import Preferences from '../components/Preferences';
|
import Preferences from '../components/Preferences';
|
||||||
import Nav from '../../../components/Nav';
|
import Nav from '../../../components/Nav';
|
||||||
|
import Console from '../components/Console';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import * as FileActions from '../actions/files';
|
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" />
|
<link type="text/css" rel="stylesheet" href="/preview-styles.css" />
|
||||||
}
|
}
|
||||||
isPlaying={this.props.ide.isPlaying}
|
isPlaying={this.props.ide.isPlaying}
|
||||||
|
dispatchConsoleEvent={this.props.dispatchConsoleEvent}
|
||||||
|
/>
|
||||||
|
<Console
|
||||||
|
consoleEvent={this.props.ide.consoleEvent}
|
||||||
|
isPlaying={this.props.ide.isPlaying}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,7 +99,8 @@ IDEView.propTypes = {
|
||||||
createProject: PropTypes.func.isRequired,
|
createProject: PropTypes.func.isRequired,
|
||||||
saveProject: PropTypes.func.isRequired,
|
saveProject: PropTypes.func.isRequired,
|
||||||
ide: PropTypes.shape({
|
ide: PropTypes.shape({
|
||||||
isPlaying: PropTypes.bool.isRequired
|
isPlaying: PropTypes.bool.isRequired,
|
||||||
|
consoleEvent: PropTypes.object
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
startSketch: PropTypes.func.isRequired,
|
startSketch: PropTypes.func.isRequired,
|
||||||
stopSketch: PropTypes.func.isRequired,
|
stopSketch: PropTypes.func.isRequired,
|
||||||
|
@ -125,7 +133,8 @@ IDEView.propTypes = {
|
||||||
setSelectedFile: PropTypes.func.isRequired,
|
setSelectedFile: PropTypes.func.isRequired,
|
||||||
htmlFile: PropTypes.object.isRequired,
|
htmlFile: PropTypes.object.isRequired,
|
||||||
jsFiles: PropTypes.array.isRequired,
|
jsFiles: PropTypes.array.isRequired,
|
||||||
cssFiles: PropTypes.array.isRequired
|
cssFiles: PropTypes.array.isRequired,
|
||||||
|
dispatchConsoleEvent: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
|
|
|
@ -2,7 +2,11 @@ import * as ActionTypes from '../../../constants';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
isPlaying: false,
|
isPlaying: false,
|
||||||
selectedFile: '1'
|
selectedFile: '1',
|
||||||
|
consoleEvent: {
|
||||||
|
method: undefined,
|
||||||
|
arguments: []
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const ide = (state = initialState, action) => {
|
const ide = (state = initialState, action) => {
|
||||||
|
@ -17,6 +21,8 @@ const ide = (state = initialState, action) => {
|
||||||
case ActionTypes.SET_PROJECT:
|
case ActionTypes.SET_PROJECT:
|
||||||
case ActionTypes.NEW_PROJECT:
|
case ActionTypes.NEW_PROJECT:
|
||||||
return Object.assign({}, state, { selectedFile: action.selectedFile });
|
return Object.assign({}, state, { selectedFile: action.selectedFile });
|
||||||
|
case ActionTypes.CONSOLE_EVENT:
|
||||||
|
return Object.assign({}, state, { consoleEvent: action.event });
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
14
client/styles/components/_console.scss
Normal file
14
client/styles/components/_console.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue