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