split HOC in two: one inside AppStateHOC, one out

`ScratchDesktopOuterComponent` is now responsible for
`showTelemetryModal` which only works if it comes from outside the
`AppStateHOC` since it's used in the `AppStateHOC` constructor. The
outer component also handles a few static props, like
`isScratchDesktop`.

`ScratchDesktopInnerComponent` handles everything else, most notably
anything that interacts with the state established by `AppStateHOC`.
This commit is contained in:
Christopher Willis-Ford 2020-11-17 15:55:30 -08:00
parent d8f289f35a
commit 19a47ecde8

View file

@ -7,7 +7,7 @@ import ReactDOM from 'react-dom';
import {connect} from 'react-redux'; import {connect} from 'react-redux';
import {compose} from 'redux'; import {compose} from 'redux';
import GUI from 'scratch-gui/src/index'; import GUI from 'scratch-gui/src/index';
import VM from 'scratch-vm'; import GUIComponent from 'scratch-gui/src/components/gui/gui.jsx';
import AppStateHOC from 'scratch-gui/src/lib/app-state-hoc.jsx'; import AppStateHOC from 'scratch-gui/src/lib/app-state-hoc.jsx';
import { import {
@ -43,8 +43,26 @@ document.body.appendChild(appTarget);
GUI.setAppElement(appTarget); GUI.setAppElement(appTarget);
const ScratchDesktopHOC = function (WrappedComponent) { const ScratchDesktopOuterHOC = function (WrappedComponent) {
class ScratchDesktopComponent extends React.Component { const ScratchDesktopOuterComponent = function (props) {
const shouldShowTelemetryModal = (typeof ipcRenderer.sendSync('getTelemetryDidOptIn') !== 'boolean');
return (<WrappedComponent
canEditTitle
canModifyCloudData={false}
isScratchDesktop
showTelemetryModal={shouldShowTelemetryModal}
// allow passed-in props to override any of the above
{...props}
/>);
};
return ScratchDesktopOuterComponent;
};
const ScratchDesktopInnerHOC = function (WrappedComponent) {
class ScratchDesktopInnerComponent extends React.Component {
constructor (props) { constructor (props) {
super(props); super(props);
bindAll(this, [ bindAll(this, [
@ -116,15 +134,9 @@ const ScratchDesktopHOC = function (WrappedComponent) {
this.setState({projectTitle: newTitle}); this.setState({projectTitle: newTitle});
} }
render () { render () {
const shouldShowTelemetryModal = (typeof ipcRenderer.sendSync('getTelemetryDidOptIn') !== 'boolean'); const childProps = omit(this.props, Object.keys(ScratchDesktopInnerComponent.propTypes));
const childProps = omit(this.props, Object.keys(ScratchDesktopComponent.propTypes));
return (<WrappedComponent return (<WrappedComponent
canEditTitle
canModifyCloudData={false}
isScratchDesktop
showTelemetryModal={shouldShowTelemetryModal}
onClickLogo={this.handleClickLogo} onClickLogo={this.handleClickLogo}
onProjectTelemetryEvent={this.handleProjectTelemetryEvent} onProjectTelemetryEvent={this.handleProjectTelemetryEvent}
onStorageInit={this.handleStorageInit} onStorageInit={this.handleStorageInit}
@ -138,7 +150,7 @@ const ScratchDesktopHOC = function (WrappedComponent) {
} }
} }
ScratchDesktopComponent.propTypes = { ScratchDesktopInnerComponent.propTypes = {
loadingState: PropTypes.oneOf(LoadingStates), loadingState: PropTypes.oneOf(LoadingStates),
onFetchedInitialProjectData: PropTypes.func, onFetchedInitialProjectData: PropTypes.func,
onHasInitialProject: PropTypes.func, onHasInitialProject: PropTypes.func,
@ -146,7 +158,8 @@ const ScratchDesktopHOC = function (WrappedComponent) {
onLoadingCompleted: PropTypes.func, onLoadingCompleted: PropTypes.func,
onLoadingStarted: PropTypes.func, onLoadingStarted: PropTypes.func,
onRequestNewProject: PropTypes.func, onRequestNewProject: PropTypes.func,
vm: PropTypes.instanceOf(VM).isRequired // using PropTypes.instanceOf(VM) here will cause prop type warnings due to VM mismatch
vm: GUIComponent.WrappedComponent.propTypes.vm
}; };
const mapStateToProps = state => { const mapStateToProps = state => {
const loadingState = state.scratchGui.projectState.loadingState; const loadingState = state.scratchGui.projectState.loadingState;
@ -177,15 +190,16 @@ const ScratchDesktopHOC = function (WrappedComponent) {
onRequestNewProject: () => dispatch(requestNewProject(false)) onRequestNewProject: () => dispatch(requestNewProject(false))
}); });
return connect(mapStateToProps, mapDispatchToProps)(ScratchDesktopComponent); return connect(mapStateToProps, mapDispatchToProps)(ScratchDesktopInnerComponent);
}; };
// note that redux's 'compose' function is just being used as a general utility to make // note that redux's 'compose' function is just being used as a general utility to make
// the hierarchy of HOC constructor calls clearer here; it has nothing to do with redux's // the hierarchy of HOC constructor calls clearer here; it has nothing to do with redux's
// ability to compose reducers. // ability to compose reducers.
const WrappedGui = compose( const WrappedGui = compose(
ScratchDesktopOuterHOC,
AppStateHOC, AppStateHOC,
ScratchDesktopHOC ScratchDesktopInnerHOC
)(GUI); )(GUI);
ReactDOM.render(<WrappedGui />, appTarget); ReactDOM.render(<WrappedGui />, appTarget);