diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index 5c7a378dc43c44ff9f3d5d37e3b9ae79ffbbe87f..b7f2a0acd1d73abf9f683dd8c7d3505394c8a441 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -14,7 +14,7 @@ import TargetPane from '../../containers/target-pane.jsx'; import SoundTab from '../../containers/sound-tab.jsx'; import StageHeader from '../../containers/stage-header.jsx'; import Stage from '../../containers/stage.jsx'; - +import Loader from '../loader/loader.jsx'; import Box from '../box/box.jsx'; import FeedbackForm from '../feedback-form/feedback-form.jsx'; import MenuBar from '../menu-bar/menu-bar.jsx'; @@ -44,6 +44,7 @@ const GUIComponent = props => { feedbackFormVisible, importInfoVisible, intl, + loading, onExtensionButtonClick, onActivateTab, previewInfoVisible, @@ -78,6 +79,9 @@ const GUIComponent = props => { {previewInfoVisible ? ( <PreviewModal /> ) : null} + {loading ? ( + <Loader /> + ) : null} {importInfoVisible ? ( <ImportModal /> ) : null} @@ -176,6 +180,7 @@ GUIComponent.propTypes = { feedbackFormVisible: PropTypes.bool, importInfoVisible: PropTypes.bool, intl: intlShape.isRequired, + loading: PropTypes.bool, onActivateTab: PropTypes.func, onExtensionButtonClick: PropTypes.func, onTabSelect: PropTypes.func, diff --git a/src/components/loader/bottom-block.svg b/src/components/loader/bottom-block.svg new file mode 100644 index 0000000000000000000000000000000000000000..b397068c448eeabc04d4245e1f5cdd210b72df5c Binary files /dev/null and b/src/components/loader/bottom-block.svg differ diff --git a/src/components/loader/loader.css b/src/components/loader/loader.css new file mode 100644 index 0000000000000000000000000000000000000000..68f8e5f4f08f852f5f1ac3c968d30e5c33729a8e --- /dev/null +++ b/src/components/loader/loader.css @@ -0,0 +1,89 @@ +@import "../../css/colors.css"; + +.background { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 999; /* Below preview modal */ + display: flex; + justify-content: center; + align-items: center; + background-color: $motion-primary; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + text-align: center; + color: white; +} + +.block-animation { + width: 125px; + height: 150px; + margin: 50px auto 0px; +} + +.block-animation img { + display: block; + position: relative; + height: 30%; + margin-top: -4px; +} + +.topBlock { + animation: top-slide-in 1.5s ease infinite; +} + +.middleBlock { + animation: middle-slide-in 1.5s ease infinite; +} + +.bottomBlock { + animation: bottom-slide-in 1.5s ease infinite; +} + + +@keyframes top-slide-in { + 0% { + transform: translateY(50px); + opacity: 0; + } + + 33% { + transform: translateY(0px); + opacity: 1; + } +} + +@keyframes middle-slide-in { + 0% { + transform: translateY(50px); + opacity: 0; + } + + 33% { + transform: translateY(50px); + opacity: 0; + } + + 66% { + transform: translateY(0px); + opacity: 1; + } +} + +@keyframes bottom-slide-in { + 0% { + transform: translateY(50px); + opacity: 0; + } + + 66% { + transform: translateY(50px); + opacity: 0; + } + + 100% { + transform: translateY(0px); + opacity: 1; + } +} diff --git a/src/components/loader/loader.jsx b/src/components/loader/loader.jsx new file mode 100644 index 0000000000000000000000000000000000000000..8a93e5c36abd385665c9fcdd1374f30096d9c0a8 --- /dev/null +++ b/src/components/loader/loader.jsx @@ -0,0 +1,144 @@ +import React from 'react'; +import {FormattedMessage} from 'react-intl'; +import styles from './loader.css'; + +import topBlock from './top-block.svg'; +import middleBlock from './middle-block.svg'; +import bottomBlock from './bottom-block.svg'; + +const LoaderComponent = () => { + const messages = [ + { + message: ( + <FormattedMessage + defaultMessage="Creating blocks …" + description="One of the loading messages" + id="gui.loader.message1" + /> + ), + weight: 50 + }, + { + message: ( + <FormattedMessage + defaultMessage="Loading sprites …" + description="One of the loading messages" + id="gui.loader.message2" + /> + ), + weight: 50 + }, + { + message: ( + <FormattedMessage + defaultMessage="Loading sounds …" + description="One of the loading messages" + id="gui.loader.message3" + /> + ), + weight: 50 + }, + { + message: ( + <FormattedMessage + defaultMessage="Loading extensions …" + description="One of the loading messages" + id="gui.loader.message4" + /> + ), + weight: 50 + }, + { + message: ( + <FormattedMessage + defaultMessage="Creating blocks …" + description="One of the loading messages" + id="gui.loader.message1" + /> + ), + weight: 20 + }, + { + message: ( + <FormattedMessage + defaultMessage="Herding cats …" + description="One of the loading messages" + id="gui.loader.message5" + /> + ), + weight: 1 + }, + { + message: ( + <FormattedMessage + defaultMessage="Transmitting nanos …" + description="One of the loading messages" + id="gui.loader.message6" + /> + ), + weight: 1 + }, + { + message: ( + <FormattedMessage + defaultMessage="Inflating gobos …" + description="One of the loading messages" + id="gui.loader.message7" + /> + ), + weight: 1 + }, + { + message: ( + <FormattedMessage + defaultMessage="Preparing emojiis …" + description="One of the loading messages" + id="gui.loader.message8" + /> + ), + weight: 1 + } + ]; + + let message; + const sum = messages.reduce((acc, m) => acc + m.weight, 0); + let rand = sum * Math.random(); + for (let i = 0; i < messages.length; i++) { + rand -= messages[i].weight; + if (rand <= 0) { + message = messages[i].message; + break; + } + } + + return ( + <div className={styles.background}> + <div className={styles.container}> + <div className={styles.blockAnimation}> + <img + className={styles.topBlock} + src={topBlock} + /> + <img + className={styles.middleBlock} + src={middleBlock} + /> + <img + className={styles.bottomBlock} + src={bottomBlock} + /> + </div> + <h1 className={styles.title}> + <FormattedMessage + defaultMessage="Loading Project" + description="Main loading message" + id="gui.loader.headline" + /> + </h1> + <p>{message}</p> + </div> + </div> + ); +}; + +export default LoaderComponent; diff --git a/src/components/loader/middle-block.svg b/src/components/loader/middle-block.svg new file mode 100644 index 0000000000000000000000000000000000000000..6e52d4213a9e8bfff2c8a0289fa324cfcbd60c27 Binary files /dev/null and b/src/components/loader/middle-block.svg differ diff --git a/src/components/loader/top-block.svg b/src/components/loader/top-block.svg new file mode 100644 index 0000000000000000000000000000000000000000..2b52a0d5d09684056643fb04aec7c80967569c95 Binary files /dev/null and b/src/components/loader/top-block.svg differ diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index de4fe27a284623c4058f07e62da59d32fd378643..320eedcdb6661a076d39bea33892c9e4a0deefa4 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -17,16 +17,29 @@ import vmListenerHOC from '../lib/vm-listener-hoc.jsx'; import GUIComponent from '../components/gui/gui.jsx'; class GUI extends React.Component { + constructor (props) { + super(props); + this.state = { + loading: true + }; + } componentDidMount () { this.audioEngine = new AudioEngine(); this.props.vm.attachAudioEngine(this.audioEngine); - this.props.vm.loadProject(this.props.projectData); - this.props.vm.setCompatibilityMode(true); - this.props.vm.start(); + this.props.vm.loadProject(this.props.projectData).then(() => { + this.setState({loading: false}, () => { + this.props.vm.setCompatibilityMode(true); + this.props.vm.start(); + }); + }); } componentWillReceiveProps (nextProps) { if (this.props.projectData !== nextProps.projectData) { - this.props.vm.loadProject(nextProps.projectData); + this.setState({loading: true}, () => { + this.props.vm.loadProject(nextProps.projectData).then(() => { + this.setState({loading: false}); + }); + }); } } componentWillUnmount () { @@ -35,12 +48,14 @@ class GUI extends React.Component { render () { const { children, + fetchingProject, projectData, // eslint-disable-line no-unused-vars vm, ...componentProps } = this.props; return ( <GUIComponent + loading={fetchingProject || this.state.loading} vm={vm} {...componentProps} > @@ -53,6 +68,7 @@ class GUI extends React.Component { GUI.propTypes = { ...GUIComponent.propTypes, feedbackFormVisible: PropTypes.bool, + fetchingProject: PropTypes.bool, importInfoVisible: PropTypes.bool, previewInfoVisible: PropTypes.bool, projectData: PropTypes.string, diff --git a/src/lib/project-loader-hoc.jsx b/src/lib/project-loader-hoc.jsx index 854928c7eaa2720dff7150c586faf9e179d90116..2c20b9fed50736b490643d02ca40c06f11a051a0 100644 --- a/src/lib/project-loader-hoc.jsx +++ b/src/lib/project-loader-hoc.jsx @@ -17,21 +17,25 @@ const ProjectLoaderHOC = function (WrappedComponent) { this.updateProject = this.updateProject.bind(this); this.state = { projectId: null, - projectData: null + projectData: null, + fetchingProject: false }; } componentDidMount () { window.addEventListener('hashchange', this.updateProject); this.updateProject(); } - componentDidUpdate (prevProps, prevState) { - if (this.state.projectId !== prevState.projectId) { - storage - .load(storage.AssetType.Project, this.state.projectId, storage.DataFormat.JSON) - .then(projectAsset => projectAsset && this.setState({ - projectData: projectAsset.data.toString() - })) - .catch(err => log.error(err)); + componentWillUpdate (nextProps, nextState) { + if (this.state.projectId !== nextState.projectId) { + this.setState({fetchingProject: true}, () => { + storage + .load(storage.AssetType.Project, this.state.projectId, storage.DataFormat.JSON) + .then(projectAsset => projectAsset && this.setState({ + projectData: projectAsset.data.toString(), + fetchingProject: false + })) + .catch(err => log.error(err)); + }); } } componentWillUnmount () { @@ -60,6 +64,7 @@ const ProjectLoaderHOC = function (WrappedComponent) { if (!this.state.projectData) return null; return ( <WrappedComponent + fetchingProject={this.state.fetchingProject} projectData={this.state.projectData} {...this.props} />