import React from 'react'; import {FormattedMessage, injectIntl, intlShape, defineMessages} from 'react-intl'; import {connect} from 'react-redux'; import classNames from 'classnames'; import PropTypes from 'prop-types'; import bindAll from 'lodash.bindall'; import styles from './loader.css'; import {getIsLoadingWithId} from '../../reducers/project-state'; import topBlock from './top-block.svg'; import middleBlock from './middle-block.svg'; import bottomBlock from './bottom-block.svg'; const mainMessages = { 'gui.loader.headline': ( ), 'gui.loader.creating': ( ) }; const messages = defineMessages({ projectData: { defaultMessage: 'Loading project …', description: 'Appears when loading project data, but not assets yet', id: 'tw.loader.projectData' }, downloadingAssets: { defaultMessage: 'Downloading assets ({complete}/{total}) …', description: 'Appears when loading project assets from a project on a remote website', id: 'tw.loader.downloadingAssets' }, loadingAssets: { defaultMessage: 'Loading assets ({complete}/{total}) …', description: 'Appears when loading project assets from a project file on the user\'s computer', id: 'tw.loader.loadingAssets' } }); // Because progress events are fired so often during the very performance-critical loading // process and React updates are very slow, we bypass React for updating the progress bar. class LoaderComponent extends React.Component { constructor (props) { super(props); bindAll(this, [ 'handleAssetProgress', 'handleProjectLoaded', 'barInnerRef', 'messageRef' ]); this.barInnerEl = null; this.messageEl = null; this.ignoreProgress = false; } componentDidMount () { this.handleAssetProgress( this.props.vm.runtime.finishedAssetRequests, this.props.vm.runtime.totalAssetRequests ); this.props.vm.on('ASSET_PROGRESS', this.handleAssetProgress); this.props.vm.runtime.on('PROJECT_LOADED', this.handleProjectLoaded); } componentWillUnmount () { this.props.vm.off('ASSET_PROGRESS', this.handleAssetProgress); this.props.vm.runtime.off('PROJECT_LOADED', this.handleProjectLoaded); } handleAssetProgress (finished, total) { if (this.ignoreProgress || !this.barInnerEl || !this.messageEl) { return; } if (total === 0) { // Started loading a new project. this.barInnerEl.style.width = '0'; this.messageEl.textContent = this.props.intl.formatMessage(messages.projectData); } else { this.barInnerEl.style.width = `${finished / total * 100}%`; const message = this.props.isRemote ? messages.downloadingAssets : messages.loadingAssets; this.messageEl.textContent = this.props.intl.formatMessage(message, { complete: finished, total }); } } handleProjectLoaded () { if (this.ignoreProgress || !this.barInnerEl || !this.messageEl) { return; } this.ignoreProgress = true; this.props.vm.runtime.resetProgress(); } barInnerRef (barInner) { this.barInnerEl = barInner; } messageRef (message) { this.messageEl = message; } render () { return (
{mainMessages[this.props.messageId]}
); } } LoaderComponent.propTypes = { intl: intlShape, isFullScreen: PropTypes.bool, isRemote: PropTypes.bool, messageId: PropTypes.string, vm: PropTypes.shape({ on: PropTypes.func, off: PropTypes.func, runtime: PropTypes.shape({ totalAssetRequests: PropTypes.number, finishedAssetRequests: PropTypes.number, resetProgress: PropTypes.func, on: PropTypes.func, off: PropTypes.func }) }) }; LoaderComponent.defaultProps = { isFullScreen: false, messageId: 'gui.loader.headline' }; const mapStateToProps = state => ({ isRemote: getIsLoadingWithId(state.scratchGui.projectState.loadingState), vm: state.scratchGui.vm }); const mapDispatchToProps = () => ({}); export default connect( mapStateToProps, mapDispatchToProps )(injectIntl(LoaderComponent));