Skip to content
Snippets Groups Projects
Unverified Commit 41fc932e authored by Andrew Sliwinski's avatar Andrew Sliwinski Committed by GitHub
Browse files

Merge pull request #3366 from fsih/preloadFonts

Preload fonts
parents c81e0e56 80bee424
No related branches found
No related tags found
No related merge requests found
...@@ -20,6 +20,7 @@ import { ...@@ -20,6 +20,7 @@ import {
closeBackdropLibrary closeBackdropLibrary
} from '../reducers/modals'; } from '../reducers/modals';
import FontLoaderHOC from '../lib/font-loader-hoc.jsx';
import ProjectFetcherHOC from '../lib/project-fetcher-hoc.jsx'; import ProjectFetcherHOC from '../lib/project-fetcher-hoc.jsx';
import ProjectSaverHOC from '../lib/project-saver-hoc.jsx'; import ProjectSaverHOC from '../lib/project-saver-hoc.jsx';
import vmListenerHOC from '../lib/vm-listener-hoc.jsx'; import vmListenerHOC from '../lib/vm-listener-hoc.jsx';
...@@ -132,6 +133,7 @@ const ConnectedGUI = connect( ...@@ -132,6 +133,7 @@ const ConnectedGUI = connect(
// ability to compose reducers. // ability to compose reducers.
const WrappedGui = compose( const WrappedGui = compose(
ErrorBoundaryHOC('Top Level App'), ErrorBoundaryHOC('Top Level App'),
FontLoaderHOC,
ProjectFetcherHOC, ProjectFetcherHOC,
ProjectSaverHOC, ProjectSaverHOC,
vmListenerHOC, vmListenerHOC,
......
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 () {
return (
<WrappedComponent
fontsLoaded={this.state.fontsLoaded}
{...this.props}
/>
);
}
}
return FontLoaderComponent;
};
export {
FontLoaderHOC as default
};
...@@ -38,7 +38,10 @@ const vmManagerHOC = function (WrappedComponent) { ...@@ -38,7 +38,10 @@ const vmManagerHOC = function (WrappedComponent) {
this.props.vm.initialized = true; this.props.vm.initialized = true;
} }
componentDidUpdate (prevProps) { componentDidUpdate (prevProps) {
if (this.props.isLoadingWithId && !prevProps.isLoadingWithId) { // if project is in loading state, AND fonts are loaded,
// and they weren't both that way until now... load project!
if (this.props.isLoadingWithId && this.props.fontsLoaded &&
(!prevProps.isLoadingWithId || !prevProps.fontsLoaded)) {
this.loadProject(this.props.projectData, this.props.loadingState); this.loadProject(this.props.projectData, this.props.loadingState);
} }
} }
...@@ -56,6 +59,7 @@ const vmManagerHOC = function (WrappedComponent) { ...@@ -56,6 +59,7 @@ const vmManagerHOC = function (WrappedComponent) {
render () { render () {
const { const {
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
fontsLoaded,
onLoadedProject: onLoadedProjectProp, onLoadedProject: onLoadedProjectProp,
projectData, projectData,
projectId, projectId,
...@@ -65,10 +69,6 @@ const vmManagerHOC = function (WrappedComponent) { ...@@ -65,10 +69,6 @@ const vmManagerHOC = function (WrappedComponent) {
vm, vm,
...componentProps ...componentProps
} = this.props; } = this.props;
// don't display anything until we have data loaded
if (!this.props.projectData) {
return null;
}
return ( return (
<WrappedComponent <WrappedComponent
errorMessage={this.state.errorMessage} errorMessage={this.state.errorMessage}
...@@ -82,6 +82,7 @@ const vmManagerHOC = function (WrappedComponent) { ...@@ -82,6 +82,7 @@ const vmManagerHOC = function (WrappedComponent) {
} }
VMManager.propTypes = { VMManager.propTypes = {
fontsLoaded: PropTypes.bool,
isLoadingWithId: PropTypes.bool, isLoadingWithId: PropTypes.bool,
loadingState: PropTypes.oneOf(LoadingStates), loadingState: PropTypes.oneOf(LoadingStates),
onLoadedProject: PropTypes.func, onLoadedProject: PropTypes.func,
......
...@@ -60,6 +60,7 @@ describe('VMManagerHOC', () => { ...@@ -60,6 +60,7 @@ describe('VMManagerHOC', () => {
const WrappedComponent = vmManagerHOC(Component); const WrappedComponent = vmManagerHOC(Component);
const mounted = mount( const mounted = mount(
<WrappedComponent <WrappedComponent
fontsLoaded
isLoadingWithId={false} isLoadingWithId={false}
store={store} store={store}
vm={vm} vm={vm}
...@@ -75,27 +76,46 @@ describe('VMManagerHOC', () => { ...@@ -75,27 +76,46 @@ describe('VMManagerHOC', () => {
// nextTick needed since vm.loadProject is async, and we have to wait for it :/ // nextTick needed since vm.loadProject is async, and we have to wait for it :/
process.nextTick(() => expect(mockedOnLoadedProject).toHaveBeenLastCalledWith(LoadingState.LOADING_VM_WITH_ID)); process.nextTick(() => expect(mockedOnLoadedProject).toHaveBeenLastCalledWith(LoadingState.LOADING_VM_WITH_ID));
}); });
test('if there is projectData, the child is rendered', () => { test('if the fontsLoaded prop becomes true, it loads project data into the vm', () => {
vm.loadProject = jest.fn(() => Promise.resolve());
const mockedOnLoadedProject = jest.fn();
const Component = () => <div />; const Component = () => <div />;
const WrappedComponent = vmManagerHOC(Component); const WrappedComponent = vmManagerHOC(Component);
const mounted = mount( const mounted = mount(
<WrappedComponent <WrappedComponent
projectData="100" isLoadingWithId
store={store} store={store}
vm={vm} vm={vm}
onLoadedProject={mockedOnLoadedProject}
/> />
); );
expect(mounted.find('div').length).toBe(1); mounted.setProps({
fontsLoaded: true,
loadingState: LoadingState.LOADING_VM_WITH_ID,
projectData: '100'
});
expect(vm.loadProject).toHaveBeenLastCalledWith('100');
// nextTick needed since vm.loadProject is async, and we have to wait for it :/
process.nextTick(() => expect(mockedOnLoadedProject).toHaveBeenLastCalledWith(LoadingState.LOADING_VM_WITH_ID));
}); });
test('if there is no projectData, nothing is rendered', () => { test('if the fontsLoaded prop is false, project data is never loaded', () => {
vm.loadProject = jest.fn(() => Promise.resolve());
const mockedOnLoadedProject = jest.fn();
const Component = () => <div />; const Component = () => <div />;
const WrappedComponent = vmManagerHOC(Component); const WrappedComponent = vmManagerHOC(Component);
const mounted = mount( const mounted = mount(
<WrappedComponent <WrappedComponent
isLoadingWithId
store={store} store={store}
vm={vm} vm={vm}
onLoadedProject={mockedOnLoadedProject}
/> />
); );
expect(mounted.find('div').length).toBe(0); mounted.setProps({
loadingState: LoadingState.LOADING_VM_WITH_ID,
projectData: '100'
});
expect(vm.loadProject).toHaveBeenCalledTimes(0);
process.nextTick(() => expect(mockedOnLoadedProject).toHaveBeenCalledTimes(0));
}); });
}); });
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment