diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index dfcc7d8ddc9..2e94229f282 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -129,7 +129,11 @@ const GUIComponent = props => { }; if (isRendererSupported === null) { - isRendererSupported = Renderer.isSupported(); + if (vm.renderer) { + isRendererSupported = true; + } else { + isRendererSupported = Renderer.isSupported(); + } } return ({isFullSize => { diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index 173f1218a6e..2a4ecdea6aa 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -35,10 +35,13 @@ import storage from '../lib/storage'; import vmListenerHOC from '../lib/vm-listener-hoc.jsx'; import vmManagerHOC from '../lib/vm-manager-hoc.jsx'; import cloudManagerHOC from '../lib/cloud-manager-hoc.jsx'; +import Loader from '../components/loader/loader.jsx'; -import GUIComponent from '../components/gui/gui.jsx'; +// import GUIComponent from '../components/gui/gui.jsx'; import {setIsScratchDesktop} from '../lib/isScratchDesktop.js'; +import VideoProvider from '../lib/video/video-provider'; + const messages = defineMessages({ defaultProjectTitle: { id: 'gui.gui.defaultProjectTitle', @@ -52,6 +55,22 @@ class GUI extends React.Component { setIsScratchDesktop(this.props.isScratchDesktop); this.setReduxTitle(this.props.projectTitle); this.props.onStorageInit(storage); + + // Use setTimeout. Do not use requestAnimationFrame or a resolved + // Promise. We want this work delayed until after the data request is + // made. + setTimeout(this.ensureRenderer.bind(this)); + + // Once the GUI component has been rendered, always render GUI and do + // not revert back to a Loader in this component. + // + // This makes GUI container not a pure component. We don't want to use + // state for this. That would possibly cause a full second render of GUI + // after the first one. + const {fontsLoaded, fetchingProject, isLoading} = this.props; + this.isAfterGUI = this.isAfterGUI || ( + fontsLoaded && !fetchingProject && !isLoading + ); } componentDidUpdate (prevProps) { if (this.props.projectId !== prevProps.projectId && this.props.projectId !== null) { @@ -65,6 +84,17 @@ class GUI extends React.Component { // At this time the project view in www doesn't need to know when a project is unloaded this.props.onProjectLoaded(); } + + // Once the GUI component has been rendered, always render GUI and do + // not revert back to a Loader in this component. + // + // This makes GUI container not a pure component. We don't want to use + // state for this. That would possibly cause a full second render of GUI + // after the first one. + const {fontsLoaded, fetchingProject, isLoading} = this.props; + this.isAfterGUI = this.isAfterGUI || ( + fontsLoaded && !fetchingProject && !isLoading + ); } setReduxTitle (newTitle) { if (newTitle === null || typeof newTitle === 'undefined') { @@ -75,6 +105,36 @@ class GUI extends React.Component { this.props.onUpdateReduxProjectTitle(newTitle); } } + ensureRenderer () { + if (this.props.vm.renderer) { + return; + } + + // Wait to load svg-renderer and render after the data request. This + // way the data request is made earlier. + const Renderer = require('scratch-render'); + const { + SVGRenderer: V2SVGAdapter, + BitmapAdapter: V2BitmapAdapter + } = require('scratch-svg-renderer'); + + const vm = this.props.vm; + this.canvas = document.createElement('canvas'); + this.renderer = new Renderer(this.canvas); + vm.attachRenderer(this.renderer); + + vm.attachV2SVGAdapter(new V2SVGAdapter()); + vm.attachV2BitmapAdapter(new V2BitmapAdapter()); + + // Only attach a video provider once because it is stateful + vm.setVideoProvider(new VideoProvider()); + + // Calling draw a single time before any project is loaded just + // makes the canvas white instead of solid black–needed because it + // is not possible to use CSS to style the canvas to have a + // different default color + vm.renderer.draw(); + } render () { if (this.props.isError) { throw new Error( @@ -85,6 +145,7 @@ class GUI extends React.Component { assetHost, cloudHost, error, + fontsLoaded, isError, isScratchDesktop, isShowingProject, @@ -102,6 +163,16 @@ class GUI extends React.Component { loadingStateVisible, ...componentProps } = this.props; + + if (!this.isAfterGUI && ( + !fontsLoaded || fetchingProject || isLoading + )) { + // Make sure a renderer exists. + if (fontsLoaded && !fetchingProject) this.ensureRenderer(); + return ; + } + + const GUIComponent = require('../components/gui/gui.jsx').default; return ( { costumeLibraryVisible: state.scratchGui.modals.costumeLibrary, costumesTabVisible: state.scratchGui.editorTab.activeTabIndex === COSTUMES_TAB_INDEX, error: state.scratchGui.projectState.error, + fontsLoaded: state.scratchGui.fontsLoaded, isError: getIsError(loadingState), isFullScreen: state.scratchGui.mode.isFullScreen, isPlayerOnly: state.scratchGui.mode.isPlayerOnly,