Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/components/browser-modal/browser-modal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
4 changes: 1 addition & 3 deletions src/components/gui/gui.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ const GUIComponent = props => {
costumeLibraryVisible,
costumesTabVisible,
enableCommunity,
hideIntro,
importInfoVisible,
intl,
isPlayerOnly,
Expand Down Expand Up @@ -116,7 +115,7 @@ const GUIComponent = props => {
{...componentProps}
>
{previewInfoVisible ? (
<PreviewModal hideIntro={hideIntro} />
<PreviewModal />
) : null}
{loading ? (
<Loader />
Expand Down Expand Up @@ -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,
Expand Down
12 changes: 9 additions & 3 deletions src/containers/gui.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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
Expand Down
32 changes: 6 additions & 26 deletions src/containers/preview-modal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 (<PreviewModalComponent
isRtl={this.props.isRtl}
previewing={this.state.previewing}
onCancel={this.handleCancel}
onTryIt={this.handleTryIt}
onViewProject={this.handleViewProject}
/>);
}
handleTryIt () {
this.setState({previewing: true});
// try to run in fullscreen mode on tablets.
Expand All @@ -59,18 +38,19 @@ class PreviewModal extends React.Component {
this.props.onViewProject();
}
render () {
return (supportedBrowser() ?
this.introIfShown() :
<BrowserModalComponent
return (
<PreviewModalComponent
isRtl={this.props.isRtl}
onBack={this.handleCancel}
previewing={this.state.previewing}
onCancel={this.handleCancel}
onTryIt={this.handleTryIt}
onViewProject={this.handleViewProject}
/>
);
}
}

PreviewModal.propTypes = {
hideIntro: PropTypes.bool,
isRtl: PropTypes.bool,
onTryIt: PropTypes.func,
onViewProject: PropTypes.func
Expand Down
70 changes: 48 additions & 22 deletions src/lib/app-state-hoc.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,57 +4,83 @@ 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';

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));
}
Expand Down
34 changes: 14 additions & 20 deletions src/playground/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,28 @@ 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('/');

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(<WrappedGui backpackOptions={backpackOptions} />, 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(<WrappedBrowserModalComponent onBack={handleBack} />, appTarget);
}
31 changes: 31 additions & 0 deletions src/playground/render-gui.jsx
Original file line number Diff line number Diff line change
@@ -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(<WrappedGui backpackOptions={backpackOptions} />, appTarget);
};