From c684281880c60b967a0eed648fed4a2fc548c2a8 Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Thu, 4 Feb 2021 15:00:56 -0500 Subject: [PATCH 01/43] Start creating preview server --- .../modules/IDE/components/PreviewFrame.jsx | 25 +++++++++---------- client/modules/IDE/reducers/files.js | 7 +++--- client/previewIndex.jsx | 17 +++++++++++++ server/domain-objects/createDefaultFiles.js | 4 +-- server/previewServer.js | 17 +++++++++++++ server/scripts/examples.js | 4 +-- server/views/previewIndex.js | 16 ++++++++++++ 7 files changed, 69 insertions(+), 21 deletions(-) create mode 100644 client/previewIndex.jsx create mode 100644 server/previewServer.js create mode 100644 server/views/previewIndex.js diff --git a/client/modules/IDE/components/PreviewFrame.jsx b/client/modules/IDE/components/PreviewFrame.jsx index 2762cd0bac..da9411ada8 100644 --- a/client/modules/IDE/components/PreviewFrame.jsx +++ b/client/modules/IDE/components/PreviewFrame.jsx @@ -79,10 +79,10 @@ class PreviewFrame extends React.Component { componentWillUnmount() { window.removeEventListener('message', this.handleConsoleEvent); - const iframeBody = this.iframeElement.contentDocument.body; - if (iframeBody) { - ReactDOM.unmountComponentAtNode(iframeBody); - } + // const iframeBody = this.iframeElement.contentDocument.body; + // if (iframeBody) { + // ReactDOM.unmountComponentAtNode(iframeBody); + // } } handleConsoleEvent(messageEvent) { @@ -282,13 +282,12 @@ class PreviewFrame extends React.Component { ); } else if (resolvedFile.name.match(PLAINTEXT_FILE_REGEX)) { // could also pull file from API instead of using bloburl - const blobURL = getBlobUrl(resolvedFile); - this.props.setBlobUrl(resolvedFile, blobURL); - - newContent = newContent.replace( - jsFileString, - quoteCharacter + blobURL + quoteCharacter - ); + // const blobURL = getBlobUrl(resolvedFile); + // this.props.setBlobUrl(resolvedFile, blobURL); + // newContent = newContent.replace( + // jsFileString, + // quoteCharacter + blobURL + quoteCharacter + // ); } } } @@ -402,7 +401,7 @@ class PreviewFrame extends React.Component { 'preview-frame--full-view': this.props.fullView }); const sandboxAttributes = - 'allow-scripts allow-pointer-lock allow-same-origin allow-popups allow-forms allow-modals allow-downloads'; + 'allow-scripts allow-pointer-lock allow-popups allow-forms allow-modals allow-downloads'; return ( `} + value={``} /> { res.send(renderPreviewIndex()); }); +app.use('/', embedRoutes); + app.listen(process.env.PREVIEW_PORT, (error) => { if (!error) { console.log( diff --git a/server/routes/redirectEmbed.routes.js b/server/routes/redirectEmbed.routes.js new file mode 100644 index 0000000000..d0450dfc22 --- /dev/null +++ b/server/routes/redirectEmbed.routes.js @@ -0,0 +1,21 @@ +import { Router } from 'express'; + +const router = new Router(); +const previewUrl = process.env.PREVIEW_URL; + +router.get('/:username/embed/:project_id', (req, res) => { + const { username, project_id: projectId } = req.params; + res.redirect(301, `${previewUrl}/${username}/embed/${projectId}`); +}); + +router.get('/:username/present/:project_id', (req, res) => { + const { username, project_id: projectId } = req.params; + res.redirect(301, `${previewUrl}/${username}/present/${projectId}`); +}); + +router.get('/embed/:project_id', (req, res) => { + const { project_id: projectId } = req.params; + res.redirect(301, `${previewUrl}/embed/${projectId}`); +}); + +export default router; diff --git a/server/server.js b/server/server.js index df6bc3ae63..f826193987 100644 --- a/server/server.js +++ b/server/server.js @@ -24,7 +24,7 @@ import files from './routes/file.routes'; import collections from './routes/collection.routes'; import aws from './routes/aws.routes'; import serverRoutes from './routes/server.routes'; -import embedRoutes from './routes/embed.routes'; +import redirectEmbedRoutes from './routes/redirectEmbed.routes'; import assetRoutes from './routes/asset.routes'; import passportRoutes from './routes/passport.routes'; import { requestsOfTypeJSON } from './utils/requestsOfType'; @@ -143,7 +143,7 @@ app.use('/', serverRoutes); app.use(assetRoutes); -app.use('/', embedRoutes); +app.use('/', redirectEmbedRoutes); app.use('/', passportRoutes); // configure passport From d9125028daba9edc1c79f528f67af5bbf16c38c6 Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Tue, 13 Apr 2021 17:35:53 -0400 Subject: [PATCH 13/43] Fix full view by waiting until frame registers --- client/modules/IDE/hooks/useInterval.js | 26 ++++++ client/modules/IDE/pages/FullView.jsx | 107 +++++++++++++----------- client/modules/Preview/previewIndex.jsx | 14 +++- client/utils/dispatcher.js | 5 +- 4 files changed, 99 insertions(+), 53 deletions(-) create mode 100644 client/modules/IDE/hooks/useInterval.js diff --git a/client/modules/IDE/hooks/useInterval.js b/client/modules/IDE/hooks/useInterval.js new file mode 100644 index 0000000000..63ddaf5503 --- /dev/null +++ b/client/modules/IDE/hooks/useInterval.js @@ -0,0 +1,26 @@ +// https://overreacted.io/making-setinterval-declarative-with-react-hooks/ +import { useState, useEffect, useRef } from 'react'; + +export default function useInterval(callback, delay) { + const savedCallback = useRef(); + const [intervalId, setIntervalId] = useState(); + + // Remember the latest callback. + useEffect(() => { + savedCallback.current = callback; + }, [callback]); + + // Set up the interval. + useEffect(() => { + function tick() { + savedCallback.current(); + } + if (delay !== null) { + const id = setInterval(tick, delay); + setIntervalId(id); + return () => clearInterval(id); + } + return null; + }, [delay]); + return () => clearInterval(intervalId); +} diff --git a/client/modules/IDE/pages/FullView.jsx b/client/modules/IDE/pages/FullView.jsx index 65ac72da26..dcbd44edd7 100644 --- a/client/modules/IDE/pages/FullView.jsx +++ b/client/modules/IDE/pages/FullView.jsx @@ -1,68 +1,75 @@ import PropTypes from 'prop-types'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; import Helmet from 'react-helmet'; -import { bindActionCreators } from 'redux'; -import { connect } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import PreviewFrame from '../components/PreviewFrame'; import PreviewNav from '../../../components/PreviewNav'; import { getProject } from '../actions/project'; import { startSketch } from '../actions/ide'; +import { + listen, + dispatchMessage, + MessageTypes +} from '../../../utils/dispatcher'; +import useInterval from '../hooks/useInterval'; -class FullView extends React.Component { - componentDidMount() { - this.props - .getProject(this.props.params.project_id, this.props.params.username) - .then(this.props.startSketch); +function FullView(props) { + const dispatch = useDispatch(); + const project = useSelector((state) => state.project); + + useEffect(() => { + dispatch(getProject(props.params.project_id, props.params.username)); + }, []); + + // send register event until iframe is loaded and sends a message back. + const [isRendered, setIsRendered] = useState(false); + const clearInterval = useInterval(() => { + dispatchMessage({ type: MessageTypes.REGISTER }); + }, 100); + if (isRendered) { + clearInterval(); } - render() { - return ( -
- - {this.props.project.name} - - -
- -
-
- ); + function handleMessageEvent(message) { + if (message.type === MessageTypes.REGISTER) { + if (!isRendered) { + setIsRendered(true); + dispatch(startSketch()); + } + } } + useEffect(() => { + const unsubscribe = listen(handleMessageEvent); + return function cleanup() { + unsubscribe(); + }; + }, []); + return ( +
+ + {project.name} + + +
+ +
+
+ ); } FullView.propTypes = { params: PropTypes.shape({ project_id: PropTypes.string, username: PropTypes.string - }).isRequired, - project: PropTypes.shape({ - name: PropTypes.string, - owner: PropTypes.shape({ - username: PropTypes.string - }) - }).isRequired, - getProject: PropTypes.func.isRequired, - startSketch: PropTypes.func.isRequired + }).isRequired }; -function mapStateToProps(state) { - return { - project: state.project - }; -} - -function mapDispatchToProps(dispatch) { - return bindActionCreators({ getProject, startSketch }, dispatch); -} - -export default connect(mapStateToProps, mapDispatchToProps)(FullView); +export default FullView; diff --git a/client/modules/Preview/previewIndex.jsx b/client/modules/Preview/previewIndex.jsx index ae13ca5a13..a2a6a0ff8a 100644 --- a/client/modules/Preview/previewIndex.jsx +++ b/client/modules/Preview/previewIndex.jsx @@ -2,9 +2,15 @@ import React, { useReducer, useState, useEffect } from 'react'; import { render } from 'react-dom'; import { hot } from 'react-hot-loader/root'; import { createGlobalStyle } from 'styled-components'; -import { listen, MessageTypes } from '../../utils/dispatcher'; +import { + registerFrame, + listen, + MessageTypes, + dispatchMessage +} from '../../utils/dispatcher'; import { filesReducer, initialState, setFiles } from './filesReducer'; import EmbedFrame from './EmbedFrame'; +import getConfig from '../../utils/getConfig'; const GlobalStyle = createGlobalStyle` body { @@ -15,6 +21,8 @@ const GlobalStyle = createGlobalStyle` const App = () => { const [state, dispatch] = useReducer(filesReducer, [], initialState); const [isPlaying, setIsPlaying] = useState(false); + registerFrame(window.parent, getConfig('EDITOR_URL')); + function handleMessageEvent(message) { const { type, payload } = message; switch (type) { @@ -27,10 +35,14 @@ const App = () => { case MessageTypes.STOP: setIsPlaying(false); break; + case MessageTypes.REGISTER: + dispatchMessage({ type: MessageTypes.REGISTER }); + break; default: break; } } + useEffect(() => { const unsubscribe = listen(handleMessageEvent); return function cleanup() { diff --git a/client/utils/dispatcher.js b/client/utils/dispatcher.js index b29b1f8e77..255873e2d7 100644 --- a/client/utils/dispatcher.js +++ b/client/utils/dispatcher.js @@ -9,7 +9,8 @@ let origin = null; export const MessageTypes = { START: 'START', STOP: 'STOP', - FILES: 'FILES' + FILES: 'FILES', + REGISTER: 'REGISTER' }; // could instead register multiple frames here @@ -36,7 +37,7 @@ function notifyFrame(message) { export function dispatchMessage(message) { if (!message) return; - notifyListener(message); + // notifyListener(message); notifyFrame(message); } From 2252989bcbfadec75fe9bfb6650ea45a1aecd138 Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Tue, 13 Apr 2021 18:10:42 -0400 Subject: [PATCH 14/43] Move PreviewFrame to styled components --- client/modules/IDE/components/PreviewFrame.jsx | 18 ++++++++++-------- client/styles/components/_preview-frame.scss | 11 ----------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/client/modules/IDE/components/PreviewFrame.jsx b/client/modules/IDE/components/PreviewFrame.jsx index e44f760da8..a9fa730423 100644 --- a/client/modules/IDE/components/PreviewFrame.jsx +++ b/client/modules/IDE/components/PreviewFrame.jsx @@ -1,9 +1,16 @@ import React, { useRef, useEffect } from 'react'; import PropTypes from 'prop-types'; -import classNames from 'classnames'; +import styled from 'styled-components'; import getConfig from '../../../utils/getConfig'; import { registerFrame } from '../../../utils/dispatcher'; +const Frame = styled.iframe` + min-height: 100%; + min-width: 100%; + position: ${(props) => (props.fullView ? 'relative' : 'absolute')}; + border-width: 0; +`; + function PreviewFrame({ fullView }) { const iframe = useRef(); const previewUrl = getConfig('PREVIEW_URL'); @@ -14,22 +21,17 @@ function PreviewFrame({ fullView }) { }; }); - // TODO move this to styled components - const iframeClass = classNames({ - 'preview-frame': true, - 'preview-frame--full-view': fullView - }); const frameUrl = previewUrl; const sandboxAttributes = 'allow-scripts allow-pointer-lock allow-popups allow-forms allow-modals allow-downloads allow-same-origin'; return ( -