168 lines
5.7 KiB
React
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;
|