diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index ab7ec87b0e68c94d1d2dfd7a7b282f379da41782..9846eed7566356ed1e5395def9b59388bb870958 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -94,7 +94,6 @@ const GUIComponent = props => { onLogOut, onOpenRegistration, onToggleLoginOpen, - onUpdateProjectTitle, onActivateCostumesTab, onActivateSoundsTab, onActivateTab, @@ -228,7 +227,6 @@ const GUIComponent = props => { onSeeCommunity={onSeeCommunity} onShare={onShare} onToggleLoginOpen={onToggleLoginOpen} - onUpdateProjectTitle={onUpdateProjectTitle} /> <Box className={styles.bodyWrapper}> <Box className={styles.flexWrapper}> @@ -406,7 +404,6 @@ GUIComponent.propTypes = { onTelemetryModalOptIn: PropTypes.func, onTelemetryModalOptOut: PropTypes.func, onToggleLoginOpen: PropTypes.func, - onUpdateProjectTitle: PropTypes.func, renderLogin: PropTypes.func, showComingSoon: PropTypes.bool, soundsTabVisible: PropTypes.bool, diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx index 1fa7a01cc1974236104490d11086e64183850073..98bc128a92f11fe272d674513930e53f897d58bb 100644 --- a/src/components/menu-bar/menu-bar.jsx +++ b/src/components/menu-bar/menu-bar.jsx @@ -395,7 +395,6 @@ class MenuBar extends React.Component { <SBFileUploader canSave={this.props.canSave} userOwnsProject={this.props.userOwnsProject} - onUpdateProjectTitle={this.props.onUpdateProjectTitle} > {(className, renderFileInput, handleLoadProject) => ( <MenuItem @@ -495,7 +494,6 @@ class MenuBar extends React.Component { > <ProjectTitleInput className={classNames(styles.titleFieldGrowable)} - onUpdateProjectTitle={this.props.onUpdateProjectTitle} /> </MenuBarItemTooltip> </div> @@ -746,7 +744,6 @@ MenuBar.propTypes = { onSeeCommunity: PropTypes.func, onShare: PropTypes.func, onToggleLoginOpen: PropTypes.func, - onUpdateProjectTitle: PropTypes.func, projectTitle: PropTypes.string, renderLogin: PropTypes.func, sessionExists: PropTypes.bool, diff --git a/src/components/menu-bar/project-title-input.jsx b/src/components/menu-bar/project-title-input.jsx index b9b5aa6d8382e55c8483a4dde3290ae1ed6ad44a..4f90914a3e3a953368bdf35c8058d73180c54a6b 100644 --- a/src/components/menu-bar/project-title-input.jsx +++ b/src/components/menu-bar/project-title-input.jsx @@ -1,9 +1,9 @@ import classNames from 'classnames'; import {connect} from 'react-redux'; import PropTypes from 'prop-types'; -import bindAll from 'lodash.bindall'; import React from 'react'; import {defineMessages, intlShape, injectIntl} from 'react-intl'; +import {setProjectTitle} from '../../reducers/project-title'; import BufferedInputHOC from '../forms/buffered-input-hoc.jsx'; import Input from '../forms/input.jsx'; @@ -19,39 +19,27 @@ const messages = defineMessages({ } }); -class ProjectTitleInput extends React.Component { - constructor (props) { - super(props); - bindAll(this, [ - 'handleUpdateProjectTitle' - ]); - } - // call onUpdateProjectTitle if it is defined (only defined when gui - // is used within scratch-www) - handleUpdateProjectTitle (newTitle) { - if (this.props.onUpdateProjectTitle) { - this.props.onUpdateProjectTitle(newTitle); - } - } - render () { - return ( - <BufferedInput - className={classNames(styles.titleField, this.props.className)} - maxLength="100" - placeholder={this.props.intl.formatMessage(messages.projectTitlePlaceholder)} - tabIndex="0" - type="text" - value={this.props.projectTitle} - onSubmit={this.handleUpdateProjectTitle} - /> - ); - } -} +const ProjectTitleInput = ({ + className, + handleUpdateReduxProjectTitle, + intl, + projectTitle +}) => ( + <BufferedInput + className={classNames(styles.titleField, className)} + maxLength="100" + placeholder={intl.formatMessage(messages.projectTitlePlaceholder)} + tabIndex="0" + type="text" + value={projectTitle} + onSubmit={handleUpdateReduxProjectTitle} + /> +); ProjectTitleInput.propTypes = { className: PropTypes.string, + handleUpdateReduxProjectTitle: PropTypes.func, intl: intlShape.isRequired, - onUpdateProjectTitle: PropTypes.func, projectTitle: PropTypes.string }; @@ -59,7 +47,9 @@ const mapStateToProps = state => ({ projectTitle: state.scratchGui.projectTitle }); -const mapDispatchToProps = () => ({}); +const mapDispatchToProps = dispatch => ({ + handleUpdateReduxProjectTitle: title => dispatch(setProjectTitle(title)) +}); export default injectIntl(connect( mapStateToProps, diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index d7b8f78c20fd9737bcdd4515042921c43a72c040..314c38893518b0a8e5a8bce6a847dc008a6ac5b6 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -103,13 +103,11 @@ GUI.propTypes = { isLoading: PropTypes.bool, isScratchDesktop: PropTypes.bool, isShowingProject: PropTypes.bool, - isShowingWithoutId: PropTypes.bool, loadingStateVisible: PropTypes.bool, onProjectLoaded: PropTypes.func, onSeeCommunity: PropTypes.func, onStorageInit: PropTypes.func, onUpdateProjectId: PropTypes.func, - onUpdateProjectTitle: PropTypes.func, onVmInit: PropTypes.func, projectHost: PropTypes.string, projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), diff --git a/src/containers/sb-file-uploader.jsx b/src/containers/sb-file-uploader.jsx index ae5f4291536338a5b39f1a7be38a071bc95a2b9e..c4f484c2221ea9c523a588baa76ddabf8520d036 100644 --- a/src/containers/sb-file-uploader.jsx +++ b/src/containers/sb-file-uploader.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import {connect} from 'react-redux'; import {defineMessages, injectIntl, intlShape} from 'react-intl'; +import {setProjectTitle} from '../reducers/project-title'; import log from '../lib/log'; import sharedMessages from '../lib/shared-messages'; @@ -131,7 +132,7 @@ class SBFileUploader extends React.Component { // This is necessary in case the user wants to reload a project if (filename) { const uploadedProjectTitle = this.getProjectTitleFromFilename(filename); - this.props.onUpdateProjectTitle(uploadedProjectTitle); + this.props.updateReduxProjectTitle(uploadedProjectTitle); } this.resetFileInput(); }) @@ -179,9 +180,9 @@ SBFileUploader.propTypes = { loadingState: PropTypes.oneOf(LoadingStates), onLoadingFinished: PropTypes.func, onLoadingStarted: PropTypes.func, - onUpdateProjectTitle: PropTypes.func, projectChanged: PropTypes.bool, requestProjectUpload: PropTypes.func, + updateReduxProjectTitle: PropTypes.func, userOwnsProject: PropTypes.bool, vm: PropTypes.shape({ loadProject: PropTypes.func @@ -209,7 +210,8 @@ const mapDispatchToProps = (dispatch, ownProps) => ({ dispatch(closeFileMenu()); }, requestProjectUpload: loadingState => dispatch(requestProjectUpload(loadingState)), - onLoadingStarted: () => dispatch(openLoadingProject()) + onLoadingStarted: () => dispatch(openLoadingProject()), + updateReduxProjectTitle: title => dispatch(setProjectTitle(title)) }); // Allow incoming props to override redux-provided props. Used to mock in tests. diff --git a/src/lib/titled-hoc.jsx b/src/lib/titled-hoc.jsx index fc8c33b8e961f51a2e4873e2b5aa6653e89616d3..30ed662178554342cf63338a44aaa32c803b9618 100644 --- a/src/lib/titled-hoc.jsx +++ b/src/lib/titled-hoc.jsx @@ -1,6 +1,5 @@ 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'; @@ -21,23 +20,18 @@ const messages = defineMessages({ */ const TitledHOC = function (WrappedComponent) { class TitledComponent extends React.Component { - constructor (props) { - super(props); - bindAll(this, [ - 'handleUpdateProjectTitle' - ]); - } componentDidMount () { - this.setReduxTitle(this.props.projectTitle); + this.props.updateReduxProjectTitle(this.titleWithDefault(this.props.projectTitle)); } componentDidUpdate (prevProps) { if (this.props.projectTitle !== prevProps.projectTitle) { - this.setReduxTitle(this.props.projectTitle); + this.props.updateReduxProjectTitle(this.titleWithDefault(this.props.projectTitle)); } - if (this.props.isShowingWithoutId && !prevProps.isShowingWithoutId) { - const defaultProjectTitle = this.titleWithDefault(); - this.setReduxTitle(defaultProjectTitle); - this.props.onUpdateProjectTitle(defaultProjectTitle); + // if the projectTitle hasn't changed, but the reduxProjectTitle + // HAS changed, we need to report that change to the projectTitle's owner + if (this.props.reduxProjectTitle !== prevProps.reduxProjectTitle && + this.props.reduxProjectTitle !== this.props.projectTitle) { + this.props.onUpdateProjectTitle(this.props.reduxProjectTitle); } } titleWithDefault (title) { @@ -46,19 +40,6 @@ const TitledHOC = function (WrappedComponent) { } 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.setReduxTitle(newTitle); - this.props.onUpdateProjectTitle(newTitle); - } render () { const { /* eslint-disable no-unused-vars */ @@ -66,16 +47,16 @@ const TitledHOC = function (WrappedComponent) { 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, + reduxProjectTitle, + updateReduxProjectTitle, /* eslint-enable no-unused-vars */ ...componentProps } = this.props; return ( <WrappedComponent - onUpdateProjectTitle={this.handleUpdateProjectTitle} {...componentProps} /> ); @@ -86,8 +67,9 @@ const TitledHOC = function (WrappedComponent) { intl: intlShape, isShowingWithoutId: PropTypes.bool, onUpdateProjectTitle: PropTypes.func, - onUpdateReduxProjectTitle: PropTypes.func, - projectTitle: PropTypes.string + projectTitle: PropTypes.string, + reduxProjectTitle: PropTypes.string, + updateReduxProjectTitle: PropTypes.func }; TitledComponent.defaultProps = { @@ -97,12 +79,13 @@ const TitledHOC = function (WrappedComponent) { const mapStateToProps = state => { const loadingState = state.scratchGui.projectState.loadingState; return { - isShowingWithoutId: getIsShowingWithoutId(loadingState) + isShowingWithoutId: getIsShowingWithoutId(loadingState), + reduxProjectTitle: state.scratchGui.projectTitle }; }; const mapDispatchToProps = dispatch => ({ - onUpdateReduxProjectTitle: title => dispatch(setProjectTitle(title)) + updateReduxProjectTitle: title => dispatch(setProjectTitle(title)) }); return injectIntl(connect( diff --git a/test/unit/containers/sb-file-uploader.test.jsx b/test/unit/containers/sb-file-uploader.test.jsx index 083dbfa2da12f4d3a7c946ea54e0e26b00518553..c757114b4ca7e387c78e7477931cf31a04c91a9a 100644 --- a/test/unit/containers/sb-file-uploader.test.jsx +++ b/test/unit/containers/sb-file-uploader.test.jsx @@ -11,7 +11,6 @@ describe('SBFileUploader Container', () => { const mockStore = configureStore(); let onLoadingFinished; let onLoadingStarted; - let onUpdateProjectTitle; let store; // Wrap this in a function so it gets test specific states and can be reused. @@ -20,7 +19,6 @@ describe('SBFileUploader Container', () => { <SBFileUploader onLoadingFinished={onLoadingFinished} onLoadingStarted={onLoadingStarted} - onUpdateProjectTitle={onUpdateProjectTitle} > {(renderFileInput, loadProject) => ( <div @@ -40,7 +38,6 @@ describe('SBFileUploader Container', () => { vm: {} } }); - onUpdateProjectTitle = jest.fn(); onLoadingFinished = jest.fn(); onLoadingStarted = jest.fn(); });