diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index 2d3cf79b8869eab536a4f3d7b2a1ab79f2fb59f7..8d251bd14e17538e5f38da679d8ddd3eb752757d 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -72,6 +72,7 @@ const GUIComponent = props => { onActivateTab, onRequestCloseBackdropLibrary, onRequestCloseCostumeLibrary, + onSeeCommunity, previewInfoVisible, targetIsStage, soundsTabVisible, @@ -141,7 +142,10 @@ const GUIComponent = props => { onRequestClose={onRequestCloseBackdropLibrary} /> ) : null} - <MenuBar enableCommunity={enableCommunity} /> + <MenuBar + enableCommunity={enableCommunity} + onSeeCommunity={onSeeCommunity} + /> <Box className={styles.bodyWrapper}> <Box className={styles.flexWrapper}> <Box className={styles.editorWrapper}> @@ -285,6 +289,7 @@ GUIComponent.propTypes = { onExtensionButtonClick: PropTypes.func, onRequestCloseBackdropLibrary: PropTypes.func, onRequestCloseCostumeLibrary: PropTypes.func, + onSeeCommunity: PropTypes.func, onTabSelect: PropTypes.func, previewInfoVisible: PropTypes.bool, soundsTabVisible: PropTypes.bool, diff --git a/src/components/stage/stage.jsx b/src/components/stage/stage.jsx index 37b8bced0b49b77d680557e42dd27fd1ce45b034..30885874d0f91204b9bda9be3084ed24b5b1b252 100644 --- a/src/components/stage/stage.jsx +++ b/src/components/stage/stage.jsx @@ -3,6 +3,7 @@ import React from 'react'; import classNames from 'classnames'; import Box from '../box/box.jsx'; +import DOMElementRenderer from '../../containers/dom-element-renderer.jsx'; import Loupe from '../loupe/loupe.jsx'; import MonitorList from '../../containers/monitor-list.jsx'; import Question from '../../containers/question.jsx'; @@ -12,7 +13,7 @@ import styles from './stage.css'; const StageComponent = props => { const { - canvasRef, + canvas, dragRef, isColorPicking, isFullScreen, @@ -21,6 +22,7 @@ const StageComponent = props => { stageSize, useEditorDragStyle, onDeactivateColorPicker, + onDoubleClick, onQuestionAnswered, ...boxProps } = props; @@ -35,14 +37,14 @@ const StageComponent = props => { [styles.stageWrapperOverlay]: isFullScreen, [styles.withColorPicker]: !isFullScreen && isColorPicking })} + onDoubleClick={onDoubleClick} > - <Box + <DOMElementRenderer className={classNames( styles.stage, {[styles.stageOverlayContent]: isFullScreen} )} - componentRef={canvasRef} - element="canvas" + domElement={canvas} height={stageDimensions.height} width={stageDimensions.width} {...boxProps} @@ -93,19 +95,19 @@ const StageComponent = props => { ); }; StageComponent.propTypes = { - canvasRef: PropTypes.func, + canvas: PropTypes.instanceOf(Element).isRequired, colorInfo: Loupe.propTypes.colorInfo, dragRef: PropTypes.func, isColorPicking: PropTypes.bool, isFullScreen: PropTypes.bool.isRequired, onDeactivateColorPicker: PropTypes.func, + onDoubleClick: PropTypes.func, onQuestionAnswered: PropTypes.func, question: PropTypes.string, stageSize: PropTypes.oneOf(Object.keys(STAGE_DISPLAY_SIZES)).isRequired, useEditorDragStyle: PropTypes.bool }; StageComponent.defaultProps = { - canvasRef: () => {}, dragRef: () => {} }; export default StageComponent; diff --git a/src/containers/dom-element-renderer.jsx b/src/containers/dom-element-renderer.jsx new file mode 100644 index 0000000000000000000000000000000000000000..4636e2fb7b6e057ad6fe2c4f011d7aeb8d7a92f7 --- /dev/null +++ b/src/containers/dom-element-renderer.jsx @@ -0,0 +1,44 @@ +import omit from 'lodash.omit'; +import PropTypes from 'prop-types'; +import React from 'react'; + +/* + * DOMElementRenderer wraps a DOM element, allowing it to be + * rendered by React. It's up to the containing component + * to retain a reference to the element prop, or else it + * will be garbage collected after unmounting. + * + * Props passed to the DOMElementRenderer will be set on the + * DOM element like it's a normal component. + */ +class DOMElementRenderer extends React.Component { + constructor (props) { + super(props); + this.setContainer = this.setContainer.bind(this); + } + componentDidMount () { + this.container.appendChild(this.props.domElement); + } + componentWillUnmount () { + this.container.removeChild(this.props.domElement); + } + setContainer (c) { + this.container = c; + } + render () { + // Apply props to the DOM element, so its attributes + // are updated as if it were a normal component. + // Look at me, I'm the React now! + Object.assign( + this.props.domElement, + omit(this.props, ['domElement', 'children']) + ); + return <div ref={this.setContainer} />; + } +} + +DOMElementRenderer.propTypes = { + domElement: PropTypes.instanceOf(Element).isRequired +}; + +export default DOMElementRenderer; diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index bdd552968f4f55c8c1a7fca096206e7574a63960..8db598c85fd28f2f1c6e66fdbae55468399123c4 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -66,9 +66,6 @@ class GUI extends React.Component { }); } } - componentWillUnmount () { - this.props.vm.stopAll(); - } render () { if (this.state.loadingError) { throw new Error( @@ -99,6 +96,7 @@ GUI.propTypes = { fetchingProject: PropTypes.bool, importInfoVisible: PropTypes.bool, loadingStateVisible: PropTypes.bool, + onSeeCommunity: PropTypes.func, previewInfoVisible: PropTypes.bool, projectData: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), vm: PropTypes.instanceOf(VM) diff --git a/src/containers/stage.jsx b/src/containers/stage.jsx index 0013307f8d31bee981f324ff83d182980badf8a2..01fc3ec7c72f668196496859159896292ae6d68f 100644 --- a/src/containers/stage.jsx +++ b/src/containers/stage.jsx @@ -37,7 +37,6 @@ class Stage extends React.Component { 'onWheel', 'updateRect', 'questionListener', - 'setCanvas', 'setDragCanvas', 'clearDragCanvas', 'drawDragCanvas', @@ -52,16 +51,22 @@ class Stage extends React.Component { colorInfo: null, question: null }; + if (this.props.vm.runtime.renderer) { + this.renderer = this.props.vm.runtime.renderer; + this.canvas = this.props.vm.runtime.renderer._gl.canvas; + } else { + this.canvas = document.createElement('canvas'); + this.renderer = new Renderer(this.canvas); + this.props.vm.attachRenderer(this.renderer); + } + this.props.vm.attachV2SVGAdapter(new V2SVGAdapter()); + this.props.vm.setVideoProvider(new VideoProvider()); } componentDidMount () { this.attachRectEvents(); this.attachMouseEvents(this.canvas); this.updateRect(); - this.renderer = new Renderer(this.canvas); - this.props.vm.attachRenderer(this.renderer); - this.props.vm.attachV2SVGAdapter(new V2SVGAdapter()); this.props.vm.runtime.addListener('QUESTION', this.questionListener); - this.props.vm.setVideoProvider(new VideoProvider()); } shouldComponentUpdate (nextProps, nextState) { return this.props.stageSize !== nextProps.stageSize || @@ -353,9 +358,6 @@ class Stage extends React.Component { commonStopDragActions(); } } - setCanvas (canvas) { - this.canvas = canvas; - } setDragCanvas (canvas) { this.dragCanvas = canvas; } @@ -367,7 +369,7 @@ class Stage extends React.Component { } = this.props; return ( <StageComponent - canvasRef={this.setCanvas} + canvas={this.canvas} colorInfo={this.state.colorInfo} dragRef={this.setDragCanvas} question={this.state.question} diff --git a/src/playground/player.jsx b/src/playground/player.jsx index d1617571c6937c260177aca1017c1b76bacfe657..a0894fe39dea782401e1b2806f3ddf55b9d267e9 100644 --- a/src/playground/player.jsx +++ b/src/playground/player.jsx @@ -1,11 +1,15 @@ +import classNames from 'classnames'; +import PropTypes from 'prop-types'; import React from 'react'; import ReactDOM from 'react-dom'; +import {connect} from 'react-redux'; 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)); + +import {setPlayer} from '../reducers/mode'; if (process.env.NODE_ENV === 'production' && typeof window === 'object') { // Warn before navigating away @@ -13,16 +17,38 @@ if (process.env.NODE_ENV === 'production' && typeof window === 'object') { } import styles from './player.css'; -const Player = () => ( - <Box className={styles.stageOnly}> - <WrappedGui - isPlayerOnly - isFullScreen={false} + +const Player = ({isPlayerOnly, onSeeInside}) => ( + <Box + className={classNames({ + [styles.stageOnly]: isPlayerOnly + })} + > + {isPlayerOnly && <button onClick={onSeeInside}>{'See inside'}</button>} + <GUI + enableCommunity + isPlayerOnly={isPlayerOnly} /> </Box> ); +Player.propTypes = { + isPlayerOnly: PropTypes.bool, + onSeeInside: PropTypes.func +}; + +const mapStateToProps = state => ({ + isPlayerOnly: state.scratchGui.mode.isPlayerOnly +}); + +const mapDispatchToProps = dispatch => ({ + onSeeInside: () => dispatch(setPlayer(false)) +}); + +const ConnectedPlayer = connect(mapStateToProps, mapDispatchToProps)(Player); +const WrappedPlayer = HashParserHOC(AppStateHOC(ConnectedPlayer)); + const appTarget = document.createElement('div'); document.body.appendChild(appTarget); -ReactDOM.render(<Player />, appTarget); +ReactDOM.render(<WrappedPlayer isPlayerOnly />, appTarget);