diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index 872950a211c1bd3e229651930659e9dcbe39af75..ad02b2feda7d185e3cd468347363b93a77e58ac7 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -48,6 +48,7 @@ const GUIComponent = props => { cardsVisible, children, costumesTabVisible, + enableCommunity, importInfoVisible, intl, isPlayerOnly, @@ -108,7 +109,7 @@ const GUIComponent = props => { {cardsVisible ? ( <Cards /> ) : null} - <MenuBar /> + <MenuBar enableCommunity={enableCommunity} /> <Box className={styles.bodyWrapper}> <Box className={styles.flexWrapper}> <Box className={styles.editorWrapper}> @@ -224,6 +225,7 @@ GUIComponent.propTypes = { cardsVisible: PropTypes.bool, children: PropTypes.node, costumesTabVisible: PropTypes.bool, + enableCommunity: PropTypes.bool, importInfoVisible: PropTypes.bool, intl: intlShape.isRequired, isPlayerOnly: PropTypes.bool, diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx index 8a5fb5bb7dd099f35b5ee83ad0309242a62be961..7742e210e362ac0d1772e98c447cb792ecec8a46 100644 --- a/src/components/menu-bar/menu-bar.jsx +++ b/src/components/menu-bar/menu-bar.jsx @@ -15,6 +15,7 @@ import {MenuItem, MenuSection} from '../menu/menu.jsx'; import ProjectSaver from '../../containers/project-saver.jsx'; import {openTipsLibrary} from '../../reducers/modals'; +import {setPlayer} from '../../reducers/mode'; import { openFileMenu, closeFileMenu, @@ -263,19 +264,33 @@ const MenuBar = props => ( </MenuBarItemTooltip> </div> <div className={classNames(styles.menuBarItem, styles.communityButtonWrapper)}> - <MenuBarItemTooltip id="community-button"> + {props.enableCommunity ? <Button className={classNames(styles.communityButton)} iconClassName={styles.communityButtonIcon} iconSrc={communityIcon} + onClick={props.onSeeCommunity} > <FormattedMessage defaultMessage="See Community" description="Label for see community button" id="gui.menuBar.seeCommunity" /> - </Button> - </MenuBarItemTooltip> + </Button> : + <MenuBarItemTooltip id="community-button"> + <Button + className={classNames(styles.communityButton)} + iconClassName={styles.communityButtonIcon} + iconSrc={communityIcon} + > + <FormattedMessage + defaultMessage="See Community" + description="Label for see community button" + id="gui.menuBar.seeCommunity" + /> + </Button> + </MenuBarItemTooltip> + } </div> </div> <div className={classNames(styles.menuBarItem, styles.feedbackButtonWrapper)}> @@ -352,12 +367,14 @@ const MenuBar = props => ( MenuBar.propTypes = { editMenuOpen: PropTypes.bool, + enableCommunity: PropTypes.bool, fileMenuOpen: PropTypes.bool, onClickEdit: PropTypes.func, onClickFile: PropTypes.func, onOpenTipLibrary: PropTypes.func, onRequestCloseEdit: PropTypes.func, - onRequestCloseFile: PropTypes.func + onRequestCloseFile: PropTypes.func, + onSeeCommunity: PropTypes.func }; const mapStateToProps = state => ({ @@ -370,7 +387,8 @@ const mapDispatchToProps = dispatch => ({ onClickFile: () => dispatch(openFileMenu()), onRequestCloseFile: () => dispatch(closeFileMenu()), onClickEdit: () => dispatch(openEditMenu()), - onRequestCloseEdit: () => dispatch(closeEditMenu()) + onRequestCloseEdit: () => dispatch(closeEditMenu()), + onSeeCommunity: () => dispatch(setPlayer(true)) }); export default connect( diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx index 20c0c19fcefba50ecd5bc30391b423b84bcddb10..b275b463f6cfaf868e03befa4699a017b2581d80 100644 --- a/src/containers/blocks.jsx +++ b/src/containers/blocks.jsx @@ -445,13 +445,15 @@ Blocks.defaultProps = { }; const mapStateToProps = state => ({ - anyModalVisible: Object.keys(state.modals).some(key => state.modals[key]) || - state.mode.isFullScreen, - extensionLibraryVisible: state.modals.extensionLibrary, + anyModalVisible: ( + Object.keys(state.scratchGui.modals).some(key => state.scratchGui.modals[key]) || + state.scratchGui.mode.isFullScreen + ), + extensionLibraryVisible: state.scratchGui.modals.extensionLibrary, locale: state.intl.locale, messages: state.intl.messages, - toolboxXML: state.toolbox.toolboxXML, - customProceduresVisible: state.customProcedures.active + toolboxXML: state.scratchGui.toolbox.toolboxXML, + customProceduresVisible: state.scratchGui.customProcedures.active }); const mapDispatchToProps = dispatch => ({ diff --git a/src/containers/cards.jsx b/src/containers/cards.jsx index 3d08932f775894aa809014deb85b2cbaca78a93c..5dc081f7b96f883cdbe07cd05ec1d23ce6c2f76c 100644 --- a/src/containers/cards.jsx +++ b/src/containers/cards.jsx @@ -17,13 +17,13 @@ import { import CardsComponent from '../components/cards/cards.jsx'; const mapStateToProps = state => ({ - visible: state.cards.visible, - content: state.cards.content, - activeDeckId: state.cards.activeDeckId, - step: state.cards.step, - x: state.cards.x, - y: state.cards.y, - dragging: state.cards.dragging + visible: state.scratchGui.cards.visible, + content: state.scratchGui.cards.content, + activeDeckId: state.scratchGui.cards.activeDeckId, + step: state.scratchGui.cards.step, + x: state.scratchGui.cards.x, + y: state.scratchGui.cards.y, + dragging: state.scratchGui.cards.dragging }); const mapDispatchToProps = dispatch => ({ diff --git a/src/containers/costume-tab.jsx b/src/containers/costume-tab.jsx index 8f203e63352c6709ec0b171071a6a38417e74e3c..3ce034caa200b15cac7e7f7bc8042fdf9843d2f3 100644 --- a/src/containers/costume-tab.jsx +++ b/src/containers/costume-tab.jsx @@ -337,12 +337,12 @@ CostumeTab.propTypes = { }; const mapStateToProps = state => ({ - editingTarget: state.targets.editingTarget, - sprites: state.targets.sprites, - stage: state.targets.stage, - cameraModalVisible: state.modals.cameraCapture, - costumeLibraryVisible: state.modals.costumeLibrary, - backdropLibraryVisible: state.modals.backdropLibrary + editingTarget: state.scratchGui.targets.editingTarget, + sprites: state.scratchGui.targets.sprites, + stage: state.scratchGui.targets.stage, + cameraModalVisible: state.scratchGui.modals.cameraCapture, + costumeLibraryVisible: state.scratchGui.modals.costumeLibrary, + backdropLibraryVisible: state.scratchGui.modals.backdropLibrary }); const mapDispatchToProps = dispatch => ({ diff --git a/src/containers/custom-procedures.jsx b/src/containers/custom-procedures.jsx index 7605fd4b6501388c40a111667b4f80b5dada8d65..879800aae880313299ab53601e6a1f9f0f667d54 100644 --- a/src/containers/custom-procedures.jsx +++ b/src/containers/custom-procedures.jsx @@ -147,7 +147,7 @@ CustomProcedures.defaultProps = { }; const mapStateToProps = state => ({ - mutator: state.customProcedures.mutator + mutator: state.scratchGui.customProcedures.mutator }); export default connect( diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index c4e105d669a27d626a003a86cfc37b17c2f94273..59e5b94268a5286b4a0ba94422819ed05a556a51 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -5,6 +5,7 @@ import VM from 'scratch-vm'; import {connect} from 'react-redux'; import ReactModal from 'react-modal'; +import ErrorBoundary from './error-boundary.jsx'; import {openExtensionLibrary} from '../reducers/modals'; import { activateTab, @@ -13,7 +14,6 @@ import { SOUNDS_TAB_INDEX } from '../reducers/editor-tab'; -import AppStateHOC from '../lib/app-state-hoc.jsx'; import ProjectLoaderHOC from '../lib/project-loader-hoc.jsx'; import vmListenerHOC from '../lib/vm-listener-hoc.jsx'; @@ -23,12 +23,13 @@ class GUI extends React.Component { constructor (props) { super(props); this.state = { - loading: true, + loading: !props.vm.initialized, loadingError: false, errorMessage: '' }; } componentDidMount () { + if (this.props.vm.initialized) return; this.audioEngine = new AudioEngine(); this.props.vm.attachAudioEngine(this.audioEngine); this.props.vm.loadProject(this.props.projectData) @@ -43,6 +44,7 @@ class GUI extends React.Component { // error page gets rendered if project failed to load this.setState({loadingError: true, errorMessage: e}); }); + this.props.vm.initialized = true; } componentWillReceiveProps (nextProps) { if (this.props.projectData !== nextProps.projectData) { @@ -73,13 +75,15 @@ class GUI extends React.Component { ...componentProps } = this.props; return ( - <GUIComponent - loading={fetchingProject || this.state.loading || loadingStateVisible} - vm={vm} - {...componentProps} - > - {children} - </GUIComponent> + <ErrorBoundary action="Top Level App"> + <GUIComponent + loading={fetchingProject || this.state.loading || loadingStateVisible} + vm={vm} + {...componentProps} + > + {children} + </GUIComponent> + </ErrorBoundary> ); } } @@ -97,16 +101,20 @@ GUI.propTypes = { GUI.defaultProps = GUIComponent.defaultProps; const mapStateToProps = state => ({ - activeTabIndex: state.editorTab.activeTabIndex, - blocksTabVisible: state.editorTab.activeTabIndex === BLOCKS_TAB_INDEX, - cardsVisible: state.cards.visible, - costumesTabVisible: state.editorTab.activeTabIndex === COSTUMES_TAB_INDEX, - importInfoVisible: state.modals.importInfo, - loadingStateVisible: state.modals.loadingProject, - previewInfoVisible: state.modals.previewInfo, - targetIsStage: state.targets.stage && state.targets.stage.id === state.targets.editingTarget, - soundsTabVisible: state.editorTab.activeTabIndex === SOUNDS_TAB_INDEX, - tipsLibraryVisible: state.modals.tipsLibrary + activeTabIndex: state.scratchGui.editorTab.activeTabIndex, + blocksTabVisible: state.scratchGui.editorTab.activeTabIndex === BLOCKS_TAB_INDEX, + cardsVisible: state.scratchGui.cards.visible, + costumesTabVisible: state.scratchGui.editorTab.activeTabIndex === COSTUMES_TAB_INDEX, + importInfoVisible: state.scratchGui.modals.importInfo, + isPlayerOnly: state.scratchGui.mode.isPlayerOnly, + loadingStateVisible: state.scratchGui.modals.loadingProject, + previewInfoVisible: state.scratchGui.modals.previewInfo, + targetIsStage: ( + state.scratchGui.targets.stage && + state.scratchGui.targets.stage.id === state.scratchGui.targets.editingTarget + ), + soundsTabVisible: state.scratchGui.editorTab.activeTabIndex === SOUNDS_TAB_INDEX, + tipsLibraryVisible: state.scratchGui.modals.tipsLibrary }); const mapDispatchToProps = dispatch => ({ @@ -121,7 +129,7 @@ const ConnectedGUI = connect( mapDispatchToProps, )(GUI); -const WrappedGui = ProjectLoaderHOC(AppStateHOC(vmListenerHOC(ConnectedGUI))); +const WrappedGui = ProjectLoaderHOC(vmListenerHOC(ConnectedGUI)); WrappedGui.setAppElement = ReactModal.setAppElement; export default WrappedGui; diff --git a/src/containers/monitor-list.jsx b/src/containers/monitor-list.jsx index b3dcd5b7c7cfb2943f27caab43a0ff80191037ce..1499b5feac1d6b860377f44850a04241f9a5182d 100644 --- a/src/containers/monitor-list.jsx +++ b/src/containers/monitor-list.jsx @@ -33,7 +33,7 @@ MonitorList.propTypes = { moveMonitorRect: PropTypes.func.isRequired }; const mapStateToProps = state => ({ - monitors: state.monitors + monitors: state.scratchGui.monitors }); const mapDispatchToProps = dispatch => ({ moveMonitorRect: (id, x, y) => dispatch(moveMonitorRect(id, x, y)) diff --git a/src/containers/monitor.jsx b/src/containers/monitor.jsx index fd7a584adff0a78f9f74eaef217b22c4b2b235f0..b39358c08fef4a5e42d124f6bec606c9b861076c 100644 --- a/src/containers/monitor.jsx +++ b/src/containers/monitor.jsx @@ -152,7 +152,7 @@ Monitor.propTypes = { y: PropTypes.number }; const mapStateToProps = state => ({ - monitorLayout: state.monitorLayout + monitorLayout: state.scratchGui.monitorLayout }); const mapDispatchToProps = dispatch => ({ addMonitorRect: (id, rect, savePosition) => diff --git a/src/containers/paint-editor-wrapper.jsx b/src/containers/paint-editor-wrapper.jsx index 495df1a931c7ff64fe82a60c0fd05c2635c27da3..9b606f3dae07f3746dd5b51b5c5cd6851c4a7be3 100644 --- a/src/containers/paint-editor-wrapper.jsx +++ b/src/containers/paint-editor-wrapper.jsx @@ -66,7 +66,7 @@ const mapStateToProps = (state, {selectedCostumeIndex}) => { editingTarget, sprites, stage - } = state.targets; + } = state.scratchGui.targets; const target = editingTarget && sprites[editingTarget] ? sprites[editingTarget] : stage; const costume = target && target.costumes[selectedCostumeIndex]; return { @@ -75,7 +75,7 @@ const mapStateToProps = (state, {selectedCostumeIndex}) => { rotationCenterY: costume && costume.rotationCenterY, imageFormat: costume && costume.dataFormat, imageId: editingTarget && `${editingTarget}${costume.skinId}`, - vm: state.vm + vm: state.scratchGui.vm }; }; diff --git a/src/containers/project-loader.jsx b/src/containers/project-loader.jsx index 782dab60df5b960255c12eba9abd440e38eea21c..80ef161ea291d8fbb87e77d203fb314e187d725b 100644 --- a/src/containers/project-loader.jsx +++ b/src/containers/project-loader.jsx @@ -100,7 +100,7 @@ ProjectLoader.propTypes = { }; const mapStateToProps = state => ({ - vm: state.vm + vm: state.scratchGui.vm }); const mapDispatchToProps = dispatch => ({ diff --git a/src/containers/project-saver.jsx b/src/containers/project-saver.jsx index 0c755c8538d612547108508f9bd688a39eaf8ec9..c594421eca357181919dd021310d9b1703118d12 100644 --- a/src/containers/project-saver.jsx +++ b/src/containers/project-saver.jsx @@ -69,7 +69,7 @@ ProjectSaver.propTypes = { }; const mapStateToProps = state => ({ - vm: state.vm + vm: state.scratchGui.vm }); export default connect( diff --git a/src/containers/record-modal.jsx b/src/containers/record-modal.jsx index f6ac44526b739b3419e83772776fa4ac530c9d48..8a57c03d7be0ba06fa2c6e4aa4693e63a3f51bb3 100644 --- a/src/containers/record-modal.jsx +++ b/src/containers/record-modal.jsx @@ -139,7 +139,7 @@ RecordModal.propTypes = { }; const mapStateToProps = state => ({ - vm: state.vm + vm: state.scratchGui.vm }); const mapDispatchToProps = dispatch => ({ diff --git a/src/containers/sound-editor.jsx b/src/containers/sound-editor.jsx index de10ebc2cd066dc3729ea2afebeb1c116970389b..6bd793fceb3d487d555ba0ae45f669b7c40f08ce 100644 --- a/src/containers/sound-editor.jsx +++ b/src/containers/sound-editor.jsx @@ -211,17 +211,17 @@ SoundEditor.propTypes = { }; const mapStateToProps = (state, {soundIndex}) => { - const sprite = state.vm.editingTarget.sprite; + const sprite = state.scratchGui.vm.editingTarget.sprite; // Make sure the sound index doesn't go out of range. const index = soundIndex < sprite.sounds.length ? soundIndex : sprite.sounds.length - 1; - const sound = state.vm.editingTarget.sprite.sounds[index]; - const audioBuffer = state.vm.getSoundBuffer(index); + const sound = state.scratchGui.vm.editingTarget.sprite.sounds[index]; + const audioBuffer = state.scratchGui.vm.getSoundBuffer(index); return { soundId: sound.soundId, sampleRate: audioBuffer.sampleRate, samples: audioBuffer.getChannelData(0), name: sound.name, - vm: state.vm + vm: state.scratchGui.vm }; }; diff --git a/src/containers/sound-tab.jsx b/src/containers/sound-tab.jsx index e39a2b6d15f99e642dde3d02bcccead248c32ebf..d532c5ee9bc0b3cb359dfae24a75dd03ff9eec38 100644 --- a/src/containers/sound-tab.jsx +++ b/src/containers/sound-tab.jsx @@ -241,11 +241,11 @@ SoundTab.propTypes = { }; const mapStateToProps = state => ({ - editingTarget: state.targets.editingTarget, - sprites: state.targets.sprites, - stage: state.targets.stage, - soundLibraryVisible: state.modals.soundLibrary, - soundRecorderVisible: state.modals.soundRecorder + editingTarget: state.scratchGui.targets.editingTarget, + sprites: state.scratchGui.targets.sprites, + stage: state.scratchGui.targets.stage, + soundLibraryVisible: state.scratchGui.modals.soundLibrary, + soundRecorderVisible: state.scratchGui.modals.soundRecorder }); const mapDispatchToProps = dispatch => ({ diff --git a/src/containers/sprite-selector-item.jsx b/src/containers/sprite-selector-item.jsx index edb124b2c6086874d40cbc6734c0f892efb3c6a4..7d3fc2f21c45677102ef9feb3b4322c48b45dfe6 100644 --- a/src/containers/sprite-selector-item.jsx +++ b/src/containers/sprite-selector-item.jsx @@ -79,9 +79,9 @@ SpriteSelectorItem.propTypes = { }; const mapStateToProps = (state, {assetId, costumeURL, id}) => ({ - costumeURL: costumeURL || (assetId && state.vm.runtime.storage.get(assetId).encodeDataURI()), - receivedBlocks: state.hoveredTarget.receivedBlocks && - state.hoveredTarget.sprite === id + costumeURL: costumeURL || (assetId && state.scratchGui.vm.runtime.storage.get(assetId).encodeDataURI()), + receivedBlocks: state.scratchGui.hoveredTarget.receivedBlocks && + state.scratchGui.hoveredTarget.sprite === id }); const mapDispatchToProps = dispatch => ({ dispatchSetHoveredSprite: spriteId => { diff --git a/src/containers/stage-header.jsx b/src/containers/stage-header.jsx index 65677e29f224dcb2e9751a68c03012587e6d09f8..883c882fa120c343673febefbe5b14b814ced929 100644 --- a/src/containers/stage-header.jsx +++ b/src/containers/stage-header.jsx @@ -50,9 +50,9 @@ StageHeader.propTypes = { }; const mapStateToProps = state => ({ - stageSize: state.stageSize.stageSize, - isFullScreen: state.mode.isFullScreen, - isPlayerOnly: state.mode.isPlayerOnly + stageSize: state.scratchGui.stageSize.stageSize, + isFullScreen: state.scratchGui.mode.isFullScreen, + isPlayerOnly: state.scratchGui.mode.isPlayerOnly }); const mapDispatchToProps = dispatch => ({ diff --git a/src/containers/stage-selector.jsx b/src/containers/stage-selector.jsx index f3497a54cd9f2e187ffc5a5586b20f7492027f0c..a2039796f5e6379cc3bbb4be44447bdfc3890efe 100644 --- a/src/containers/stage-selector.jsx +++ b/src/containers/stage-selector.jsx @@ -99,8 +99,8 @@ StageSelector.propTypes = { }; const mapStateToProps = (state, {assetId}) => ({ - url: assetId && state.vm.runtime.storage.get(assetId).encodeDataURI(), - vm: state.vm + url: assetId && state.scratchGui.vm.runtime.storage.get(assetId).encodeDataURI(), + vm: state.scratchGui.vm }); const mapDispatchToProps = dispatch => ({ diff --git a/src/containers/stage.jsx b/src/containers/stage.jsx index b66efa2c5090ec70fb54f0c1a73340ea41a7aa66..881c9c929aa3cef447a1f9b8831670158b321969 100644 --- a/src/containers/stage.jsx +++ b/src/containers/stage.jsx @@ -395,10 +395,10 @@ Stage.defaultProps = { }; const mapStateToProps = state => ({ - isColorPicking: state.colorPicker.active, - isFullScreen: state.mode.isFullScreen, + isColorPicking: state.scratchGui.colorPicker.active, + isFullScreen: state.scratchGui.mode.isFullScreen, // Do not use editor drag style in fullscreen or player mode. - useEditorDragStyle: !(state.mode.isFullScreen || state.mode.isPlayerOnly) + useEditorDragStyle: !(state.scratchGui.mode.isFullScreen || state.scratchGui.mode.isPlayerOnly) }); const mapDispatchToProps = dispatch => ({ diff --git a/src/containers/target-pane.jsx b/src/containers/target-pane.jsx index 1178ed64ec9d7fc10eb6d07afe1fcba16a535ffb..dd20a4742f5825851410f3b8c5f901b815c5ace5 100644 --- a/src/containers/target-pane.jsx +++ b/src/containers/target-pane.jsx @@ -144,10 +144,10 @@ TargetPane.propTypes = { }; const mapStateToProps = state => ({ - editingTarget: state.targets.editingTarget, - hoveredTarget: state.hoveredTarget, - sprites: Object.keys(state.targets.sprites).reduce((sprites, k) => { - let {direction, size, x, y, ...sprite} = state.targets.sprites[k]; + editingTarget: state.scratchGui.targets.editingTarget, + hoveredTarget: state.scratchGui.hoveredTarget, + sprites: Object.keys(state.scratchGui.targets.sprites).reduce((sprites, k) => { + let {direction, size, x, y, ...sprite} = state.scratchGui.targets.sprites[k]; if (typeof direction !== 'undefined') direction = Math.round(direction); if (typeof x !== 'undefined') x = Math.round(x); if (typeof y !== 'undefined') y = Math.round(y); @@ -155,9 +155,9 @@ const mapStateToProps = state => ({ sprites[k] = {...sprite, direction, size, x, y}; return sprites; }, {}), - stage: state.targets.stage, - raiseSprites: state.blockDrag, - spriteLibraryVisible: state.modals.spriteLibrary + stage: state.scratchGui.targets.stage, + raiseSprites: state.scratchGui.blockDrag, + spriteLibraryVisible: state.scratchGui.modals.spriteLibrary }); const mapDispatchToProps = dispatch => ({ onNewSpriteClick: e => { diff --git a/src/containers/tips-library.jsx b/src/containers/tips-library.jsx index 7d45f37a6c967aa2ff669464bc41042aee768da7..20304cab41b62caa541c718002995c54d4818afa 100644 --- a/src/containers/tips-library.jsx +++ b/src/containers/tips-library.jsx @@ -61,7 +61,7 @@ TipsLibrary.propTypes = { }; const mapStateToProps = state => ({ - visible: state.modals.tipsLibrary + visible: state.scratchGui.modals.tipsLibrary }); const mapDispatchToProps = dispatch => ({ diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000000000000000000000000000000000000..3bfa737f52db778b0196009bcfecb83c96f94795 --- /dev/null +++ b/src/index.js @@ -0,0 +1,24 @@ +import GUI from './containers/gui.jsx'; +import GuiReducer, {guiInitialState, guiMiddleware, initFullScreen, initPlayer} from './reducers/gui'; +import {ScratchPaintReducer} from 'scratch-paint'; +import IntlReducer from './reducers/intl'; +import {setFullScreen, setPlayer} from './reducers/mode'; +import {setAppElement} from 'react-modal'; + +const guiReducers = { + intl: IntlReducer, + scratchGui: GuiReducer, + scratchPaint: ScratchPaintReducer +}; + +export { + GUI as default, + setAppElement, + guiReducers, + guiInitialState, + guiMiddleware, + initPlayer, + initFullScreen, + setFullScreen, + setPlayer +}; diff --git a/src/lib/app-state-hoc.jsx b/src/lib/app-state-hoc.jsx index 91d4069f098426c10e53560ee78f6facd5f384f2..dc62aa31bca70f8a05ea90cc698d8050c811ea7c 100644 --- a/src/lib/app-state-hoc.jsx +++ b/src/lib/app-state-hoc.jsx @@ -1,22 +1,20 @@ import React from 'react'; import PropTypes from 'prop-types'; import {Provider} from 'react-redux'; -import {createStore, applyMiddleware, compose} from 'redux'; -import throttle from 'redux-throttle'; +import {createStore, combineReducers, compose} from 'redux'; import {intlShape} from 'react-intl'; import {IntlProvider, updateIntl} from 'react-intl-redux'; -import {intlInitialState} from '../reducers/intl.js'; -import {initialState as modeInitialState, setPlayer, setFullScreen} from '../reducers/mode.js'; -import reducer from '../reducers/gui'; -import ErrorBoundary from '../containers/error-boundary.jsx'; +import intlReducer from '../reducers/intl.js'; + +import guiReducer, {guiInitialState, guiMiddleware, initFullScreen, initPlayer} from '../reducers/gui'; + +import {setPlayer, setFullScreen} from '../reducers/mode.js'; + +import {ScratchPaintReducer} from 'scratch-paint'; const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; -const enhancer = composeEnhancers( - applyMiddleware( - throttle(300, {leading: true, trailing: true}) - ) -); +const enhancer = composeEnhancers(guiMiddleware); /* * Higher Order Component to provide redux state. If an `intl` prop is provided @@ -28,32 +26,22 @@ const AppStateHOC = function (WrappedComponent) { class AppStateWrapper extends React.Component { constructor (props) { super(props); - let intl = {}; - let mode = {}; - if (props.intl) { - intl = { - defaultLocale: 'en', - locale: props.intl.locale, - messages: props.intl.messages - }; - } else { - intl = intlInitialState.intl; + let initializedGui = guiInitialState; + if (props.isFullScreen) { + initializedGui = initFullScreen(initializedGui); } - if (props.isPlayerOnly || props.isFullScreen) { - mode = { - isFullScreen: props.isFullScreen || false, - isPlayerOnly: props.isPlayerOnly || false - }; - } else { - mode = modeInitialState; + if (props.isPlayerOnly) { + initializedGui = initPlayer(initializedGui); } + const reducer = combineReducers({ + intl: intlReducer, + scratchGui: guiReducer, + scratchPaint: ScratchPaintReducer + }); this.store = createStore( reducer, - { - intl: intl, - mode: mode - }, + {scratchGui: initializedGui}, enhancer); } componentDidUpdate (prevProps) { @@ -68,12 +56,16 @@ const AppStateHOC = function (WrappedComponent) { } } render () { + const { + intl, // eslint-disable-line no-unused-vars + isFullScreen, // eslint-disable-line no-unused-vars + isPlayerOnly, // eslint-disable-line no-unused-vars + ...componentProps + } = this.props; return ( <Provider store={this.store}> <IntlProvider> - <ErrorBoundary action="Top Level App"> - <WrappedComponent {...this.props} /> - </ErrorBoundary> + <WrappedComponent {...componentProps} /> </IntlProvider> </Provider> ); diff --git a/src/lib/vm-listener-hoc.jsx b/src/lib/vm-listener-hoc.jsx index 37132632069669fd6f93765798e4ba171ff0068a..3fd9e2096f1f0c3de8b6b843ff75dba696d52af3 100644 --- a/src/lib/vm-listener-hoc.jsx +++ b/src/lib/vm-listener-hoc.jsx @@ -96,7 +96,7 @@ const vmListenerHOC = function (WrappedComponent) { attachKeyboardEvents: true }; const mapStateToProps = state => ({ - vm: state.vm + vm: state.scratchGui.vm }); const mapDispatchToProps = dispatch => ({ onTargetsUpdate: data => { diff --git a/src/playground/blocks-only.jsx b/src/playground/blocks-only.jsx index 9a925424cc2d1d5c1ea4d9b38e855f95ff3a3f3e..1a72e267b8776201ecf8cf9bda733142636087ce 100644 --- a/src/playground/blocks-only.jsx +++ b/src/playground/blocks-only.jsx @@ -6,10 +6,11 @@ import Controls from '../containers/controls.jsx'; import Blocks from '../containers/blocks.jsx'; import GUI from '../containers/gui.jsx'; import HashParserHOC from '../lib/hash-parser-hoc.jsx'; +import AppStateHOC from '../lib/app-state-hoc.jsx'; import styles from './blocks-only.css'; -const mapStateToProps = state => ({vm: state.vm}); +const mapStateToProps = state => ({vm: state.scratchGui.vm}); const VMBlocks = connect(mapStateToProps)(Blocks); const VMControls = connect(mapStateToProps)(Controls); @@ -26,7 +27,7 @@ const BlocksOnly = props => ( </GUI> ); -const App = HashParserHOC(BlocksOnly); +const App = HashParserHOC(AppStateHOC(BlocksOnly)); const appTarget = document.createElement('div'); document.body.appendChild(appTarget); diff --git a/src/playground/compatibility-testing.jsx b/src/playground/compatibility-testing.jsx index a1b6fba5cea1963787f616f6932e2bda15aaf957..3209131c3f7a943b65757979b49f2b096011947a 100644 --- a/src/playground/compatibility-testing.jsx +++ b/src/playground/compatibility-testing.jsx @@ -1,17 +1,11 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import {connect} from 'react-redux'; -import Controls from '../containers/controls.jsx'; -import Stage from '../containers/stage.jsx'; -import Box from '../components/box/box.jsx'; import GUI from '../containers/gui.jsx'; import HashParserHOC from '../lib/hash-parser-hoc.jsx'; +import AppStateHOC from '../lib/app-state-hoc.jsx'; +const WrappedGui = HashParserHOC(AppStateHOC(GUI)); -const mapStateToProps = state => ({vm: state.vm}); - -const VMStage = connect(mapStateToProps)(Stage); -const VMControls = connect(mapStateToProps)(Controls); const DEFAULT_PROJECT_ID = '10015059'; @@ -37,27 +31,12 @@ class Player extends React.Component { this.setState({projectId: window.location.hash.substring(1)}); } render () { - const width = 480; - const height = 360; return ( <div style={{display: 'flex'}}> - <GUI - {...this.props} - width={width} - > - <Box height={40}> - <VMControls - style={{ - marginRight: 10, - height: 40 - }} - /> - </Box> - <VMStage - height={height} - width={width} - /> - </GUI> + <WrappedGui + isPlayerOnly + isFullScreen={false} + /> <iframe allowFullScreen allowTransparency @@ -71,9 +50,7 @@ class Player extends React.Component { } } -const App = HashParserHOC(Player); - const appTarget = document.createElement('div'); document.body.appendChild(appTarget); -ReactDOM.render(<App />, appTarget); +ReactDOM.render(<Player />, appTarget); diff --git a/src/playground/index.jsx b/src/playground/index.jsx index 60e018fb6501fa7e865916dd4920595390b7a780..278d2dd43a1c9331a087da2774f449dcf5e651bf 100644 --- a/src/playground/index.jsx +++ b/src/playground/index.jsx @@ -5,6 +5,7 @@ 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 styles from './index.css'; @@ -21,6 +22,6 @@ appTarget.className = styles.app; document.body.appendChild(appTarget); GUI.setAppElement(appTarget); -const WrappedGui = HashParserHOC(GUI); +const WrappedGui = HashParserHOC(AppStateHOC(GUI)); ReactDOM.render(<WrappedGui />, appTarget); diff --git a/src/playground/player.jsx b/src/playground/player.jsx index 4ecae4c0c13af54df9cf36b25475fe4e027dfe53..d1617571c6937c260177aca1017c1b76bacfe657 100644 --- a/src/playground/player.jsx +++ b/src/playground/player.jsx @@ -4,7 +4,8 @@ import ReactDOM from 'react-dom'; import Box from '../components/box/box.jsx'; import GUI from '../containers/gui.jsx'; import HashParserHOC from '../lib/hash-parser-hoc.jsx'; -const WrappedGui = HashParserHOC(GUI); +import AppStateHOC from '../lib/app-state-hoc.jsx'; +const WrappedGui = HashParserHOC(AppStateHOC(GUI)); if (process.env.NODE_ENV === 'production' && typeof window === 'object') { // Warn before navigating away diff --git a/src/reducers/block-drag.js b/src/reducers/block-drag.js index 5a261ceaa93459575a41046dc87b664c3af7c6b1..745cee756baee5a98fe053d67daa08a2ac4fb886 100644 --- a/src/reducers/block-drag.js +++ b/src/reducers/block-drag.js @@ -24,5 +24,6 @@ const updateBlockDrag = function (areBlocksOverGui) { export { reducer as default, + initialState as blockDragInitialState, updateBlockDrag }; diff --git a/src/reducers/cards.js b/src/reducers/cards.js index 334225639ca579c80c3ab62ee6d267d10b81ef97..6f4add89d20b6936e55646f46771072d8d0904f9 100644 --- a/src/reducers/cards.js +++ b/src/reducers/cards.js @@ -107,6 +107,7 @@ const endDrag = function () { export { reducer as default, + initialState as cardsInitialState, activateDeck, viewCards, closeCards, diff --git a/src/reducers/color-picker.js b/src/reducers/color-picker.js index fc16354a0a25f0f92579c21382f5f0a87d51ac2a..b5edbdc475c5ad27b109583444f6dacbf9e56f49 100644 --- a/src/reducers/color-picker.js +++ b/src/reducers/color-picker.js @@ -34,6 +34,7 @@ const setCallback = callback => ({type: SET_CALLBACK, callback: callback}); export { reducer as default, + initialState as colorPickerInitialState, activateColorPicker, deactivateColorPicker, setCallback diff --git a/src/reducers/custom-procedures.js b/src/reducers/custom-procedures.js index 2f4f63d983b0a610ce4395b0610db48e24ea21ab..3b982941e646710920d5ea44ddd8abec797940cb 100644 --- a/src/reducers/custom-procedures.js +++ b/src/reducers/custom-procedures.js @@ -60,6 +60,7 @@ const deactivateCustomProcedures = mutator => ({ export { reducer as default, + initialState as customProceduresInitialState, activateCustomProcedures, deactivateCustomProcedures }; diff --git a/src/reducers/editor-tab.js b/src/reducers/editor-tab.js index 21b3534e29fb2700a4a2469886445d4e2da0b557..e44a7ca842055c876a53367d6a9220adeaee0dcb 100644 --- a/src/reducers/editor-tab.js +++ b/src/reducers/editor-tab.js @@ -30,6 +30,7 @@ const activateTab = function (tab) { export { reducer as default, + initialState as editorTabInitialState, activateTab, BLOCKS_TAB_INDEX, COSTUMES_TAB_INDEX, diff --git a/src/reducers/gui.js b/src/reducers/gui.js index 8bb226a9074f6d4681830ce9deb0aa18b26a80a2..e28b757163b50b0c10c8265222e6bc4f877a14d8 100644 --- a/src/reducers/gui.js +++ b/src/reducers/gui.js @@ -1,23 +1,62 @@ -import {combineReducers} from 'redux'; -import cardsReducer from './cards'; -import colorPickerReducer from './color-picker'; -import customProceduresReducer from './custom-procedures'; -import blockDragReducer from './block-drag'; -import editorTabReducer from './editor-tab'; -import hoveredTargetReducer from './hovered-target'; -import intlReducer from './intl'; -import menuReducer from './menus'; -import modalReducer from './modals'; -import modeReducer from './mode'; -import monitorReducer from './monitors'; -import monitorLayoutReducer from './monitor-layout'; -import targetReducer from './targets'; -import toolboxReducer from './toolbox'; -import vmReducer from './vm'; -import stageSizeReducer from './stage-size'; -import {ScratchPaintReducer} from 'scratch-paint'; +import {applyMiddleware, compose, combineReducers} from 'redux'; +import cardsReducer, {cardsInitialState} from './cards'; +import colorPickerReducer, {colorPickerInitialState} from './color-picker'; +import customProceduresReducer, {customProceduresInitialState} from './custom-procedures'; +import blockDragReducer, {blockDragInitialState} from './block-drag'; +import editorTabReducer, {editorTabInitialState} from './editor-tab'; +import hoveredTargetReducer, {hoveredTargetInitialState} from './hovered-target'; +import menuReducer, {menuInitialState} from './menus'; +import modalReducer, {modalsInitialState} from './modals'; +import modeReducer, {modeInitialState} from './mode'; +import monitorReducer, {monitorsInitialState} from './monitors'; +import monitorLayoutReducer, {monitorLayoutInitialState} from './monitor-layout'; +import stageSizeReducer, {stageSizeInitialState} from './stage-size'; +import targetReducer, {targetsInitialState} from './targets'; +import toolboxReducer, {toolboxInitialState} from './toolbox'; +import vmReducer, {vmInitialState} from './vm'; +import throttle from 'redux-throttle'; -export default combineReducers({ +const guiMiddleware = compose(applyMiddleware(throttle(300, {leading: true, trailing: true}))); + +const guiInitialState = { + blockDrag: blockDragInitialState, + cards: cardsInitialState, + colorPicker: colorPickerInitialState, + customProcedures: customProceduresInitialState, + editorTab: editorTabInitialState, + mode: modeInitialState, + hoveredTarget: hoveredTargetInitialState, + stageSize: stageSizeInitialState, + menus: menuInitialState, + modals: modalsInitialState, + monitors: monitorsInitialState, + monitorLayout: monitorLayoutInitialState, + targets: targetsInitialState, + toolbox: toolboxInitialState, + vm: vmInitialState +}; + +const initPlayer = function (currentState) { + return Object.assign( + {}, + currentState, + {mode: { + isFullScreen: currentState.mode.isFullScreen, + isPlayerOnly: true + }} + ); +}; +const initFullScreen = function (currentState) { + return Object.assign( + {}, + currentState, + {mode: { + isFullScreen: true, + isPlayerOnly: currentState.mode.isPlayerOnly + }} + ); +}; +const guiReducer = combineReducers({ blockDrag: blockDragReducer, cards: cardsReducer, colorPicker: colorPickerReducer, @@ -25,7 +64,6 @@ export default combineReducers({ editorTab: editorTabReducer, mode: modeReducer, hoveredTarget: hoveredTargetReducer, - intl: intlReducer, stageSize: stageSizeReducer, menus: menuReducer, modals: modalReducer, @@ -33,6 +71,13 @@ export default combineReducers({ monitorLayout: monitorLayoutReducer, targets: targetReducer, toolbox: toolboxReducer, - vm: vmReducer, - scratchPaint: ScratchPaintReducer + vm: vmReducer }); + +export { + guiReducer as default, + guiInitialState, + guiMiddleware, + initFullScreen, + initPlayer +}; diff --git a/src/reducers/hovered-target.js b/src/reducers/hovered-target.js index 43bf87ac0bb6ce81e4471955b31dec5f90eef4a5..d6b44f878d287df1023b44f86eec60b6cd99cbf8 100644 --- a/src/reducers/hovered-target.js +++ b/src/reducers/hovered-target.js @@ -43,6 +43,7 @@ const setReceivedBlocks = function (receivedBlocks) { export { reducer as default, + initialState as hoveredTargetInitialState, setHoveredSprite, setReceivedBlocks }; diff --git a/src/reducers/menus.js b/src/reducers/menus.js index aac3fb852aee00712baa546ed823076422f71008..9d9ab0be9882e9001c5bd8f63195d9755a82631d 100644 --- a/src/reducers/menus.js +++ b/src/reducers/menus.js @@ -35,13 +35,14 @@ const closeMenu = menu => ({ }); const openFileMenu = () => openMenu(MENU_FILE); const closeFileMenu = () => closeMenu(MENU_FILE); -const fileMenuOpen = state => state.menus[MENU_FILE]; +const fileMenuOpen = state => state.scratchGui.menus[MENU_FILE]; const openEditMenu = () => openMenu(MENU_EDIT); const closeEditMenu = () => closeMenu(MENU_EDIT); -const editMenuOpen = state => state.menus[MENU_EDIT]; +const editMenuOpen = state => state.scratchGui.menus[MENU_EDIT]; export { reducer as default, + initialState as menuInitialState, openFileMenu, closeFileMenu, openEditMenu, diff --git a/src/reducers/modals.js b/src/reducers/modals.js index c373ce79610eb53010f5dc45b22ec3f6f3fc0390..ddcb5080675dcb18c1922e01c61d090766fc1c1a 100644 --- a/src/reducers/modals.js +++ b/src/reducers/modals.js @@ -137,6 +137,7 @@ const closeTipsLibrary = function () { }; export { reducer as default, + initialState as modalsInitialState, openBackdropLibrary, openCameraCapture, openCostumeLibrary, diff --git a/src/reducers/mode.js b/src/reducers/mode.js index b158c95230dcdbd7aef38898908ddfbe690dec06..d95cb1f9905c95befaa7673484e0d6d26235da2b 100644 --- a/src/reducers/mode.js +++ b/src/reducers/mode.js @@ -39,7 +39,7 @@ const setPlayer = function (isPlayerOnly) { export { reducer as default, - initialState, + initialState as modeInitialState, setFullScreen, setPlayer }; diff --git a/src/reducers/monitor-layout.js b/src/reducers/monitor-layout.js index 55cb52ee52be04b42c30ba81f5b6c604eca282e2..7e394caedbf80e3312b944e4deaa9b489fed523d 100644 --- a/src/reducers/monitor-layout.js +++ b/src/reducers/monitor-layout.js @@ -307,6 +307,7 @@ const removeMonitorRect = function (monitorId) { export { reducer as default, + initialState as monitorLayoutInitialState, addMonitorRect, getInitialPosition, moveMonitorRect, diff --git a/src/reducers/monitors.js b/src/reducers/monitors.js index 1b1b1f43679b0b3441b1c9f5f35d60f2f0045a20..091624225d5513812519be2692e7a99789554755 100644 --- a/src/reducers/monitors.js +++ b/src/reducers/monitors.js @@ -25,5 +25,6 @@ const updateMonitors = function (monitors) { export { reducer as default, + initialState as monitorsInitialState, updateMonitors }; diff --git a/src/reducers/stage-size.js b/src/reducers/stage-size.js index 4c4a58a52191c04a1d13781c94610aa6d4675cb3..74653f19e778169509ceeb898e6d87060742ca5a 100644 --- a/src/reducers/stage-size.js +++ b/src/reducers/stage-size.js @@ -31,6 +31,7 @@ const setStageSize = function (stageSize) { export { reducer as default, + initialState as stageSizeInitialState, setStageSize, STAGE_SIZES }; diff --git a/src/reducers/targets.js b/src/reducers/targets.js index 883fe4917869a9cde0383bc77f8504ad7e0182b1..c1f661394f827ec3e63610bde7090155160053e5 100644 --- a/src/reducers/targets.js +++ b/src/reducers/targets.js @@ -39,5 +39,6 @@ const updateTargets = function (targetList, editingTarget) { }; export { reducer as default, + initialState as targetsInitialState, updateTargets }; diff --git a/src/reducers/toolbox.js b/src/reducers/toolbox.js index e3317ea855e6e734aaa91d4cc2807fca93d24bbf..4e7d8d99235967d73ab86e34289328f3335d87b5 100644 --- a/src/reducers/toolbox.js +++ b/src/reducers/toolbox.js @@ -26,5 +26,6 @@ const updateToolbox = function (toolboxXML) { export { reducer as default, + initialState as toolboxInitialState, updateToolbox }; diff --git a/src/reducers/vm.js b/src/reducers/vm.js index dd70f3f4fd756e17e76655ff149559cefd7ffbee..da2155cf8cd55dc52cb9f9b2d4a63e42c3ca2ff4 100644 --- a/src/reducers/vm.js +++ b/src/reducers/vm.js @@ -23,5 +23,6 @@ const setVM = function (vm) { }; export { reducer as default, + initialState as vmInitialState, setVM }; diff --git a/test/unit/containers/sound-editor.test.jsx b/test/unit/containers/sound-editor.test.jsx index 988aaca0f780fa61f2051eec27a0fb572fadfc55..6070b7592c914052e9bbc666d83ca6cebaf737cd 100644 --- a/test/unit/containers/sound-editor.test.jsx +++ b/test/unit/containers/sound-editor.test.jsx @@ -35,7 +35,7 @@ describe('Sound Editor Container', () => { } } }; - store = mockStore({vm}); + store = mockStore({scratchGui: {vm: vm}}); }); test('should pass the correct data to the component from the store', () => { diff --git a/test/unit/containers/sprite-selector-item.test.jsx b/test/unit/containers/sprite-selector-item.test.jsx index a423fe81635a3685b6521a73f12824f3a5b380e0..23bda4e143de8f2ed55a7aac834cf182ffe5bdbd 100644 --- a/test/unit/containers/sprite-selector-item.test.jsx +++ b/test/unit/containers/sprite-selector-item.test.jsx @@ -36,7 +36,9 @@ describe('SpriteSelectorItem Container', () => { }; beforeEach(() => { - store = mockStore({hoveredTarget: {receivedBlocks: false, sprite: null}}); + store = mockStore({scratchGui: { + hoveredTarget: {receivedBlocks: false, sprite: null}} + }); className = 'ponies'; costumeURL = 'https://scratch.mit.edu/foo/bar/pony'; id = 1337; diff --git a/webpack.config.js b/webpack.config.js index cba33b005d795bb3578aa48d1c3587c4c911a574..68c87a3ae6b59c8ae380d73d6f9d50d1249dcd13 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -166,7 +166,7 @@ module.exports = [ defaultsDeep({}, base, { target: 'web', entry: { - 'scratch-gui': './src/containers/gui.jsx' + 'scratch-gui': './src/index.js' }, output: { libraryTarget: 'umd', @@ -179,7 +179,6 @@ module.exports = [ module: { rules: base.module.rules.concat([ { - test: /\.(svg|png|wav|gif|jpg)$/, loader: 'file-loader', options: {