From 775173661fade65b7f8ef0d65a79d6514ecc1de5 Mon Sep 17 00:00:00 2001 From: Ben Wheeler Date: Tue, 17 Sep 2019 21:49:48 -0400 Subject: [PATCH] embed view with minimal functionality, route --- src/routes.json | 10 +- src/views/preview/embed-view.jsx | 154 +++++++++++++++++++++++++++++ src/views/preview/embed.jsx | 29 ++++++ src/views/preview/project-view.jsx | 5 +- 4 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 src/views/preview/embed-view.jsx create mode 100644 src/views/preview/embed.jsx diff --git a/src/routes.json b/src/routes.json index c1494768f..4cfeb3963 100644 --- a/src/routes.json +++ b/src/routes.json @@ -177,12 +177,20 @@ }, { "name": "projects", - "pattern": "^/projects(/editor|(/\\d+(/editor|/fullscreen|/embed)?)?)?/?(\\?.*)?$", + "pattern": "^/projects(/editor|(/\\d+(/editor|/fullscreen)?)?)?/?(\\?.*)?$", "routeAlias": "/projects/?$", "view": "preview/preview", "title": "Scratch Project", "dynamicMetaTags": true }, + { + "name": "projects", + "pattern": "^/projects/\\d+/embed/?(\\?.*)?$", + "routeAlias": "/projects/?$", + "view": "preview/embed", + "title": "Scratch Project", + "dynamicMetaTags": true + }, { "name": "parents", "pattern": "^/parents/?(\\?.*)?$", diff --git a/src/views/preview/embed-view.jsx b/src/views/preview/embed-view.jsx new file mode 100644 index 000000000..94d6afe5c --- /dev/null +++ b/src/views/preview/embed-view.jsx @@ -0,0 +1,154 @@ +// embed view + +const bindAll = require('lodash.bindall'); +const React = require('react'); +const PropTypes = require('prop-types'); +const connect = require('react-redux').connect; +const injectIntl = require('react-intl').injectIntl; + +const Page = require('../../components/page/www/page.jsx'); +const storage = require('../../lib/storage.js').default; +const jar = require('../../lib/jar.js'); +const projectShape = require('./projectshape.jsx').projectShape; +const NotAvailable = require('../../components/not-available/not-available.jsx'); +const Meta = require('./meta.jsx'); + +const sessionActions = require('../../redux/session.js'); +const previewActions = require('../../redux/preview.js'); + +const GUI = require('scratch-gui'); +const IntlGUI = injectIntl(GUI.default); + +const Sentry = require('@sentry/browser'); +if (`${process.env.SENTRY_DSN}` !== '') { + Sentry.init({ + dsn: `${process.env.SENTRY_DSN}`, + // Do not collect global onerror, only collect specifically from React error boundaries. + // TryCatch plugin also includes errors from setTimeouts (i.e. the VM) + integrations: integrations => integrations.filter(i => + !(i.name === 'GlobalHandlers' || i.name === 'TryCatch')) + }); + window.Sentry = Sentry; // Allow GUI access to Sentry via window +} + +class EmbedView extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + ]); + const pathname = window.location.pathname.toLowerCase(); + const parts = pathname.split('/').filter(Boolean); + this.state = { + extensions: [], + invalidProject: parts.length === 1, + projectId: parts[1] + }; + } + componentDidUpdate (prevProps) { + if (this.state.projectId > 0 && + ((this.props.sessionStatus !== prevProps.sessionStatus && + this.props.sessionStatus === sessionActions.Status.FETCHED))) { + this.props.getProjectInfo(this.state.projectId); + this.getProjectData(this.state.projectId, true /* Show cloud/username alerts */); + } + } + getProjectData (projectId) { + if (projectId <= 0) return 0; + storage + .load(storage.AssetType.Project, projectId, storage.DataFormat.JSON) + .then(projectAsset => { // NOTE: this is turning up null, breaking the line below. + let input = projectAsset.data; + if (typeof input === 'object' && !(input instanceof ArrayBuffer) && + !ArrayBuffer.isView(input)) { // taken from scratch-vm + // If the input is an object and not any ArrayBuffer + // or an ArrayBuffer view (this includes all typed arrays and DataViews) + // turn the object into a JSON string, because we suspect + // this is a project.json as an object + // validate expects a string or buffer as input + // TODO not sure if we need to check that it also isn't a data view + input = JSON.stringify(input); + } + }); + } + handleSetLanguage (locale) { + jar.set('scratchlanguage', locale); + } + render () { + if (this.props.projectNotAvailable || this.state.invalidProject) { + return ( + +
+ +
+
+ ); + } + + return ( + + + + + + + ); + } +} + +EmbedView.propTypes = { + assetHost: PropTypes.string.isRequired, + getProjectInfo: PropTypes.func.isRequired, + projectHost: PropTypes.string.isRequired, + projectInfo: projectShape, + projectNotAvailable: PropTypes.bool, + sessionStatus: PropTypes.string +}; + +EmbedView.defaultProps = { + assetHost: process.env.ASSET_HOST, + projectHost: process.env.PROJECT_HOST +}; + +const mapStateToProps = state => ({ + projectInfo: state.preview.projectInfo, + projectNotAvailable: state.preview.projectNotAvailable +}); + +const mapDispatchToProps = dispatch => ({ + getProjectInfo: (id, token) => { + dispatch(previewActions.getProjectInfo(id, token)); + } +}); + +module.exports.View = connect( + mapStateToProps, + mapDispatchToProps +)(EmbedView); + +// initialize GUI by calling its reducer functions depending on URL +GUI.setAppElement(document.getElementById('app')); +module.exports.initGuiState = guiInitialState => { + const pathname = window.location.pathname.toLowerCase(); + const parts = pathname.split('/').filter(Boolean); + // parts[0]: 'projects' + // parts[1]: either :id or 'editor' + // parts[2]: undefined if no :id, otherwise either 'editor', 'fullscreen' or 'embed' + if (parts.indexOf('embed') !== -1) { + guiInitialState = GUI.initEmbedded(guiInitialState); + } + return guiInitialState; +}; + +module.exports.guiReducers = GUI.guiReducers; +module.exports.guiInitialState = GUI.guiInitialState; +module.exports.guiMiddleware = GUI.guiMiddleware; +module.exports.initLocale = GUI.initLocale; +module.exports.localesInitialState = GUI.localesInitialState; diff --git a/src/views/preview/embed.jsx b/src/views/preview/embed.jsx new file mode 100644 index 000000000..278fe3591 --- /dev/null +++ b/src/views/preview/embed.jsx @@ -0,0 +1,29 @@ +// preview view can show either project page or editor page; +// idea is that we shouldn't require a page reload to switch back and forth +const React = require('react'); +const Page = require('../../components/page/www/page.jsx'); +const render = require('../../lib/render.jsx'); + +const previewActions = require('../../redux/preview.js'); + +const isSupportedBrowser = require('../../lib/supported-browser').default; +const UnsupportedBrowser = require('./unsupported-browser.jsx'); + +if (isSupportedBrowser()) { + const EmbedView = require('./embed-view.jsx'); + render( + , + document.getElementById('app'), + { + preview: previewActions.previewReducer, + ...EmbedView.guiReducers + }, + { + locales: EmbedView.initLocale(EmbedView.localesInitialState, window._locale), + scratchGui: EmbedView.initGuiState(EmbedView.guiInitialState) + }, + EmbedView.guiMiddleware + ); +} else { + render(, document.getElementById('app')); +} diff --git a/src/views/preview/project-view.jsx b/src/views/preview/project-view.jsx index f31353bc1..97ffd7fd3 100644 --- a/src/views/preview/project-view.jsx +++ b/src/views/preview/project-view.jsx @@ -1092,16 +1092,13 @@ module.exports.initGuiState = guiInitialState => { const parts = pathname.split('/').filter(Boolean); // parts[0]: 'projects' // parts[1]: either :id or 'editor' - // parts[2]: undefined if no :id, otherwise either 'editor', 'fullscreen' or 'embed' + // parts[2]: undefined if no :id, otherwise either 'editor' or 'fullscreen' if (parts.indexOf('editor') === -1) { guiInitialState = GUI.initPlayer(guiInitialState); } if (parts.indexOf('fullscreen') !== -1) { guiInitialState = GUI.initFullScreen(guiInitialState); } - if (parts.indexOf('embed') !== -1) { - guiInitialState = GUI.initEmbedded(guiInitialState); - } return guiInitialState; };