2018-02-07 18:06:07 +00:00
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
import React from 'react';
|
2016-06-23 22:29:55 +00:00
|
|
|
import ReactDOM from 'react-dom';
|
2016-11-16 18:12:36 +00:00
|
|
|
// import escapeStringRegexp from 'escape-string-regexp';
|
2018-07-30 16:20:57 +00:00
|
|
|
import { isEqual } from 'lodash';
|
2016-07-11 21:21:20 +00:00
|
|
|
import srcDoc from 'srcdoc-polyfill';
|
2016-10-05 17:58:45 +00:00
|
|
|
import loopProtect from 'loop-protect';
|
2017-10-21 21:22:16 +00:00
|
|
|
import { JSHINT } from 'jshint';
|
2018-05-04 20:36:59 +00:00
|
|
|
import decomment from 'decomment';
|
2018-10-22 18:59:08 +00:00
|
|
|
import classNames from 'classnames';
|
2018-11-15 21:40:37 +00:00
|
|
|
import { Decode } from 'console-feed';
|
2016-10-22 20:42:43 +00:00
|
|
|
import { getBlobUrl } from '../actions/files';
|
2016-11-16 18:12:36 +00:00
|
|
|
import { resolvePathToFile } from '../../../../server/utils/filePath';
|
2018-02-01 21:45:19 +00:00
|
|
|
import {
|
|
|
|
MEDIA_FILE_REGEX,
|
|
|
|
MEDIA_FILE_QUOTED_REGEX,
|
|
|
|
STRING_REGEX,
|
2018-05-30 22:23:32 +00:00
|
|
|
PLAINTEXT_FILE_REGEX,
|
2018-02-01 21:45:19 +00:00
|
|
|
EXTERNAL_LINK_REGEX,
|
|
|
|
NOT_EXTERNAL_LINK_REGEX
|
|
|
|
} from '../../../../server/utils/fileUtils';
|
2018-07-30 16:20:57 +00:00
|
|
|
import { hijackConsoleErrorsScript, startTag, getAllScriptOffsets }
|
2018-05-04 20:36:59 +00:00
|
|
|
from '../../../utils/consoleUtils';
|
2016-08-28 13:52:57 +00:00
|
|
|
|
2020-06-19 18:55:37 +00:00
|
|
|
|
2020-09-21 10:22:19 +00:00
|
|
|
// let lastUpdate = null;
|
|
|
|
|
2020-06-19 18:55:37 +00:00
|
|
|
const shouldRenderSketch = (props, prevProps = undefined) => {
|
|
|
|
const { isPlaying, previewIsRefreshing, fullView } = props;
|
|
|
|
|
|
|
|
// if the user explicitly clicks on the play button
|
|
|
|
if (isPlaying && previewIsRefreshing) return true;
|
|
|
|
|
|
|
|
if (!prevProps) return false;
|
|
|
|
|
|
|
|
return (props.isPlaying !== prevProps.isPlaying // if sketch starts or stops playing, want to rerender
|
|
|
|
|| props.isAccessibleOutputPlaying !== prevProps.isAccessibleOutputPlaying // if user switches textoutput preferences
|
|
|
|
|| props.textOutput !== prevProps.textOutput
|
|
|
|
|| props.gridOutput !== prevProps.gridOutput
|
|
|
|
|| props.soundOutput !== prevProps.soundOutput
|
|
|
|
|| (fullView && props.files[0].id !== prevProps.files[0].id));
|
|
|
|
};
|
|
|
|
|
2016-06-23 22:29:55 +00:00
|
|
|
class PreviewFrame extends React.Component {
|
2018-07-30 16:20:57 +00:00
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
this.handleConsoleEvent = this.handleConsoleEvent.bind(this);
|
|
|
|
}
|
2016-07-17 23:06:43 +00:00
|
|
|
|
2018-07-30 16:20:57 +00:00
|
|
|
componentDidMount() {
|
|
|
|
window.addEventListener('message', this.handleConsoleEvent);
|
2020-06-19 18:24:09 +00:00
|
|
|
|
2020-06-19 18:55:37 +00:00
|
|
|
const props = {
|
|
|
|
...this.props,
|
|
|
|
previewIsRefreshing: this.props.previewIsRefreshing,
|
|
|
|
isAccessibleOutputPlaying: this.props.isAccessibleOutputPlaying
|
|
|
|
};
|
|
|
|
if (shouldRenderSketch(props)) this.renderSketch();
|
2016-06-23 22:29:55 +00:00
|
|
|
}
|
|
|
|
|
2016-06-27 19:47:48 +00:00
|
|
|
componentDidUpdate(prevProps) {
|
2020-10-09 17:06:19 +00:00
|
|
|
if (prevProps.isPlaying === true && this.props.isPlaying === false) {
|
|
|
|
console.trace('update', this.props, prevProps);
|
|
|
|
}
|
2020-06-19 18:55:37 +00:00
|
|
|
if (shouldRenderSketch(this.props, prevProps)) this.renderSketch();
|
2016-09-29 04:54:35 +00:00
|
|
|
// small bug - if autorefresh is on, and the usr changes files
|
|
|
|
// in the sketch, preview will reload
|
2016-06-27 19:47:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
2018-07-30 16:20:57 +00:00
|
|
|
window.removeEventListener('message', this.handleConsoleEvent);
|
2020-07-03 17:40:20 +00:00
|
|
|
const iframeBody = this.iframeElement.contentDocument.body;
|
|
|
|
if (iframeBody) { ReactDOM.unmountComponentAtNode(iframeBody); }
|
2016-06-27 19:47:48 +00:00
|
|
|
}
|
|
|
|
|
2018-07-30 16:20:57 +00:00
|
|
|
handleConsoleEvent(messageEvent) {
|
|
|
|
if (Array.isArray(messageEvent.data)) {
|
2019-01-08 22:38:02 +00:00
|
|
|
const decodedMessages = messageEvent.data.map(message =>
|
|
|
|
Object.assign(Decode(message.log), {
|
|
|
|
source: message.source
|
|
|
|
}));
|
2018-11-15 21:40:37 +00:00
|
|
|
|
|
|
|
decodedMessages.every((message, index, arr) => {
|
|
|
|
const { data: args } = message;
|
2018-07-30 16:20:57 +00:00
|
|
|
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;
|
2018-11-15 21:40:37 +00:00
|
|
|
while (isEqual(cur.data, arr[nextIndex].data) && cur.method === arr[nextIndex].method) {
|
2018-07-30 16:20:57 +00:00
|
|
|
cur.times += 1;
|
|
|
|
arr.splice(nextIndex, 1);
|
|
|
|
if (nextIndex === arr.length) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
|
2018-11-15 21:40:37 +00:00
|
|
|
this.props.dispatchConsoleEvent(decodedMessages);
|
2018-07-30 16:20:57 +00:00
|
|
|
}
|
2016-07-11 19:22:29 +00:00
|
|
|
}
|
|
|
|
|
2017-08-28 21:54:39 +00:00
|
|
|
addLoopProtect(sketchDoc) {
|
|
|
|
const scriptsInHTML = sketchDoc.getElementsByTagName('script');
|
|
|
|
const scriptsInHTMLArray = Array.prototype.slice.call(scriptsInHTML);
|
|
|
|
scriptsInHTMLArray.forEach((script) => {
|
2017-11-01 20:47:43 +00:00
|
|
|
script.innerHTML = this.jsPreprocess(script.innerHTML); // eslint-disable-line
|
2017-08-28 21:54:39 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-11-01 20:47:43 +00:00
|
|
|
jsPreprocess(jsText) {
|
|
|
|
let newContent = jsText;
|
2017-10-26 17:48:01 +00:00
|
|
|
// check the code for js errors before sending it to strip comments
|
|
|
|
// or loops.
|
|
|
|
JSHINT(newContent);
|
|
|
|
|
2018-02-01 22:16:54 +00:00
|
|
|
if (JSHINT.errors.length === 0) {
|
2017-10-26 17:48:01 +00:00
|
|
|
newContent = decomment(newContent, {
|
2018-02-09 17:46:47 +00:00
|
|
|
ignore: /\/\/\s*noprotect/g,
|
2017-10-26 17:48:01 +00:00
|
|
|
space: true
|
|
|
|
});
|
|
|
|
newContent = loopProtect(newContent);
|
|
|
|
}
|
|
|
|
return newContent;
|
|
|
|
}
|
|
|
|
|
2019-05-02 19:30:56 +00:00
|
|
|
mergeLocalFilesAndEditorActiveFile() {
|
|
|
|
const files = this.props.files.slice();
|
2019-05-10 22:26:48 +00:00
|
|
|
if (this.props.cmController.getContent) {
|
|
|
|
const activeFileInEditor = this.props.cmController.getContent();
|
|
|
|
files.find(file => file.id === activeFileInEditor.id).content = activeFileInEditor.content;
|
|
|
|
}
|
2019-05-02 19:30:56 +00:00
|
|
|
return files;
|
|
|
|
}
|
|
|
|
|
2016-07-11 19:22:29 +00:00
|
|
|
injectLocalFiles() {
|
2016-11-16 18:12:36 +00:00
|
|
|
const htmlFile = this.props.htmlFile.content;
|
2016-08-28 13:52:57 +00:00
|
|
|
let scriptOffs = [];
|
2019-05-02 19:30:56 +00:00
|
|
|
const files = this.mergeLocalFilesAndEditorActiveFile();
|
|
|
|
const resolvedFiles = this.resolveJSAndCSSLinks(files);
|
2016-11-16 18:12:36 +00:00
|
|
|
const parser = new DOMParser();
|
|
|
|
const sketchDoc = parser.parseFromString(htmlFile, 'text/html');
|
|
|
|
|
2016-11-30 17:38:53 +00:00
|
|
|
const base = sketchDoc.createElement('base');
|
|
|
|
base.href = `${window.location.href}/`;
|
|
|
|
sketchDoc.head.appendChild(base);
|
|
|
|
|
2016-11-16 18:12:36 +00:00
|
|
|
this.resolvePathsForElementsWithAttribute('src', sketchDoc, resolvedFiles);
|
|
|
|
this.resolvePathsForElementsWithAttribute('href', sketchDoc, resolvedFiles);
|
|
|
|
// should also include background, data, poster, but these are used way less often
|
|
|
|
|
|
|
|
this.resolveScripts(sketchDoc, resolvedFiles);
|
|
|
|
this.resolveStyles(sketchDoc, resolvedFiles);
|
|
|
|
|
2018-02-22 21:47:25 +00:00
|
|
|
const accessiblelib = sketchDoc.createElement('script');
|
2018-05-09 02:14:29 +00:00
|
|
|
accessiblelib.setAttribute(
|
|
|
|
'src',
|
|
|
|
'https://cdn.rawgit.com/processing/p5.accessibility/v0.1.1/dist/p5-accessibility.js'
|
|
|
|
);
|
2018-02-22 21:47:25 +00:00
|
|
|
const accessibleOutputs = sketchDoc.createElement('section');
|
|
|
|
accessibleOutputs.setAttribute('id', 'accessible-outputs');
|
2018-03-01 18:28:43 +00:00
|
|
|
accessibleOutputs.setAttribute('aria-label', 'accessible-output');
|
2018-02-22 21:47:25 +00:00
|
|
|
if (this.props.textOutput) {
|
|
|
|
sketchDoc.body.appendChild(accessibleOutputs);
|
|
|
|
sketchDoc.body.appendChild(accessiblelib);
|
|
|
|
const textSection = sketchDoc.createElement('section');
|
|
|
|
textSection.setAttribute('id', 'textOutput-content');
|
|
|
|
sketchDoc.getElementById('accessible-outputs').appendChild(textSection);
|
|
|
|
}
|
|
|
|
if (this.props.gridOutput) {
|
|
|
|
sketchDoc.body.appendChild(accessibleOutputs);
|
|
|
|
sketchDoc.body.appendChild(accessiblelib);
|
|
|
|
const gridSection = sketchDoc.createElement('section');
|
2018-08-10 18:09:55 +00:00
|
|
|
gridSection.setAttribute('id', 'tableOutput-content');
|
2018-02-22 21:47:25 +00:00
|
|
|
sketchDoc.getElementById('accessible-outputs').appendChild(gridSection);
|
|
|
|
}
|
|
|
|
if (this.props.soundOutput) {
|
|
|
|
sketchDoc.body.appendChild(accessibleOutputs);
|
|
|
|
sketchDoc.body.appendChild(accessiblelib);
|
|
|
|
const soundSection = sketchDoc.createElement('section');
|
|
|
|
soundSection.setAttribute('id', 'soundOutput-content');
|
|
|
|
sketchDoc.getElementById('accessible-outputs').appendChild(soundSection);
|
2016-11-16 18:12:36 +00:00
|
|
|
}
|
|
|
|
|
2018-07-30 16:20:57 +00:00
|
|
|
const previewScripts = sketchDoc.createElement('script');
|
|
|
|
previewScripts.src = '/previewScripts.js';
|
|
|
|
sketchDoc.head.appendChild(previewScripts);
|
2016-07-11 19:22:29 +00:00
|
|
|
|
2016-11-16 18:12:36 +00:00
|
|
|
const sketchDocString = `<!DOCTYPE HTML>\n${sketchDoc.documentElement.outerHTML}`;
|
|
|
|
scriptOffs = getAllScriptOffsets(sketchDocString);
|
|
|
|
const consoleErrorsScript = sketchDoc.createElement('script');
|
|
|
|
consoleErrorsScript.innerHTML = hijackConsoleErrorsScript(JSON.stringify(scriptOffs));
|
2017-08-28 21:54:39 +00:00
|
|
|
this.addLoopProtect(sketchDoc);
|
2016-12-13 20:37:11 +00:00
|
|
|
sketchDoc.head.insertBefore(consoleErrorsScript, sketchDoc.head.firstElement);
|
2016-11-16 18:12:36 +00:00
|
|
|
|
|
|
|
return `<!DOCTYPE HTML>\n${sketchDoc.documentElement.outerHTML}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
resolvePathsForElementsWithAttribute(attr, sketchDoc, files) {
|
|
|
|
const elements = sketchDoc.querySelectorAll(`[${attr}]`);
|
2016-12-07 21:12:06 +00:00
|
|
|
const elementsArray = Array.prototype.slice.call(elements);
|
2017-02-22 19:29:35 +00:00
|
|
|
elementsArray.forEach((element) => {
|
2018-02-01 21:45:19 +00:00
|
|
|
if (element.getAttribute(attr).match(MEDIA_FILE_REGEX)) {
|
2016-11-16 18:12:36 +00:00
|
|
|
const resolvedFile = resolvePathToFile(element.getAttribute(attr), files);
|
2018-02-01 21:45:19 +00:00
|
|
|
if (resolvedFile && resolvedFile.url) {
|
2016-11-16 18:12:36 +00:00
|
|
|
element.setAttribute(attr, resolvedFile.url);
|
2016-10-25 22:38:20 +00:00
|
|
|
}
|
2016-11-16 18:12:36 +00:00
|
|
|
}
|
2016-10-25 22:38:20 +00:00
|
|
|
});
|
2016-11-16 18:12:36 +00:00
|
|
|
}
|
2016-10-25 22:38:20 +00:00
|
|
|
|
2016-11-16 18:12:36 +00:00
|
|
|
resolveJSAndCSSLinks(files) {
|
|
|
|
const newFiles = [];
|
2017-02-22 19:29:35 +00:00
|
|
|
files.forEach((file) => {
|
2016-11-16 18:12:36 +00:00
|
|
|
const newFile = { ...file };
|
|
|
|
if (file.name.match(/.*\.js$/i)) {
|
|
|
|
newFile.content = this.resolveJSLinksInString(newFile.content, files);
|
|
|
|
} else if (file.name.match(/.*\.css$/i)) {
|
|
|
|
newFile.content = this.resolveCSSLinksInString(newFile.content, files);
|
2016-11-11 22:36:19 +00:00
|
|
|
}
|
2016-11-16 18:12:36 +00:00
|
|
|
newFiles.push(newFile);
|
2016-07-11 19:22:29 +00:00
|
|
|
});
|
2016-11-16 18:12:36 +00:00
|
|
|
return newFiles;
|
|
|
|
}
|
2016-07-11 19:22:29 +00:00
|
|
|
|
2016-11-16 18:12:36 +00:00
|
|
|
resolveJSLinksInString(content, files) {
|
|
|
|
let newContent = content;
|
|
|
|
let jsFileStrings = content.match(STRING_REGEX);
|
|
|
|
jsFileStrings = jsFileStrings || [];
|
2017-02-22 19:29:35 +00:00
|
|
|
jsFileStrings.forEach((jsFileString) => {
|
2018-02-01 21:45:19 +00:00
|
|
|
if (jsFileString.match(MEDIA_FILE_QUOTED_REGEX)) {
|
2016-11-16 18:12:36 +00:00
|
|
|
const filePath = jsFileString.substr(1, jsFileString.length - 2);
|
2020-06-25 06:11:32 +00:00
|
|
|
const quoteCharacter = jsFileString.substr(0, 1);
|
2016-11-16 18:12:36 +00:00
|
|
|
const resolvedFile = resolvePathToFile(filePath, files);
|
2020-06-25 06:11:32 +00:00
|
|
|
|
2016-11-16 18:12:36 +00:00
|
|
|
if (resolvedFile) {
|
|
|
|
if (resolvedFile.url) {
|
2020-06-25 06:11:32 +00:00
|
|
|
newContent = newContent.replace(jsFileString, quoteCharacter + resolvedFile.url + quoteCharacter);
|
2018-05-30 22:23:32 +00:00
|
|
|
} else if (resolvedFile.name.match(PLAINTEXT_FILE_REGEX)) {
|
2016-11-16 18:12:36 +00:00
|
|
|
// could also pull file from API instead of using bloburl
|
|
|
|
const blobURL = getBlobUrl(resolvedFile);
|
|
|
|
this.props.setBlobUrl(resolvedFile, blobURL);
|
2020-06-25 06:11:32 +00:00
|
|
|
|
|
|
|
newContent = newContent.replace(jsFileString, quoteCharacter + blobURL + quoteCharacter);
|
2016-11-16 18:12:36 +00:00
|
|
|
}
|
|
|
|
}
|
2016-11-11 22:36:19 +00:00
|
|
|
}
|
2016-07-12 01:54:08 +00:00
|
|
|
});
|
2017-10-21 21:22:16 +00:00
|
|
|
|
2017-10-26 17:48:01 +00:00
|
|
|
return this.jsPreprocess(newContent);
|
2016-11-16 18:12:36 +00:00
|
|
|
}
|
2016-07-12 01:54:08 +00:00
|
|
|
|
2016-11-16 18:12:36 +00:00
|
|
|
resolveCSSLinksInString(content, files) {
|
|
|
|
let newContent = content;
|
|
|
|
let cssFileStrings = content.match(STRING_REGEX);
|
|
|
|
cssFileStrings = cssFileStrings || [];
|
2017-02-22 19:29:35 +00:00
|
|
|
cssFileStrings.forEach((cssFileString) => {
|
2018-02-01 21:45:19 +00:00
|
|
|
if (cssFileString.match(MEDIA_FILE_QUOTED_REGEX)) {
|
2016-11-16 18:12:36 +00:00
|
|
|
const filePath = cssFileString.substr(1, cssFileString.length - 2);
|
2020-06-25 06:11:32 +00:00
|
|
|
const quoteCharacter = cssFileString.substr(0, 1);
|
2016-11-16 18:12:36 +00:00
|
|
|
const resolvedFile = resolvePathToFile(filePath, files);
|
|
|
|
if (resolvedFile) {
|
|
|
|
if (resolvedFile.url) {
|
2020-06-25 06:11:32 +00:00
|
|
|
newContent = newContent.replace(cssFileString, quoteCharacter + resolvedFile.url + quoteCharacter);
|
2016-11-16 18:12:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return newContent;
|
|
|
|
}
|
2016-08-12 19:50:33 +00:00
|
|
|
|
2016-11-16 18:12:36 +00:00
|
|
|
resolveScripts(sketchDoc, files) {
|
|
|
|
const scriptsInHTML = sketchDoc.getElementsByTagName('script');
|
|
|
|
const scriptsInHTMLArray = Array.prototype.slice.call(scriptsInHTML);
|
2017-02-22 19:29:35 +00:00
|
|
|
scriptsInHTMLArray.forEach((script) => {
|
2016-11-16 18:12:36 +00:00
|
|
|
if (script.getAttribute('src') && script.getAttribute('src').match(NOT_EXTERNAL_LINK_REGEX) !== null) {
|
|
|
|
const resolvedFile = resolvePathToFile(script.getAttribute('src'), files);
|
|
|
|
if (resolvedFile) {
|
|
|
|
if (resolvedFile.url) {
|
|
|
|
script.setAttribute('src', resolvedFile.url);
|
|
|
|
} else {
|
2016-12-09 21:21:43 +00:00
|
|
|
script.setAttribute('data-tag', `${startTag}${resolvedFile.name}`);
|
2016-11-16 18:12:36 +00:00
|
|
|
script.removeAttribute('src');
|
|
|
|
script.innerHTML = resolvedFile.content; // eslint-disable-line
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (!(script.getAttribute('src') && script.getAttribute('src').match(EXTERNAL_LINK_REGEX)) !== null) {
|
2018-03-02 17:06:36 +00:00
|
|
|
script.setAttribute('crossorigin', '');
|
2016-11-16 18:12:36 +00:00
|
|
|
script.innerHTML = this.resolveJSLinksInString(script.innerHTML, files); // eslint-disable-line
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2016-10-06 19:45:26 +00:00
|
|
|
|
2016-11-16 18:12:36 +00:00
|
|
|
resolveStyles(sketchDoc, files) {
|
|
|
|
const inlineCSSInHTML = sketchDoc.getElementsByTagName('style');
|
|
|
|
const inlineCSSInHTMLArray = Array.prototype.slice.call(inlineCSSInHTML);
|
2017-02-22 19:29:35 +00:00
|
|
|
inlineCSSInHTMLArray.forEach((style) => {
|
2016-11-16 18:12:36 +00:00
|
|
|
style.innerHTML = this.resolveCSSLinksInString(style.innerHTML, files); // eslint-disable-line
|
|
|
|
});
|
2016-08-30 22:06:55 +00:00
|
|
|
|
2016-11-16 18:12:36 +00:00
|
|
|
const cssLinksInHTML = sketchDoc.querySelectorAll('link[rel="stylesheet"]');
|
2016-12-07 21:12:06 +00:00
|
|
|
const cssLinksInHTMLArray = Array.prototype.slice.call(cssLinksInHTML);
|
2017-02-22 19:29:35 +00:00
|
|
|
cssLinksInHTMLArray.forEach((css) => {
|
2016-11-16 18:12:36 +00:00
|
|
|
if (css.getAttribute('href') && css.getAttribute('href').match(NOT_EXTERNAL_LINK_REGEX) !== null) {
|
|
|
|
const resolvedFile = resolvePathToFile(css.getAttribute('href'), files);
|
|
|
|
if (resolvedFile) {
|
|
|
|
if (resolvedFile.url) {
|
|
|
|
css.href = resolvedFile.url; // eslint-disable-line
|
|
|
|
} else {
|
|
|
|
const style = sketchDoc.createElement('style');
|
|
|
|
style.innerHTML = `\n${resolvedFile.content}`;
|
2016-12-13 20:37:11 +00:00
|
|
|
sketchDoc.head.appendChild(style);
|
2016-11-16 18:12:36 +00:00
|
|
|
css.parentElement.removeChild(css);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2016-06-23 22:29:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
renderSketch() {
|
2017-02-22 19:29:35 +00:00
|
|
|
const doc = this.iframeElement;
|
2020-09-21 10:22:19 +00:00
|
|
|
// const localFiles = this.injectLocalFiles();
|
2020-10-09 15:21:16 +00:00
|
|
|
// console.trace('renderSketch()', this.props);
|
2020-09-21 10:22:19 +00:00
|
|
|
|
|
|
|
const changedFiles = this.props.files.filter(file => file.changed);
|
|
|
|
const filesRequiringReload = changedFiles.filter(file => !file.content.startsWith('// liveUpdate'));
|
|
|
|
const filesToHotSwap = changedFiles.filter(file => filesRequiringReload.indexOf(file) === -1);
|
2020-10-09 15:21:16 +00:00
|
|
|
// console.log('changed and requiring reload:', changedFiles, filesRequiringReload, filesToHotSwap);
|
2020-09-21 10:22:19 +00:00
|
|
|
|
|
|
|
let saving;
|
2020-10-09 17:06:19 +00:00
|
|
|
console.log('is saving...', this.props);
|
|
|
|
if (changedFiles.length > 0 || this.props.project.updatedAt === '') {
|
2020-09-21 10:22:19 +00:00
|
|
|
saving = this.props.saveProject();
|
2016-07-11 19:22:29 +00:00
|
|
|
} else {
|
2020-09-21 10:22:19 +00:00
|
|
|
// can be done pretier: a promise that always resolves
|
|
|
|
saving = new Promise((resolve, err) => resolve());
|
2016-07-11 19:22:29 +00:00
|
|
|
}
|
2020-09-21 10:22:19 +00:00
|
|
|
|
2020-10-09 17:06:19 +00:00
|
|
|
console.log('saving:', saving);
|
|
|
|
|
2020-10-09 15:21:16 +00:00
|
|
|
if (saving === null) {
|
2020-10-09 17:06:19 +00:00
|
|
|
console.log('Error saving... not authenticated?');
|
2020-10-09 15:21:16 +00:00
|
|
|
this.props.stopSketch(); // prevent crazy loop of renderSketch() through componentDidUpdate()
|
|
|
|
} else {
|
|
|
|
saving.catch(() => {
|
2020-10-09 17:06:19 +00:00
|
|
|
console.log('Error when saving... not authenticated? Redirect!');
|
2020-10-09 15:21:16 +00:00
|
|
|
this.props.stopSketch(); // prevent crazy loop of renderSketch() through componentDidUpdate()
|
2020-10-09 17:06:19 +00:00
|
|
|
// this.props.clearConsole(); // introduces bug: causes nested compnonentDidUpdate()
|
2020-10-09 15:21:16 +00:00
|
|
|
});
|
2020-09-21 10:22:19 +00:00
|
|
|
|
2020-10-09 15:21:16 +00:00
|
|
|
saving.then(() => {
|
2020-10-09 17:06:19 +00:00
|
|
|
console.log('play!', this.props.isPlaying);
|
2020-10-09 15:21:16 +00:00
|
|
|
if (this.props.isPlaying) {
|
|
|
|
doc.removeAttribute('srcdoc');
|
|
|
|
|
2020-10-09 17:06:19 +00:00
|
|
|
if (typeof this.props.project.owner === 'undefined') {
|
|
|
|
console.error('no owner for project?', this.props.project);
|
|
|
|
} else {
|
|
|
|
const source = `${window.location.origin}/${this.props.project.owner.username}/sketches/${this.props.project.id}/index.html`;
|
|
|
|
// console.log('FILES', this.props.files, doc.src, source, lastUpdate);
|
|
|
|
if (doc.src === source) {
|
|
|
|
// const newFiles = this.props.files.filter(file => new Date(file.updatedAt) > lastUpdate);
|
|
|
|
if (this.props.unsavedChanges) {
|
|
|
|
// console.log('unsaved changes');
|
|
|
|
}
|
|
|
|
|
|
|
|
// console.log('doc', doc);
|
|
|
|
// we need a hard reload
|
|
|
|
if (filesRequiringReload.length > 0) {
|
|
|
|
doc.src = source; // for now...
|
|
|
|
} else {
|
|
|
|
// if (doc.contentWindow.document.querySelector('script[src="/assets/hotswap.js"]') == null) {
|
|
|
|
// const headEl = doc.contentWindow.document.querySelector('head');
|
|
|
|
// const srcEl = doc.contentWindow.document.createElement('script');
|
|
|
|
// srcEl.src = '/assets/hotswap.js';
|
|
|
|
// headEl.appendChild(srcEl);
|
|
|
|
// }
|
|
|
|
|
|
|
|
console.log('Hot swap (..append):', filesToHotSwap);
|
|
|
|
const headEl = doc.contentWindow.document.querySelector('head');
|
|
|
|
const updatevar = Date.now();
|
|
|
|
filesToHotSwap.forEach((file) => {
|
|
|
|
// doc.contentWindow.postMessage({ 'action': 'code', 'contents': file.content }, '*');
|
|
|
|
const srcEl = doc.contentWindow.document.createElement('script');
|
|
|
|
srcEl.src = `${file.name}?changed=${updatevar}`;
|
|
|
|
srcEl.onload = 'setupAssets();'; // (re)load assets
|
|
|
|
headEl.appendChild(srcEl);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
// if ( this.props.htmlFile.content === doc.contentWindow)
|
|
|
|
// TODO: don't set, but update only (will be hard... :-P)
|
2020-10-09 15:21:16 +00:00
|
|
|
} else {
|
2020-10-09 17:06:19 +00:00
|
|
|
doc.src = source;
|
2020-10-09 15:21:16 +00:00
|
|
|
}
|
2020-09-21 10:22:19 +00:00
|
|
|
}
|
|
|
|
} else {
|
2020-10-09 15:21:16 +00:00
|
|
|
doc.removeAttribute('src');
|
|
|
|
doc.srcdoc = '';
|
|
|
|
srcDoc.set(doc, ' ');
|
2020-09-21 10:22:19 +00:00
|
|
|
}
|
2020-10-09 17:06:19 +00:00
|
|
|
|
|
|
|
// prevent looping
|
|
|
|
if (this.props.endSketchRefresh) {
|
|
|
|
this.props.endSketchRefresh();
|
|
|
|
}
|
2020-10-09 15:21:16 +00:00
|
|
|
});
|
|
|
|
}
|
2016-06-23 22:29:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2018-10-22 18:59:08 +00:00
|
|
|
const iframeClass = classNames({
|
|
|
|
'preview-frame': true,
|
|
|
|
'preview-frame--full-view': this.props.fullView
|
|
|
|
});
|
2020-06-15 17:14:48 +00:00
|
|
|
const sandboxAttributes =
|
|
|
|
'allow-scripts allow-pointer-lock allow-same-origin allow-popups allow-forms allow-modals allow-downloads';
|
2016-06-27 19:47:48 +00:00
|
|
|
return (
|
|
|
|
<iframe
|
2018-10-18 18:10:37 +00:00
|
|
|
id="canvas_frame"
|
2018-10-22 18:59:08 +00:00
|
|
|
className={iframeClass}
|
2016-08-10 21:24:52 +00:00
|
|
|
aria-label="sketch output"
|
|
|
|
role="main"
|
2016-06-27 19:47:48 +00:00
|
|
|
frameBorder="0"
|
2019-10-31 21:27:23 +00:00
|
|
|
title="sketch preview"
|
2017-02-22 19:29:35 +00:00
|
|
|
ref={(element) => { this.iframeElement = element; }}
|
2020-06-15 17:14:48 +00:00
|
|
|
sandbox={sandboxAttributes}
|
2016-07-17 23:06:43 +00:00
|
|
|
/>
|
2016-06-27 19:47:48 +00:00
|
|
|
);
|
2016-06-23 22:29:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-27 19:47:48 +00:00
|
|
|
PreviewFrame.propTypes = {
|
|
|
|
isPlaying: PropTypes.bool.isRequired,
|
2017-05-31 19:23:30 +00:00
|
|
|
isAccessibleOutputPlaying: PropTypes.bool.isRequired,
|
|
|
|
textOutput: PropTypes.bool.isRequired,
|
|
|
|
gridOutput: PropTypes.bool.isRequired,
|
|
|
|
soundOutput: PropTypes.bool.isRequired,
|
2016-07-11 19:22:29 +00:00
|
|
|
htmlFile: PropTypes.shape({
|
|
|
|
content: PropTypes.string.isRequired
|
2017-02-22 19:29:35 +00:00
|
|
|
}).isRequired,
|
|
|
|
files: PropTypes.arrayOf(PropTypes.shape({
|
|
|
|
content: PropTypes.string.isRequired,
|
|
|
|
name: PropTypes.string.isRequired,
|
|
|
|
url: PropTypes.string,
|
|
|
|
id: PropTypes.string.isRequired
|
|
|
|
})).isRequired,
|
|
|
|
dispatchConsoleEvent: PropTypes.func.isRequired,
|
2016-09-28 22:05:14 +00:00
|
|
|
endSketchRefresh: PropTypes.func.isRequired,
|
|
|
|
previewIsRefreshing: PropTypes.bool.isRequired,
|
2016-10-08 22:52:32 +00:00
|
|
|
fullView: PropTypes.bool,
|
2017-01-11 19:13:49 +00:00
|
|
|
setBlobUrl: PropTypes.func.isRequired,
|
|
|
|
stopSketch: PropTypes.func.isRequired,
|
2019-04-30 21:44:41 +00:00
|
|
|
expandConsole: PropTypes.func.isRequired,
|
2020-09-21 10:22:19 +00:00
|
|
|
saveProject: PropTypes.func.isRequired,
|
|
|
|
project: PropTypes.shape({
|
|
|
|
id: PropTypes.string,
|
|
|
|
name: PropTypes.string.isRequired,
|
|
|
|
owner: PropTypes.shape({
|
|
|
|
username: PropTypes.string,
|
|
|
|
id: PropTypes.string,
|
|
|
|
}),
|
|
|
|
updatedAt: PropTypes.string,
|
|
|
|
}).isRequired,
|
2020-10-09 17:06:19 +00:00
|
|
|
// clearConsole: PropTypes.func,
|
2020-09-21 10:22:19 +00:00
|
|
|
unsavedChanges: PropTypes.bool,
|
2019-05-02 19:30:56 +00:00
|
|
|
cmController: PropTypes.shape({
|
|
|
|
getContent: PropTypes.func
|
2020-06-19 20:50:21 +00:00
|
|
|
}),
|
2016-06-27 19:47:48 +00:00
|
|
|
};
|
|
|
|
|
2017-02-22 19:29:35 +00:00
|
|
|
PreviewFrame.defaultProps = {
|
2019-05-02 19:30:56 +00:00
|
|
|
fullView: false,
|
2020-09-21 10:22:19 +00:00
|
|
|
unsavedChanges: false,
|
2020-10-09 17:06:19 +00:00
|
|
|
cmController: {},
|
2017-02-22 19:29:35 +00:00
|
|
|
};
|
|
|
|
|
2016-06-23 22:29:55 +00:00
|
|
|
export default PreviewFrame;
|