- {Object.keys(args).map(key =>
{args[key]})}
+
+ { times > 1 &&
+
{times}
+ }
+
);
})}
@@ -67,7 +125,9 @@ Console.propTypes = {
isExpanded: PropTypes.bool.isRequired,
collapseConsole: PropTypes.func.isRequired,
expandConsole: PropTypes.func.isRequired,
- clearConsole: PropTypes.func.isRequired
+ clearConsole: PropTypes.func.isRequired,
+ dispatchConsoleEvent: PropTypes.func.isRequired,
+ theme: PropTypes.string.isRequired
};
Console.defaultProps = {
diff --git a/client/modules/IDE/components/Editor.jsx b/client/modules/IDE/components/Editor.jsx
index b36a6332..74bd6ceb 100644
--- a/client/modules/IDE/components/Editor.jsx
+++ b/client/modules/IDE/components/Editor.jsx
@@ -116,8 +116,8 @@ class Editor extends React.Component {
this.props.setUnsavedChanges(true);
this.props.updateFileContent(this.props.file.name, this._cm.getValue());
if (this.props.autorefresh && this.props.isPlaying) {
- this.props.startRefreshSketch();
this.props.clearConsole();
+ this.props.startRefreshSketch();
}
}, 400));
diff --git a/client/modules/IDE/components/PreviewFrame.jsx b/client/modules/IDE/components/PreviewFrame.jsx
index ade57b97..507bdfb7 100644
--- a/client/modules/IDE/components/PreviewFrame.jsx
+++ b/client/modules/IDE/components/PreviewFrame.jsx
@@ -2,10 +2,9 @@ import PropTypes from 'prop-types';
import React from 'react';
import ReactDOM from 'react-dom';
// import escapeStringRegexp from 'escape-string-regexp';
+import { isEqual } from 'lodash';
import srcDoc from 'srcdoc-polyfill';
-
import loopProtect from 'loop-protect';
-import loopProtectScript from 'loop-protect/dist/loop-protect.min';
import { JSHINT } from 'jshint';
import decomment from 'decomment';
import { getBlobUrl } from '../actions/files';
@@ -18,38 +17,20 @@ import {
EXTERNAL_LINK_REGEX,
NOT_EXTERNAL_LINK_REGEX
} from '../../../../server/utils/fileUtils';
-import { hijackConsole, hijackConsoleErrorsScript, startTag, getAllScriptOffsets }
+import { hijackConsoleErrorsScript, startTag, getAllScriptOffsets }
from '../../../utils/consoleUtils';
-
class PreviewFrame extends React.Component {
- componentDidMount() {
- if (this.props.isPlaying) {
- this.renderFrameContents();
- }
+ constructor(props) {
+ super(props);
+ this.handleConsoleEvent = this.handleConsoleEvent.bind(this);
+ }
- window.addEventListener('message', (messageEvent) => {
- console.log(messageEvent);
- messageEvent.data.forEach((message) => {
- const args = message.arguments;
- Object.keys(args).forEach((key) => {
- if (args[key].includes('Exiting potential infinite loop')) {
- this.props.stopSketch();
- this.props.expandConsole();
- }
- });
- });
- this.props.dispatchConsoleEvent(messageEvent.data);
- });
+ componentDidMount() {
+ window.addEventListener('message', this.handleConsoleEvent);
}
componentDidUpdate(prevProps) {
- // if sketch starts or stops playing, want to rerender
- if (this.props.isPlaying !== prevProps.isPlaying) {
- this.renderSketch();
- return;
- }
-
// if the user explicitly clicks on the play button
if (this.props.isPlaying && this.props.previewIsRefreshing) {
this.renderSketch();
@@ -86,12 +67,43 @@ class PreviewFrame extends React.Component {
}
componentWillUnmount() {
+ window.removeEventListener('message', this.handleConsoleEvent);
ReactDOM.unmountComponentAtNode(this.iframeElement.contentDocument.body);
}
- clearPreview() {
- const doc = this.iframeElement;
- doc.srcDoc = '';
+ handleConsoleEvent(messageEvent) {
+ if (Array.isArray(messageEvent.data)) {
+ messageEvent.data.every((message, index, arr) => {
+ const { arguments: args } = message;
+ let hasInfiniteLoop = false;
+ Object.keys(args).forEach((key) => {
+ if (typeof args[key] === 'string' && args[key].includes('Exiting potential infinite loop')) {
+ this.props.stopSketch();
+ this.props.expandConsole();
+ hasInfiniteLoop = true;
+ }
+ });
+ if (hasInfiniteLoop) {
+ return false;
+ }
+ if (index === arr.length - 1) {
+ Object.assign(message, { times: 1 });
+ return false;
+ }
+ const cur = Object.assign(message, { times: 1 });
+ const nextIndex = index + 1;
+ while (isEqual(cur.arguments, arr[nextIndex].arguments) && cur.method === arr[nextIndex].method) {
+ cur.times += 1;
+ arr.splice(nextIndex, 1);
+ if (nextIndex === arr.length) {
+ return false;
+ }
+ }
+ return true;
+ });
+
+ this.props.dispatchConsoleEvent(messageEvent.data);
+ }
}
addLoopProtect(sketchDoc) {
@@ -121,9 +133,7 @@ class PreviewFrame extends React.Component {
injectLocalFiles() {
const htmlFile = this.props.htmlFile.content;
let scriptOffs = [];
-
const resolvedFiles = this.resolveJSAndCSSLinks(this.props.files);
-
const parser = new DOMParser();
const sketchDoc = parser.parseFromString(htmlFile, 'text/html');
@@ -138,10 +148,6 @@ class PreviewFrame extends React.Component {
this.resolveScripts(sketchDoc, resolvedFiles);
this.resolveStyles(sketchDoc, resolvedFiles);
- const scriptsToInject = [
- loopProtectScript,
- hijackConsole
- ];
const accessiblelib = sketchDoc.createElement('script');
accessiblelib.setAttribute(
'src',
@@ -156,7 +162,6 @@ class PreviewFrame extends React.Component {
const textSection = sketchDoc.createElement('section');
textSection.setAttribute('id', 'textOutput-content');
sketchDoc.getElementById('accessible-outputs').appendChild(textSection);
- this.iframeElement.focus();
}
if (this.props.gridOutput) {
sketchDoc.body.appendChild(accessibleOutputs);
@@ -164,7 +169,6 @@ class PreviewFrame extends React.Component {
const gridSection = sketchDoc.createElement('section');
gridSection.setAttribute('id', 'gridOutput-content');
sketchDoc.getElementById('accessible-outputs').appendChild(gridSection);
- this.iframeElement.focus();
}
if (this.props.soundOutput) {
sketchDoc.body.appendChild(accessibleOutputs);
@@ -174,11 +178,9 @@ class PreviewFrame extends React.Component {
sketchDoc.getElementById('accessible-outputs').appendChild(soundSection);
}
- scriptsToInject.forEach((scriptToInject) => {
- const script = sketchDoc.createElement('script');
- script.text = scriptToInject;
- sketchDoc.head.appendChild(script);
- });
+ const previewScripts = sketchDoc.createElement('script');
+ previewScripts.src = '/previewScripts.js';
+ sketchDoc.head.appendChild(previewScripts);
const sketchDocString = `\n${sketchDoc.documentElement.outerHTML}`;
scriptOffs = getAllScriptOffsets(sketchDocString);
@@ -320,15 +322,6 @@ class PreviewFrame extends React.Component {
}
}
- renderFrameContents() {
- const doc = this.iframeElement.contentDocument;
- if (doc.readyState === 'complete') {
- this.renderSketch();
- } else {
- setTimeout(this.renderFrameContents, 0);
- }
- }
-
render() {
return (
diff --git a/client/styles/abstracts/_variables.scss b/client/styles/abstracts/_variables.scss
index d3a62a22..8c8eb6fb 100644
--- a/client/styles/abstracts/_variables.scss
+++ b/client/styles/abstracts/_variables.scss
@@ -5,6 +5,10 @@ $p5js-pink: #ed225d;
$white: #fff;
$black: #000;
$yellow: #F5DC23;
+$orange: #ffa500;
+$red: #ff0000;
+$lightsteelblue: #B0C4DE;
+$dodgerblue: #1E90FF;
$primary-text-color: #333;
$icon-color: #8b8b8b;
$icon-hover-color: #333;
@@ -37,8 +41,13 @@ $themes: (
icon-toast-hover-color: $white,
shadow-color: rgba(0, 0, 0, 0.16),
console-background-color: #eee,
+ console-color: $white,
console-header-background-color: #d6d6d6,
console-header-color: #b1b1b1,
+ console-info-background-color: $lightsteelblue,
+ console-warn-background-color: $orange,
+ console-debug-background-color: $dodgerblue,
+ console-error-background-color: $red,
ide-border-color: #f4f4f4,
editor-gutter-color: #f4f4f4,
file-selected-color: #f4f4f4,
@@ -78,8 +87,13 @@ $themes: (
icon-toast-hover-color: $white,
shadow-color: rgba(0, 0, 0, 0.16),
console-background-color: #4f4f4f,
+ console-color: $black,
console-header-background-color: #3f3f3f,
console-header-color: #b5b5b5,
+ console-info-background-color: $lightsteelblue,
+ console-warn-background-color: $orange,
+ console-debug-background-color: $dodgerblue,
+ console-error-background-color: $red,
ide-border-color: #949494,
editor-gutter-color: #363636,
file-selected-color: #404040,
@@ -118,8 +132,13 @@ $themes: (
icon-toast-hover-color: $yellow,
shadow-color: rgba(0, 0, 0, 0.16),
console-background-color: #4f4f4f,
+ console-color: $black,
console-header-background-color: #3f3f3f,
console-header-color: #b5b5b5,
+ console-info-background-color: $lightsteelblue,
+ console-warn-background-color: $orange,
+ console-debug-background-color: $dodgerblue,
+ console-error-background-color: $red,
ide-border-color: #949494,
editor-gutter-color: #454545,
file-selected-color: #404040,
@@ -152,4 +171,4 @@ $form-button-active-color: $white;
$form-navigation-options-color: #999999;
$about-play-background-color: rgba(255, 255, 255, 0.7);
-$about-button-border-color: rgba(151, 151, 151, 0.7);
+$about-button-border-color: rgba(151, 151, 151, 0.7);
\ No newline at end of file
diff --git a/client/styles/components/_console-feed.scss b/client/styles/components/_console-feed.scss
new file mode 100644
index 00000000..597f9e97
--- /dev/null
+++ b/client/styles/components/_console-feed.scss
@@ -0,0 +1,47 @@
+$CONSOLE_FEED_WITHOUT_ICONS: (
+ LOG_WARN_ICON: 'none',
+ LOG_ERROR_ICON: 'none',
+ LOG_DEBUG_ICON: 'none',
+ LOG_INFO_ICON: 'none'
+);
+
+$CONSOLE_FEED_LIGHT_STYLES: (
+ BASE_BACKGROUND_COLOR: '',
+ LOG_ERROR_BACKGROUND: 'hsl(0, 100%, 97%)',
+ LOG_ERROR_COLOR: '#D11518',
+ LOG_ERROR_BORDER: 'hsl(0, 100%, 92%)',
+ LOG_WARN_BACKGROUND: 'hsl(50, 100%, 95%)',
+ LOG_WARN_COLOR: '#FAAF00',
+ LOG_WARN_BORDER: 'hsl(50, 100%, 88%)',
+ LOG_INFO_COLOR: '#7D7D7D',
+ LOG_DEBUG_COLOR: '#007BBB',
+ LOG_COLOR: 'rgb(128, 128, 128)'
+);
+
+$CONSOLE_FEED_DARK_STYLES: (
+ BASE_BACKGROUND_COLOR: '',
+ BASE_COLOR: 'white',
+ OBJECT_NAME_COLOR: 'white',
+ OBJECT_VALUE_NULL_COLOR: 'hsl(230, 100%, 80%)',
+ OBJECT_VALUE_UNDEFINED_COLOR: 'hsl(230, 100%, 80%)',
+ OBJECT_VALUE_REGEXP_COLOR: 'hsl(230, 100%, 80%)',
+ OBJECT_VALUE_STRING_COLOR: 'hsl(230, 100%, 80%)',
+ OBJECT_VALUE_SYMBOL_COLOR: 'hsl(230, 100%, 80%)',
+ OBJECT_VALUE_NUMBER_COLOR: 'hsl(230, 100%, 80%)',
+ OBJECT_VALUE_BOOLEAN_COLOR: 'hsl(230, 100%, 80%)',
+ OBJECT_VALUE_FUNCTION_KEYWORD_COLOR: 'hsl(230, 100%, 80%)',
+ LOG_ERROR_BACKGROUND: 'hsl(0, 100%, 8%)',
+ LOG_ERROR_COLOR: '#df3a3d',
+ LOG_WARN_BACKGROUND: 'hsl(50, 100%, 10%)',
+ LOG_WARN_COLOR: '#f5bc38',
+ LOG_INFO_COLOR: '#a3a3a3',
+ LOG_DEBUG_COLOR: '#0c99e2',
+ TABLE_BORDER_COLOR: 'grey',
+ TABLE_TH_BACKGROUND_COLOR: 'transparent',
+ TABLE_TH_HOVER_COLOR: 'grey',
+ TABLE_SORT_ICON_COLOR: 'grey',
+ TABLE_DATA_BACKGROUND_IMAGE: 'grey',
+ TABLE_DATA_BACKGROUND_SIZE: 'grey'
+);
+
+$CONSOLE_FEED_CONTRAST_STYLES: $CONSOLE_FEED_DARK_STYLES;
\ No newline at end of file
diff --git a/client/styles/components/_console.scss b/client/styles/components/_console.scss
index 9ed29fc7..abd8a4f0 100644
--- a/client/styles/components/_console.scss
+++ b/client/styles/components/_console.scss
@@ -17,29 +17,12 @@
text-align:left;
}
- // assign styles to different types of console messages
- .preview-console__log {
- @include themify(){
- color: getThemifyVariable('secondary-text-color');
+ .preview-console__message {
+ @include themify() {
+ color: getThemifyVariable('console-color');
}
flex: 1 0 auto;
- }
-
- .preview-console__undefined {
- @include themify(){
- color: getThemifyVariable('inactive-text-color');
- }
- flex: 1 0 auto;
- }
-
- .preview-console__error {
- color: $console-error-color;
- flex: 1 0 auto;
- }
-
- .preview-console__warn {
- color: $console-warn-color;
- flex: 1 0 auto;
+ position: relative;
}
}
@@ -102,4 +85,36 @@
.preview-console--collapsed & {
display: none;
}
+}
+
+.preview-console__logged-times {
+ font-size: #{10 / $base-font-size}rem;
+ font-weight: bold;
+ margin: #{2 / $base-font-size}rem 0 0 #{8 / $base-font-size}rem;
+ border-radius: 10px;
+ padding: #{1 / $base-font-size}rem #{4 / $base-font-size}rem;
+ z-index: 100;
+ left: 0;
+ position: absolute;
+
+ .preview-console__message--info &, .preview-console__message--log & {
+ @include themify() {
+ background-color: getThemifyVariable('console-info-background-color');
+ }
+ }
+ .preview-console__message--warn & {
+ @include themify() {
+ background-color: getThemifyVariable('console-warn-background-color');
+ }
+ }
+ .preview-console__message--debug & {
+ @include themify() {
+ background-color: getThemifyVariable('console-debug-background-color');
+ }
+ }
+ .preview-console__message--error & {
+ @include themify() {
+ background-color: getThemifyVariable('console-error-background-color');
+ }
+ }
}
\ No newline at end of file
diff --git a/client/styles/components/_preview-frame.scss b/client/styles/components/_preview-frame.scss
index 789b4ad6..8c356972 100644
--- a/client/styles/components/_preview-frame.scss
+++ b/client/styles/components/_preview-frame.scss
@@ -2,6 +2,7 @@
min-height: 100%;
min-width: 100%;
position: absolute;
+ border-width: 0;
}
diff --git a/client/utils/consoleUtils.js b/client/utils/consoleUtils.js
index 61fccceb..7a41da8a 100644
--- a/client/utils/consoleUtils.js
+++ b/client/utils/consoleUtils.js
@@ -2,42 +2,6 @@ import {
EXTERNAL_LINK_REGEX
} from '../../server/utils/fileUtils';
-export const hijackConsole = `var iframeWindow = window;
- var originalConsole = iframeWindow.console;
- iframeWindow.console = {};
-
- var methods = [
- 'debug', 'clear', 'error', 'info', 'log', 'warn'
- ];
-
- var consoleBuffer = [];
- var LOGWAIT = 500;
-
- 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);
- });
-
- consoleBuffer.push({
- method: method,
- arguments: args,
- source: 'sketch'
- });
- };
- });
-
- setInterval(function() {
- if (consoleBuffer.length > 0) {
- window.parent.postMessage(consoleBuffer, '*');
- consoleBuffer.length = 0;
- }
- }, LOGWAIT);`;
-
export const hijackConsoleErrorsScript = (offs) => {
const s = `
function getScriptOff(line) {
diff --git a/client/utils/previewEntry.js b/client/utils/previewEntry.js
new file mode 100644
index 00000000..3504980c
--- /dev/null
+++ b/client/utils/previewEntry.js
@@ -0,0 +1,21 @@
+import loopProtect from 'loop-protect';
+import Hook from 'console-feed/lib/Hook/index';
+
+window.loopProtect = loopProtect;
+
+const consoleBuffer = [];
+const LOGWAIT = 500;
+Hook(window.console, (log) => {
+ const { method, data: args } = log[0];
+ consoleBuffer.push({
+ method,
+ arguments: args,
+ source: 'sketch'
+ });
+});
+setInterval(() => {
+ if (consoleBuffer.length > 0) {
+ window.parent.postMessage(consoleBuffer, '*');
+ consoleBuffer.length = 0;
+ }
+}, LOGWAIT);
diff --git a/package.json b/package.json
index bc53a3a2..25aa764f 100644
--- a/package.json
+++ b/package.json
@@ -73,6 +73,7 @@
"clipboard": "^1.7.1",
"codemirror": "^5.38.0",
"connect-mongo": "^1.2.0",
+ "console-feed": "^2.8.1",
"cookie-parser": "^1.4.1",
"cors": "^2.8.1",
"cross-env": "^5.1.3",
@@ -106,9 +107,8 @@
"project-name-generator": "^2.1.3",
"prop-types": "^15.6.0",
"q": "^1.4.1",
- "raw-loader": "^0.5.1",
- "react": "^16.2.0",
- "react-dom": "^16.2.0",
+ "react": "^16.4.0",
+ "react-dom": "^16.4.0",
"react-helmet": "^5.1.3",
"react-hot-loader": "^4.1.2",
"react-inlinesvg": "^0.7.5",
@@ -126,6 +126,9 @@
"request-promise": "^4.1.1",
"s3": "^4.4.0",
"s3-policy": "^0.2.0",
+ "sass-extract": "^2.1.0",
+ "sass-extract-js": "^0.4.0",
+ "sass-extract-loader": "^1.1.0",
"shortid": "^2.2.6",
"slugify": "^1.2.9",
"srcdoc-polyfill": "^0.2.0",
diff --git a/server/server.js b/server/server.js
index 446326c5..395e37b8 100644
--- a/server/server.js
+++ b/server/server.js
@@ -39,7 +39,7 @@ const corsOriginsWhitelist = [
// Run Webpack dev server in development mode
if (process.env.NODE_ENV === 'development') {
const compiler = webpack(config);
- app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath }));
+ app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config[0].output.publicPath }));
app.use(webpackHotMiddleware(compiler));
corsOriginsWhitelist.push(/localhost/);
diff --git a/webpack.config.babel.js b/webpack.config.babel.js
index f19cc4b0..85726fff 100644
--- a/webpack.config.babel.js
+++ b/webpack.config.babel.js
@@ -18,7 +18,7 @@ module.exports = {
module: {
loaders: [
{
- test: /\.scss$/,
+ test: /main\.scss$/,
exclude: /node_modules/,
loader: ExtractTextPlugin.extract({
fallback: 'style-loader',
diff --git a/webpack.config.dev.js b/webpack.config.dev.js
index 9b453932..28748654 100644
--- a/webpack.config.dev.js
+++ b/webpack.config.dev.js
@@ -1,7 +1,8 @@
const webpack = require('webpack');
+const path = require('path');
require('dotenv').config();
-module.exports = {
+module.exports = [{
devtool: 'cheap-module-eval-source-map',
entry: {
app: [
@@ -70,7 +71,7 @@ module.exports = {
// }
},
{
- test: /\.scss$/,
+ test: /main\.scss$/,
loaders: ['style-loader', 'css-loader', 'sass-loader']
},
{
@@ -82,9 +83,52 @@ module.exports = {
loader: 'file-loader'
},
{
- test: /.*loop-protect.min.js$/,
- loader: 'raw-loader'
+ test: /_console-feed.scss/,
+ loader: 'sass-extract-loader',
+ options: {
+ plugins: [{ plugin: 'sass-extract-js', options: { camelCase: false } }]
+ }
}
],
},
-};
+},
+{
+ entry: path.resolve(__dirname, 'client/utils/previewEntry.js'),
+ target: 'web',
+ output: {
+ path: `${__dirname}`,
+ filename: 'previewScripts.js',
+ publicPath: '/'
+ },
+ resolve: {
+ extensions: ['*', '.js', '.jsx'],
+ modules: [
+ 'client',
+ 'node_modules',
+ ],
+ },
+ module: {
+ loaders: [
+ {
+ test: /\.js$/,
+ exclude: /node_modules/,
+ loader: 'babel-loader',
+ query: {
+ presets: [
+ 'react',
+ 'env',
+ 'stage-0',
+ ],
+ plugins: [
+ [
+ 'babel-plugin-webpack-loaders', {
+ 'config': './webpack.config.babel.js',
+ "verbose": false
+ }
+ ]
+ ]
+ },
+ }
+ ],
+ },
+}]
diff --git a/webpack.config.prod.js b/webpack.config.prod.js
index 1a59fd96..63b58101 100644
--- a/webpack.config.prod.js
+++ b/webpack.config.prod.js
@@ -8,7 +8,7 @@ const postcssReporter = require('postcss-reporter');
const cssnano = require('cssnano');
require('dotenv').config();
-module.exports = {
+module.exports = [{
devtool: 'source-map',
entry: {
@@ -53,7 +53,7 @@ module.exports = {
module: {
loaders: [
{
- test: /\.scss$/,
+ test: /main\.scss$/,
exclude: /node_modules/,
loader: ExtractTextPlugin.extract({
fallback: 'style-loader',
@@ -74,8 +74,11 @@ module.exports = {
loader: 'file-loader'
},
{
- test: /.*loop-protect.min.js$/,
- loader: 'raw-loader'
+ test: /_console-feed.scss/,
+ loader: 'sass-extract-loader',
+ options: {
+ plugins: [{ plugin: 'sass-extract-js', options: { camelCase: false } }]
+ }
}
]
},
@@ -126,4 +129,40 @@ module.exports = {
})
],
-};
+},
+{
+ entry: {
+ app: [
+ './client/utils/previewEntry.js'
+ ]
+ },
+ target: 'web',
+ output: {
+ path: `${__dirname}/dist/static`,
+ filename: 'previewScripts.js',
+ publicPath: '/'
+ },
+ resolve: {
+ extensions: ['*', '.js', '.jsx'],
+ modules: [
+ 'client',
+ 'node_modules',
+ ],
+ },
+ module: {
+ loaders: [
+ {
+ test: /\.jsx?$/,
+ exclude: /node_modules/,
+ loader: 'babel-loader'
+ }
+ ]
+ },
+ plugins: [
+ new webpack.optimize.UglifyJsPlugin({
+ compress: {
+ warnings: false
+ }
+ })
+ ]
+}];
\ No newline at end of file