diff --git a/src/components/browser-modal/browser-modal.jsx b/src/components/browser-modal/browser-modal.jsx index 0a271ea7a78..9afac0e3891 100644 --- a/src/components/browser-modal/browser-modal.jsx +++ b/src/components/browser-modal/browser-modal.jsx @@ -84,4 +84,8 @@ BrowserModal.propTypes = { onBack: PropTypes.func.isRequired }; -export default injectIntl(BrowserModal); +const WrappedBrowserModal = injectIntl(BrowserModal); + +WrappedBrowserModal.setAppElement = ReactModal.setAppElement; + +export default WrappedBrowserModal; diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index 96fea86537f..71c62313509 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -62,7 +62,6 @@ const GUIComponent = props => { costumeLibraryVisible, costumesTabVisible, enableCommunity, - hideIntro, importInfoVisible, intl, isPlayerOnly, @@ -116,7 +115,7 @@ const GUIComponent = props => { {...componentProps} > {previewInfoVisible ? ( - + ) : null} {loading ? ( @@ -282,7 +281,6 @@ GUIComponent.propTypes = { costumeLibraryVisible: PropTypes.bool, costumesTabVisible: PropTypes.bool, enableCommunity: PropTypes.bool, - hideIntro: PropTypes.bool, importInfoVisible: PropTypes.bool, intl: intlShape.isRequired, isPlayerOnly: PropTypes.bool, diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index 6e5d96070b7..f08e6bab746 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -72,10 +72,15 @@ class GUI extends React.Component { `Failed to load project from server [id=${window.location.hash}]: ${this.state.errorMessage}`); } const { + /* eslint-disable no-unused-vars */ + assetHost, + hideIntro, + projectData, + projectHost, + /* eslint-enable no-unused-vars */ children, fetchingProject, loadingStateVisible, - projectData, // eslint-disable-line no-unused-vars vm, ...componentProps } = this.props; @@ -94,6 +99,7 @@ class GUI extends React.Component { GUI.propTypes = { children: PropTypes.node, fetchingProject: PropTypes.bool, + hideIntro: PropTypes.bool, importInfoVisible: PropTypes.bool, loadingStateVisible: PropTypes.bool, onSeeCommunity: PropTypes.func, @@ -102,7 +108,7 @@ GUI.propTypes = { vm: PropTypes.instanceOf(VM) }; -const mapStateToProps = state => ({ +const mapStateToProps = (state, ownProps) => ({ activeTabIndex: state.scratchGui.editorTab.activeTabIndex, backdropLibraryVisible: state.scratchGui.modals.backdropLibrary, blocksTabVisible: state.scratchGui.editorTab.activeTabIndex === BLOCKS_TAB_INDEX, @@ -113,7 +119,7 @@ const mapStateToProps = state => ({ isPlayerOnly: state.scratchGui.mode.isPlayerOnly, isRtl: state.locales.isRtl, loadingStateVisible: state.scratchGui.modals.loadingProject, - previewInfoVisible: state.scratchGui.modals.previewInfo, + previewInfoVisible: state.scratchGui.modals.previewInfo && !ownProps.hideIntro, targetIsStage: ( state.scratchGui.targets.stage && state.scratchGui.targets.stage.id === state.scratchGui.targets.editingTarget diff --git a/src/containers/preview-modal.jsx b/src/containers/preview-modal.jsx index 7145cfa6acb..47b3cc3115e 100644 --- a/src/containers/preview-modal.jsx +++ b/src/containers/preview-modal.jsx @@ -6,8 +6,6 @@ import {connect} from 'react-redux'; import tabletFullScreen from '../lib/tablet-full-screen'; import PreviewModalComponent from '../components/preview-modal/preview-modal.jsx'; -import BrowserModalComponent from '../components/browser-modal/browser-modal.jsx'; -import supportedBrowser from '../lib/supported-browser'; import { closePreviewInfo, @@ -27,25 +25,6 @@ class PreviewModal extends React.Component { previewing: false }; } - - /** - * Conditionally returns an intro modal depending on the hideIntro prop - * @returns { React.Component | null } null if hideIntro is true, the intro modal component otherwise - */ - introIfShown () { - if (this.props.hideIntro) { - return null; // If hideIntro is true, the intro modal should not appear - } - - // otherwise, show the intro modal - return (); - } handleTryIt () { this.setState({previewing: true}); // try to run in fullscreen mode on tablets. @@ -59,18 +38,19 @@ class PreviewModal extends React.Component { this.props.onViewProject(); } render () { - return (supportedBrowser() ? - this.introIfShown() : - ); } } PreviewModal.propTypes = { - hideIntro: PropTypes.bool, isRtl: PropTypes.bool, onTryIt: PropTypes.func, onViewProject: PropTypes.func diff --git a/src/lib/app-state-hoc.jsx b/src/lib/app-state-hoc.jsx index 2f70b44d251..fe7ac023eab 100644 --- a/src/lib/app-state-hoc.jsx +++ b/src/lib/app-state-hoc.jsx @@ -4,7 +4,6 @@ import {Provider} from 'react-redux'; import {createStore, combineReducers, compose} from 'redux'; import ConnectedIntlProvider from './connected-intl-provider.jsx'; -import guiReducer, {guiInitialState, guiMiddleware, initFullScreen, initPlayer} from '../reducers/gui'; import localesReducer, {initLocale, localesInitialState} from '../reducers/locales'; import {setPlayer, setFullScreen} from '../reducers/mode.js'; @@ -12,49 +11,76 @@ import {setPlayer, setFullScreen} from '../reducers/mode.js'; import locales from 'scratch-l10n'; import {detectLocale} from './detect-locale'; -import {ScratchPaintReducer} from 'scratch-paint'; - const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; -const enhancer = composeEnhancers(guiMiddleware); /* * Higher Order Component to provide redux state. If an `intl` prop is provided * it will override the internal `intl` redux state * @param {React.Component} WrappedComponent - component to provide state for + * @param {boolean} localesOnly - only provide the locale state, not everything + * required by the GUI. Used to exclude excess state when + only rendering modals, not the GUI. * @returns {React.Component} component with redux and intl state provided */ -const AppStateHOC = function (WrappedComponent) { +const AppStateHOC = function (WrappedComponent, localesOnly) { class AppStateWrapper extends React.Component { constructor (props) { super(props); - let initializedGui = guiInitialState; - if (props.isFullScreen) { - initializedGui = initFullScreen(initializedGui); - } - if (props.isPlayerOnly) { - initializedGui = initPlayer(initializedGui); - } + let initialState = {}; + let reducers = {}; + let enhancer; let initializedLocales = localesInitialState; const locale = detectLocale(Object.keys(locales)); if (locale !== 'en') { initializedLocales = initLocale(initializedLocales, locale); } + if (localesOnly) { + // Used for instantiating minimal state for the unsupported + // browser modal + reducers = {locales: localesReducer}; + initialState = {locales: initializedLocales}; + enhancer = composeEnhancers(); + } else { + // You are right, this is gross. But it's necessary to avoid + // importing unneeded code that will crash unsupported browsers. + const guiRedux = require('../reducers/gui'); + const guiReducer = guiRedux.default; + const { + guiInitialState, + guiMiddleware, + initFullScreen, + initPlayer + } = guiRedux; + const {ScratchPaintReducer} = require('scratch-paint'); - const reducer = combineReducers({ - locales: localesReducer, - scratchGui: guiReducer, - scratchPaint: ScratchPaintReducer - }); - this.store = createStore( - reducer, - { + let initializedGui = guiInitialState; + if (props.isFullScreen) { + initializedGui = initFullScreen(initializedGui); + } + if (props.isPlayerOnly) { + initializedGui = initPlayer(initializedGui); + } + reducers = { + locales: localesReducer, + scratchGui: guiReducer, + scratchPaint: ScratchPaintReducer + }; + initialState = { locales: initializedLocales, scratchGui: initializedGui - }, - enhancer); + }; + enhancer = composeEnhancers(guiMiddleware); + } + const reducer = combineReducers(reducers); + this.store = createStore( + reducer, + initialState, + enhancer + ); } componentDidUpdate (prevProps) { + if (localesOnly) return; if (prevProps.isPlayerOnly !== this.props.isPlayerOnly) { this.store.dispatch(setPlayer(this.props.isPlayerOnly)); } diff --git a/src/playground/index.jsx b/src/playground/index.jsx index da2be7a2783..ba0ada4da78 100644 --- a/src/playground/index.jsx +++ b/src/playground/index.jsx @@ -7,17 +7,12 @@ import React from 'react'; import ReactDOM from 'react-dom'; import analytics from '../lib/analytics'; -import GUI from '../containers/gui.jsx'; -import HashParserHOC from '../lib/hash-parser-hoc.jsx'; import AppStateHOC from '../lib/app-state-hoc.jsx'; +import BrowserModalComponent from '../components/browser-modal/browser-modal.jsx'; +import supportedBrowser from '../lib/supported-browser'; import styles from './index.css'; -if (process.env.NODE_ENV === 'production' && typeof window === 'object') { - // Warn before navigating away - window.onbeforeunload = () => true; -} - // Register "base" page view analytics.pageview('/'); @@ -25,16 +20,15 @@ const appTarget = document.createElement('div'); appTarget.className = styles.app; document.body.appendChild(appTarget); -GUI.setAppElement(appTarget); -const WrappedGui = HashParserHOC(AppStateHOC(GUI)); - -// TODO a hack for testing the backpack, allow backpack host to be set by url param -const backpackHostMatches = window.location.href.match(/[?&]backpack_host=([^&]*)&?/); -const backpackHost = backpackHostMatches ? backpackHostMatches[1] : null; - -const backpackOptions = { - visible: true, - host: backpackHost -}; - -ReactDOM.render(, appTarget); +if (supportedBrowser()) { + // require needed here to avoid importing unsupported browser-crashing code + // at the top level + require('./render-gui.jsx').default(appTarget); + +} else { + BrowserModalComponent.setAppElement(appTarget); + const WrappedBrowserModalComponent = AppStateHOC(BrowserModalComponent, true /* localesOnly */); + const handleBack = () => {}; + // eslint-disable-next-line react/jsx-no-bind + ReactDOM.render(, appTarget); +} diff --git a/src/playground/render-gui.jsx b/src/playground/render-gui.jsx new file mode 100644 index 00000000000..adec922b8c2 --- /dev/null +++ b/src/playground/render-gui.jsx @@ -0,0 +1,31 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +import AppStateHOC from '../lib/app-state-hoc.jsx'; +import GUI from '../containers/gui.jsx'; +import HashParserHOC from '../lib/hash-parser-hoc.jsx'; + +/* + * Render the GUI playground. This is a separate function because importing anything + * that instantiates the VM causes unsupported browsers to crash + * {object} appTarget - the DOM element to render to + */ +export default appTarget => { + GUI.setAppElement(appTarget); + const WrappedGui = HashParserHOC(AppStateHOC(GUI)); + + // TODO a hack for testing the backpack, allow backpack host to be set by url param + const backpackHostMatches = window.location.href.match(/[?&]backpack_host=([^&]*)&?/); + const backpackHost = backpackHostMatches ? backpackHostMatches[1] : null; + + const backpackOptions = { + visible: true, + host: backpackHost + }; + if (process.env.NODE_ENV === 'production' && typeof window === 'object') { + // Warn before navigating away + window.onbeforeunload = () => true; + } + + ReactDOM.render(, appTarget); +};