diff --git a/src/lib/sentry.js b/src/lib/sentry.js
new file mode 100644
index 000000000..b16cd03a9
--- /dev/null
+++ b/src/lib/sentry.js
@@ -0,0 +1,18 @@
+const initSentry = () => {
+ // initialize Sentry instance, making sure it hasn't been initialized already
+ if (!window.Sentry && `${process.env.SENTRY_DSN}` !== '') {
+ const Sentry = require('@sentry/browser');
+
+ 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
+ }
+};
+
+module.exports = initSentry;
diff --git a/src/routes.json b/src/routes.json
index 4af0ff35b..d4e473daf 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": "embed",
+ "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..be3184d5b
--- /dev/null
+++ b/src/views/preview/embed-view.jsx
@@ -0,0 +1,97 @@
+// embed view
+
+const React = require('react');
+const PropTypes = require('prop-types');
+const connect = require('react-redux').connect;
+const injectIntl = require('react-intl').injectIntl;
+
+const ErrorBoundary = require('../../components/errorboundary/errorboundary.jsx');
+const projectShape = require('./projectshape.jsx').projectShape;
+const NotAvailable = require('../../components/not-available/not-available.jsx');
+const Meta = require('./meta.jsx');
+
+const previewActions = require('../../redux/preview.js');
+
+const GUI = require('scratch-gui');
+const IntlGUI = injectIntl(GUI.default);
+
+const initSentry = require('../../lib/sentry.js');
+initSentry();
+
+class EmbedView extends React.Component {
+ constructor (props) {
+ super(props);
+ const pathname = window.location.pathname.toLowerCase();
+ const parts = pathname.split('/').filter(Boolean);
+ this.state = {
+ extensions: [],
+ invalidProject: parts.length === 1,
+ projectId: parts[1]
+ };
+ }
+ componentDidMount () {
+ this.props.getProjectInfo(this.state.projectId);
+ }
+ 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
+};
+
+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);
+
+GUI.setAppElement(document.getElementById('app'));
+module.exports.initGuiState = GUI.initEmbedded;
+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..f458948be
--- /dev/null
+++ b/src/views/preview/embed.jsx
@@ -0,0 +1,30 @@
+const React = require('react');
+const ErrorBoundary = require('../../components/errorboundary/errorboundary.jsx');
+const render = require('../../lib/render.jsx');
+
+// Require this even though we don't use it because, without it, webpack runs out of memory...
+const Page = require('../../components/page/www/page.jsx'); // eslint-disable-line no-unused-vars
+
+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..c41ec84ee 100644
--- a/src/views/preview/project-view.jsx
+++ b/src/views/preview/project-view.jsx
@@ -35,17 +35,8 @@ const IntlGUI = injectIntl(GUI.default);
const localStorageAvailable = 'localStorage' in window && window.localStorage !== null;
-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
-}
+const initSentry = require('../../lib/sentry.js');
+initSentry();
class Preview extends React.Component {
constructor (props) {
@@ -258,7 +249,7 @@ class Preview extends React.Component {
// 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);
+ input = JSON.stringify(input); // NOTE: what is the point of doing this??
}
parser(projectAsset.data, false, (err, projectData) => {
if (err) {
@@ -1092,16 +1083,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;
};