diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index ea705596f58c60d7a9a4246a1a2e6e929d3ebf4f..49b29672da7f6a9c459520bd62fecce3fe8d7d91 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -20,6 +20,7 @@ import { closeBackdropLibrary } from '../reducers/modals'; +import FontLoaderHOC from '../lib/font-loader-hoc.jsx'; import ProjectFetcherHOC from '../lib/project-fetcher-hoc.jsx'; import ProjectSaverHOC from '../lib/project-saver-hoc.jsx'; import vmListenerHOC from '../lib/vm-listener-hoc.jsx'; @@ -132,6 +133,7 @@ const ConnectedGUI = connect( // ability to compose reducers. const WrappedGui = compose( ErrorBoundaryHOC('Top Level App'), + FontLoaderHOC, ProjectFetcherHOC, ProjectSaverHOC, vmListenerHOC, diff --git a/src/lib/font-loader-hoc.jsx b/src/lib/font-loader-hoc.jsx new file mode 100644 index 0000000000000000000000000000000000000000..22193f3a9554df4cbe0336983c62a37eb10d3112 --- /dev/null +++ b/src/lib/font-loader-hoc.jsx @@ -0,0 +1,66 @@ +import React from 'react'; + +/* Higher Order Component to provide behavior for loading fonts. + * @param {React.Component} WrappedComponent component to receive fontsLoaded prop + * @returns {React.Component} component with font loading behavior + */ +const FontLoaderHOC = function (WrappedComponent) { + class FontLoaderComponent extends React.Component { + constructor (props) { + super(props); + this.state = { + fontsLoaded: false + }; + } + componentDidMount () { + const getFontPromises = () => { + const fontPromises = []; + // Browsers that support the font loader interface have an iterable document.fonts.values() + // Firefox has a mocked out object that doesn't actually implement iterable, which is why + // the deep safety check is necessary. + if (document.fonts && + typeof document.fonts.values === 'function' && + typeof document.fonts.values()[Symbol.iterator] === 'function') { + for (const fontFace of document.fonts.values()) { + fontPromises.push(fontFace.loaded); + fontFace.load(); + } + } + return fontPromises; + }; + // Font promises must be gathered after the document is loaded, because on Mac Chrome, the promise + // objects get replaced and the old ones never resolve. + if (document.readyState === 'complete') { + Promise.all(getFontPromises()).then(() => { + this.setState({fontsLoaded: true}); + }); + } else { + document.onreadystatechange = () => { + if (document.readyState !== 'complete') return; + document.onreadystatechange = null; + Promise.all(getFontPromises()).then(() => { + this.setState({fontsLoaded: true}); + }); + }; + } + } + render () { + const { + /* eslint-disable no-unused-vars */ + /* eslint-enable no-unused-vars */ + ...componentProps + } = this.props; + return ( + <WrappedComponent + fontsLoaded={this.state.fontsLoaded} + {...componentProps} + /> + ); + } + } + return FontLoaderComponent; +}; + +export { + FontLoaderHOC as default +}; diff --git a/src/lib/vm-manager-hoc.jsx b/src/lib/vm-manager-hoc.jsx index 449bff0eec2399b6792bda68b2b65c313a69e083..251670dd87fb97668a6a1e0961768e02cb765cb1 100644 --- a/src/lib/vm-manager-hoc.jsx +++ b/src/lib/vm-manager-hoc.jsx @@ -5,6 +5,10 @@ import {connect} from 'react-redux'; import VM from 'scratch-vm'; import AudioEngine from 'scratch-audio'; +import Renderer from 'scratch-render'; +import VideoProvider from '../lib/video/video-provider'; +import {SVGRenderer as V2SVGAdapter} from 'scratch-svg-renderer'; +import {BitmapAdapter as V2BitmapAdapter} from 'scratch-svg-renderer'; import { LoadingStates, @@ -38,7 +42,10 @@ const vmManagerHOC = function (WrappedComponent) { this.props.vm.initialized = true; } componentDidUpdate (prevProps) { - if (this.props.isLoadingWithId && !prevProps.isLoadingWithId) { + // if project state is LOADING variation, and fonts are loaded, + // and that wasn't true until now, load project + if (this.props.isLoadingWithId && this.props.fontsLoaded && + (!prevProps.isLoadingWithId || !prevProps.fontsLoaded)) { this.loadProject(this.props.projectData, this.props.loadingState); } } @@ -56,6 +63,7 @@ const vmManagerHOC = function (WrappedComponent) { render () { const { /* eslint-disable no-unused-vars */ + fontsLoaded, onLoadedProject: onLoadedProjectProp, projectData, projectId, @@ -65,10 +73,6 @@ const vmManagerHOC = function (WrappedComponent) { vm, ...componentProps } = this.props; - // don't display anything until we have data loaded - if (!this.props.projectData) { - return null; - } return ( <WrappedComponent errorMessage={this.state.errorMessage} @@ -82,6 +86,7 @@ const vmManagerHOC = function (WrappedComponent) { } VMManager.propTypes = { + fontsLoaded: PropTypes.bool, isLoadingWithId: PropTypes.bool, loadingState: PropTypes.oneOf(LoadingStates), onLoadedProject: PropTypes.func,