From b63ac51fb1e022e17303062d84408c2e62a739a2 Mon Sep 17 00:00:00 2001
From: Ben Wheeler <wheeler.benjamin@gmail.com>
Date: Sat, 14 Sep 2019 20:16:04 -0400
Subject: [PATCH] move project title management into titled hoc

---
 src/containers/gui.jsx | 41 +++----------------
 src/lib/titled-hoc.jsx | 93 ++++++++++++++++++++++++++++++++++++++----
 2 files changed, 90 insertions(+), 44 deletions(-)

diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx
index d2a47b412..d7b8f78c2 100644
--- a/src/containers/gui.jsx
+++ b/src/containers/gui.jsx
@@ -4,15 +4,13 @@ import {compose} from 'redux';
 import {connect} from 'react-redux';
 import ReactModal from 'react-modal';
 import VM from 'scratch-vm';
-import {defineMessages, injectIntl, intlShape} from 'react-intl';
+import {injectIntl, intlShape} from 'react-intl';
 
 import ErrorBoundaryHOC from '../lib/error-boundary-hoc.jsx';
 import {
     getIsError,
-    getIsShowingProject,
-    getIsShowingWithoutId
+    getIsShowingProject
 } from '../reducers/project-state';
-import {setProjectTitle} from '../reducers/project-title';
 import {
     activateTab,
     BLOCKS_TAB_INDEX,
@@ -30,6 +28,7 @@ import {
 import FontLoaderHOC from '../lib/font-loader-hoc.jsx';
 import LocalizationHOC from '../lib/localization-hoc.jsx';
 import ProjectFetcherHOC from '../lib/project-fetcher-hoc.jsx';
+import TitledHOC from '../lib/titled-hoc.jsx';
 import ProjectSaverHOC from '../lib/project-saver-hoc.jsx';
 import QueryParserHOC from '../lib/query-parser-hoc.jsx';
 import storage from '../lib/storage';
@@ -40,18 +39,9 @@ import cloudManagerHOC from '../lib/cloud-manager-hoc.jsx';
 import GUIComponent from '../components/gui/gui.jsx';
 import {setIsScratchDesktop} from '../lib/isScratchDesktop.js';
 
-const messages = defineMessages({
-    defaultProjectTitle: {
-        id: 'gui.gui.defaultProjectTitle',
-        description: 'Default title for project',
-        defaultMessage: 'Scratch Project'
-    }
-});
-
 class GUI extends React.Component {
     componentDidMount () {
         setIsScratchDesktop(this.props.isScratchDesktop);
-        this.setReduxTitle(this.props.projectTitle);
         this.props.onStorageInit(storage);
         this.props.onVmInit(this.props.vm);
     }
@@ -59,26 +49,11 @@ class GUI extends React.Component {
         if (this.props.projectId !== prevProps.projectId && this.props.projectId !== null) {
             this.props.onUpdateProjectId(this.props.projectId);
         }
-        if (this.props.projectTitle !== prevProps.projectTitle) {
-            this.setReduxTitle(this.props.projectTitle);
-        }
         if (this.props.isShowingProject && !prevProps.isShowingProject) {
             // this only notifies container when a project changes from not yet loaded to loaded
             // At this time the project view in www doesn't need to know when a project is unloaded
             this.props.onProjectLoaded();
         }
-        if (this.props.isShowingWithoutId && !prevProps.isShowingWithoutId) {
-            this.props.onUpdateProjectTitle(this.props.intl.formatMessage(messages.defaultProjectTitle));
-        }
-    }
-    setReduxTitle (newTitle) {
-        if (newTitle === null || typeof newTitle === 'undefined') {
-            this.props.onUpdateReduxProjectTitle(
-                this.props.intl.formatMessage(messages.defaultProjectTitle)
-            );
-        } else {
-            this.props.onUpdateReduxProjectTitle(newTitle);
-        }
     }
     render () {
         if (this.props.isError) {
@@ -96,11 +71,9 @@ class GUI extends React.Component {
             onProjectLoaded,
             onStorageInit,
             onUpdateProjectId,
-            onUpdateReduxProjectTitle,
             onVmInit,
             projectHost,
             projectId,
-            projectTitle,
             /* eslint-enable no-unused-vars */
             children,
             fetchingProject,
@@ -137,11 +110,9 @@ GUI.propTypes = {
     onStorageInit: PropTypes.func,
     onUpdateProjectId: PropTypes.func,
     onUpdateProjectTitle: PropTypes.func,
-    onUpdateReduxProjectTitle: PropTypes.func,
     onVmInit: PropTypes.func,
     projectHost: PropTypes.string,
     projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
-    projectTitle: PropTypes.string,
     telemetryModalVisible: PropTypes.bool,
     vm: PropTypes.instanceOf(VM).isRequired
 };
@@ -151,7 +122,6 @@ GUI.defaultProps = {
     onStorageInit: storageInstance => storageInstance.addOfficialScratchWebStores(),
     onProjectLoaded: () => {},
     onUpdateProjectId: () => {},
-    onUpdateProjectTitle: () => {},
     onVmInit: (/* vm */) => {}
 };
 
@@ -172,7 +142,6 @@ const mapStateToProps = state => {
         isPlayerOnly: state.scratchGui.mode.isPlayerOnly,
         isRtl: state.locales.isRtl,
         isShowingProject: getIsShowingProject(loadingState),
-        isShowingWithoutId: getIsShowingWithoutId(loadingState),
         loadingStateVisible: state.scratchGui.modals.loadingProject,
         projectId: state.scratchGui.projectState.projectId,
         soundsTabVisible: state.scratchGui.editorTab.activeTabIndex === SOUNDS_TAB_INDEX,
@@ -193,8 +162,7 @@ const mapDispatchToProps = dispatch => ({
     onActivateSoundsTab: () => dispatch(activateTab(SOUNDS_TAB_INDEX)),
     onRequestCloseBackdropLibrary: () => dispatch(closeBackdropLibrary()),
     onRequestCloseCostumeLibrary: () => dispatch(closeCostumeLibrary()),
-    onRequestCloseTelemetryModal: () => dispatch(closeTelemetryModal()),
-    onUpdateReduxProjectTitle: title => dispatch(setProjectTitle(title))
+    onRequestCloseTelemetryModal: () => dispatch(closeTelemetryModal())
 });
 
 const ConnectedGUI = injectIntl(connect(
@@ -211,6 +179,7 @@ const WrappedGui = compose(
     FontLoaderHOC,
     QueryParserHOC,
     ProjectFetcherHOC,
+    TitledHOC,
     ProjectSaverHOC,
     vmListenerHOC,
     vmManagerHOC,
diff --git a/src/lib/titled-hoc.jsx b/src/lib/titled-hoc.jsx
index 377d6150d..fc8c33b8e 100644
--- a/src/lib/titled-hoc.jsx
+++ b/src/lib/titled-hoc.jsx
@@ -1,5 +1,19 @@
+import PropTypes from 'prop-types';
 import React from 'react';
 import bindAll from 'lodash.bindall';
+import {connect} from 'react-redux';
+import {defineMessages, injectIntl, intlShape} from 'react-intl';
+
+import {getIsShowingWithoutId} from '../reducers/project-state';
+import {setProjectTitle} from '../reducers/project-title';
+
+const messages = defineMessages({
+    defaultProjectTitle: {
+        id: 'gui.gui.defaultProjectTitle',
+        description: 'Default title for project',
+        defaultMessage: 'Scratch Project'
+    }
+});
 
 /* Higher Order Component to get and set the project title
  * @param {React.Component} WrappedComponent component to receive project title related props
@@ -12,26 +26,89 @@ const TitledHOC = function (WrappedComponent) {
             bindAll(this, [
                 'handleUpdateProjectTitle'
             ]);
-            this.state = {
-                projectTitle: null
-            };
+        }
+        componentDidMount () {
+            this.setReduxTitle(this.props.projectTitle);
+        }
+        componentDidUpdate (prevProps) {
+            if (this.props.projectTitle !== prevProps.projectTitle) {
+                this.setReduxTitle(this.props.projectTitle);
+            }
+            if (this.props.isShowingWithoutId && !prevProps.isShowingWithoutId) {
+                const defaultProjectTitle = this.titleWithDefault();
+                this.setReduxTitle(defaultProjectTitle);
+                this.props.onUpdateProjectTitle(defaultProjectTitle);
+            }
+        }
+        titleWithDefault (title) {
+            if (title === null || typeof title === 'undefined') {
+                return this.props.intl.formatMessage(messages.defaultProjectTitle);
+            }
+            return title;
+        }
+        setReduxTitle (newTitle) {
+            if (newTitle === null || typeof newTitle === 'undefined') {
+                this.props.onUpdateReduxProjectTitle(
+                    this.props.intl.formatMessage(messages.defaultProjectTitle)
+                );
+            } else {
+                this.props.onUpdateReduxProjectTitle(newTitle);
+            }
         }
         handleUpdateProjectTitle (newTitle) {
-            this.setState({projectTitle: newTitle});
+            this.setReduxTitle(newTitle);
+            this.props.onUpdateProjectTitle(newTitle);
         }
         render () {
+            const {
+                /* eslint-disable no-unused-vars */
+                intl,
+                isShowingWithoutId,
+                // for children, we replace onUpdateProjectTitle with our own
+                onUpdateProjectTitle,
+                onUpdateReduxProjectTitle,
+                // we don't pass projectTitle prop to children -- they must use
+                // redux value
+                projectTitle,
+                /* eslint-enable no-unused-vars */
+                ...componentProps
+            } = this.props;
             return (
                 <WrappedComponent
-                    canEditTitle
-                    projectTitle={this.state.projectTitle}
                     onUpdateProjectTitle={this.handleUpdateProjectTitle}
-                    {...this.props}
+                    {...componentProps}
                 />
             );
         }
     }
 
-    return TitledComponent;
+    TitledComponent.propTypes = {
+        intl: intlShape,
+        isShowingWithoutId: PropTypes.bool,
+        onUpdateProjectTitle: PropTypes.func,
+        onUpdateReduxProjectTitle: PropTypes.func,
+        projectTitle: PropTypes.string
+    };
+
+    TitledComponent.defaultProps = {
+        onUpdateProjectTitle: () => {}
+    };
+
+    const mapStateToProps = state => {
+        const loadingState = state.scratchGui.projectState.loadingState;
+        return {
+            isShowingWithoutId: getIsShowingWithoutId(loadingState)
+        };
+    };
+
+    const mapDispatchToProps = dispatch => ({
+        onUpdateReduxProjectTitle: title => dispatch(setProjectTitle(title))
+    });
+
+    return injectIntl(connect(
+        mapStateToProps,
+        mapDispatchToProps,
+    )(TitledComponent));
 };
 
 export {
-- 
GitLab