Files
scratch-gui/src/lib/cloud-manager-hoc.jsx
T

168 lines
5.7 KiB
React

import PropTypes from 'prop-types';
import React from 'react';
import {connect} from 'react-redux';
import bindAll from 'lodash.bindall';
import VM from 'scratch-vm';
import CloudProvider from '../lib/cloud-provider';
import {
getIsShowingWithId
} from '../reducers/project-state';
import {
showAlertWithTimeout
} from '../reducers/alerts';
/*
* Higher Order Component to manage the connection to the cloud server.
* @param {React.Component} WrappedComponent component to manage VM events for
* @returns {React.Component} connected component with vm events bound to redux
*/
const cloudManagerHOC = function (WrappedComponent) {
class CloudManager extends React.Component {
constructor (props) {
super(props);
this.cloudProvider = null;
bindAll(this, [
'handleCloudDataUpdate'
]);
this.props.vm.on('HAS_CLOUD_DATA_UPDATE', this.handleCloudDataUpdate);
}
componentDidMount () {
if (this.shouldConnect(this.props)) {
this.connectToCloud();
}
}
componentDidUpdate (prevProps) {
// TODO need to add cloud provider disconnection logic and cloud data clearing logic
// when loading a new project e.g. via file upload
// (and eventually move it out of the vm.clear function)
if (this.shouldConnect(this.props) && !this.shouldConnect(prevProps)) {
this.connectToCloud();
}
if (this.shouldDisconnect(this.props, prevProps)) {
this.disconnectFromCloud();
}
}
componentWillUnmount () {
this.disconnectFromCloud();
}
canUseCloud (props) {
return !!(props.cloudHost && props.username && props.vm && props.projectId && props.hasCloudPermission);
}
shouldConnect (props) {
return !this.isConnected() && this.canUseCloud(props) &&
props.isShowingWithId && props.vm.runtime.hasCloudData() &&
props.canModifyCloudData;
}
shouldDisconnect (props, prevProps) {
return this.isConnected() &&
( // Can no longer use cloud or cloud provider info is now stale
!this.canUseCloud(props) ||
!props.vm.runtime.hasCloudData() ||
(props.projectId !== prevProps.projectId) ||
(props.username !== prevProps.username) ||
// Editing someone else's project
!props.canModifyCloudData
);
}
isConnected () {
return this.cloudProvider && !!this.cloudProvider.connection;
}
connectToCloud () {
this.cloudProvider = new CloudProvider(
this.props.cloudHost,
this.props.vm,
this.props.username,
this.props.projectId);
this.props.vm.setCloudProvider(this.cloudProvider);
}
disconnectFromCloud () {
if (this.cloudProvider) {
this.cloudProvider.requestCloseConnection();
this.cloudProvider = null;
this.props.vm.setCloudProvider(null);
}
}
handleCloudDataUpdate (projectHasCloudData) {
if (this.isConnected() && !projectHasCloudData) {
this.disconnectFromCloud();
} else if (this.shouldConnect(this.props)) {
this.props.onShowCloudInfo();
this.connectToCloud();
}
}
render () {
const {
/* eslint-disable no-unused-vars */
canModifyCloudData,
cloudHost,
projectId,
username,
hasCloudPermission,
isShowingWithId,
onShowCloudInfo,
/* eslint-enable no-unused-vars */
vm,
...componentProps
} = this.props;
return (
<WrappedComponent
canUseCloud={this.canUseCloud(this.props)}
vm={vm}
{...componentProps}
/>
);
}
}
CloudManager.propTypes = {
canModifyCloudData: PropTypes.bool.isRequired,
cloudHost: PropTypes.string,
hasCloudPermission: PropTypes.bool,
isShowingWithId: PropTypes.bool.isRequired,
onShowCloudInfo: PropTypes.func,
projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
username: PropTypes.string,
vm: PropTypes.instanceOf(VM).isRequired
};
CloudManager.defaultProps = {
cloudHost: null,
hasCloudPermission: false,
onShowCloudInfo: () => {},
username: null
};
const mapStateToProps = (state, ownProps) => {
const loadingState = state.scratchGui.projectState.loadingState;
return {
isShowingWithId: getIsShowingWithId(loadingState),
projectId: state.scratchGui.projectState.projectId,
// if you're editing someone else's project, you can't modify cloud data
canModifyCloudData: (!state.scratchGui.mode.hasEverEnteredEditor || ownProps.canSave)
};
};
const mapDispatchToProps = dispatch => ({
onShowCloudInfo: () => showAlertWithTimeout(dispatch, 'cloudInfo')
});
// Allow incoming props to override redux-provided props. Used to mock in tests.
const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign(
{}, stateProps, dispatchProps, ownProps
);
return connect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(CloudManager);
};
export default cloudManagerHOC;