Skip to content

Commit 395b558

Browse files
authored
Merge pull request scratchfoundation#5168 from benjiwheeler/clear-title-on-default-project
Refactor title handling
2 parents 758b0ef + e3fa196 commit 395b558

File tree

9 files changed

+112
-96
lines changed

9 files changed

+112
-96
lines changed

src/components/gui/gui.jsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ const GUIComponent = props => {
9494
onLogOut,
9595
onOpenRegistration,
9696
onToggleLoginOpen,
97-
onUpdateProjectTitle,
9897
onActivateCostumesTab,
9998
onActivateSoundsTab,
10099
onActivateTab,
@@ -228,7 +227,6 @@ const GUIComponent = props => {
228227
onSeeCommunity={onSeeCommunity}
229228
onShare={onShare}
230229
onToggleLoginOpen={onToggleLoginOpen}
231-
onUpdateProjectTitle={onUpdateProjectTitle}
232230
/>
233231
<Box className={styles.bodyWrapper}>
234232
<Box className={styles.flexWrapper}>
@@ -406,7 +404,6 @@ GUIComponent.propTypes = {
406404
onTelemetryModalOptIn: PropTypes.func,
407405
onTelemetryModalOptOut: PropTypes.func,
408406
onToggleLoginOpen: PropTypes.func,
409-
onUpdateProjectTitle: PropTypes.func,
410407
renderLogin: PropTypes.func,
411408
showComingSoon: PropTypes.bool,
412409
soundsTabVisible: PropTypes.bool,
@@ -433,7 +430,6 @@ GUIComponent.defaultProps = {
433430
isCreating: false,
434431
isShared: false,
435432
loading: false,
436-
onUpdateProjectTitle: () => {},
437433
showComingSoon: false,
438434
stageSizeMode: STAGE_SIZE_MODES.large
439435
};

src/components/menu-bar/menu-bar.jsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,6 @@ class MenuBar extends React.Component {
395395
<SBFileUploader
396396
canSave={this.props.canSave}
397397
userOwnsProject={this.props.userOwnsProject}
398-
onUpdateProjectTitle={this.props.onUpdateProjectTitle}
399398
>
400399
{(className, renderFileInput, handleLoadProject) => (
401400
<MenuItem
@@ -495,7 +494,6 @@ class MenuBar extends React.Component {
495494
>
496495
<ProjectTitleInput
497496
className={classNames(styles.titleFieldGrowable)}
498-
onUpdateProjectTitle={this.props.onUpdateProjectTitle}
499497
/>
500498
</MenuBarItemTooltip>
501499
</div>
@@ -746,7 +744,6 @@ MenuBar.propTypes = {
746744
onSeeCommunity: PropTypes.func,
747745
onShare: PropTypes.func,
748746
onToggleLoginOpen: PropTypes.func,
749-
onUpdateProjectTitle: PropTypes.func,
750747
projectTitle: PropTypes.string,
751748
renderLogin: PropTypes.func,
752749
sessionExists: PropTypes.bool,

src/components/menu-bar/project-title-input.jsx

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import classNames from 'classnames';
22
import {connect} from 'react-redux';
33
import PropTypes from 'prop-types';
4-
import bindAll from 'lodash.bindall';
54
import React from 'react';
65
import {defineMessages, intlShape, injectIntl} from 'react-intl';
6+
import {setProjectTitle} from '../../reducers/project-title';
77

88
import BufferedInputHOC from '../forms/buffered-input-hoc.jsx';
99
import Input from '../forms/input.jsx';
@@ -19,47 +19,37 @@ const messages = defineMessages({
1919
}
2020
});
2121

22-
class ProjectTitleInput extends React.Component {
23-
constructor (props) {
24-
super(props);
25-
bindAll(this, [
26-
'handleUpdateProjectTitle'
27-
]);
28-
}
29-
// call onUpdateProjectTitle if it is defined (only defined when gui
30-
// is used within scratch-www)
31-
handleUpdateProjectTitle (newTitle) {
32-
if (this.props.onUpdateProjectTitle) {
33-
this.props.onUpdateProjectTitle(newTitle);
34-
}
35-
}
36-
render () {
37-
return (
38-
<BufferedInput
39-
className={classNames(styles.titleField, this.props.className)}
40-
maxLength="100"
41-
placeholder={this.props.intl.formatMessage(messages.projectTitlePlaceholder)}
42-
tabIndex="0"
43-
type="text"
44-
value={this.props.projectTitle}
45-
onSubmit={this.handleUpdateProjectTitle}
46-
/>
47-
);
48-
}
49-
}
22+
const ProjectTitleInput = ({
23+
className,
24+
intl,
25+
onSubmit,
26+
projectTitle
27+
}) => (
28+
<BufferedInput
29+
className={classNames(styles.titleField, className)}
30+
maxLength="100"
31+
placeholder={intl.formatMessage(messages.projectTitlePlaceholder)}
32+
tabIndex="0"
33+
type="text"
34+
value={projectTitle}
35+
onSubmit={onSubmit}
36+
/>
37+
);
5038

5139
ProjectTitleInput.propTypes = {
5240
className: PropTypes.string,
5341
intl: intlShape.isRequired,
54-
onUpdateProjectTitle: PropTypes.func,
42+
onSubmit: PropTypes.func,
5543
projectTitle: PropTypes.string
5644
};
5745

5846
const mapStateToProps = state => ({
5947
projectTitle: state.scratchGui.projectTitle
6048
});
6149

62-
const mapDispatchToProps = () => ({});
50+
const mapDispatchToProps = dispatch => ({
51+
onSubmit: title => dispatch(setProjectTitle(title))
52+
});
6353

6454
export default injectIntl(connect(
6555
mapStateToProps,

src/containers/gui.jsx

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@ import {compose} from 'redux';
44
import {connect} from 'react-redux';
55
import ReactModal from 'react-modal';
66
import VM from 'scratch-vm';
7-
import {defineMessages, injectIntl, intlShape} from 'react-intl';
7+
import {injectIntl, intlShape} from 'react-intl';
88

99
import ErrorBoundaryHOC from '../lib/error-boundary-hoc.jsx';
1010
import {
1111
getIsError,
1212
getIsShowingProject
1313
} from '../reducers/project-state';
14-
import {setProjectTitle} from '../reducers/project-title';
1514
import {
1615
activateTab,
1716
BLOCKS_TAB_INDEX,
@@ -29,6 +28,7 @@ import {
2928
import FontLoaderHOC from '../lib/font-loader-hoc.jsx';
3029
import LocalizationHOC from '../lib/localization-hoc.jsx';
3130
import ProjectFetcherHOC from '../lib/project-fetcher-hoc.jsx';
31+
import TitledHOC from '../lib/titled-hoc.jsx';
3232
import ProjectSaverHOC from '../lib/project-saver-hoc.jsx';
3333
import QueryParserHOC from '../lib/query-parser-hoc.jsx';
3434
import storage from '../lib/storage';
@@ -39,43 +39,22 @@ import cloudManagerHOC from '../lib/cloud-manager-hoc.jsx';
3939
import GUIComponent from '../components/gui/gui.jsx';
4040
import {setIsScratchDesktop} from '../lib/isScratchDesktop.js';
4141

42-
const messages = defineMessages({
43-
defaultProjectTitle: {
44-
id: 'gui.gui.defaultProjectTitle',
45-
description: 'Default title for project',
46-
defaultMessage: 'Scratch Project'
47-
}
48-
});
49-
5042
class GUI extends React.Component {
5143
componentDidMount () {
5244
setIsScratchDesktop(this.props.isScratchDesktop);
53-
this.setReduxTitle(this.props.projectTitle);
5445
this.props.onStorageInit(storage);
5546
this.props.onVmInit(this.props.vm);
5647
}
5748
componentDidUpdate (prevProps) {
5849
if (this.props.projectId !== prevProps.projectId && this.props.projectId !== null) {
5950
this.props.onUpdateProjectId(this.props.projectId);
6051
}
61-
if (this.props.projectTitle !== prevProps.projectTitle) {
62-
this.setReduxTitle(this.props.projectTitle);
63-
}
6452
if (this.props.isShowingProject && !prevProps.isShowingProject) {
6553
// this only notifies container when a project changes from not yet loaded to loaded
6654
// At this time the project view in www doesn't need to know when a project is unloaded
6755
this.props.onProjectLoaded();
6856
}
6957
}
70-
setReduxTitle (newTitle) {
71-
if (newTitle === null || typeof newTitle === 'undefined') {
72-
this.props.onUpdateReduxProjectTitle(
73-
this.props.intl.formatMessage(messages.defaultProjectTitle)
74-
);
75-
} else {
76-
this.props.onUpdateReduxProjectTitle(newTitle);
77-
}
78-
}
7958
render () {
8059
if (this.props.isError) {
8160
throw new Error(
@@ -92,11 +71,9 @@ class GUI extends React.Component {
9271
onProjectLoaded,
9372
onStorageInit,
9473
onUpdateProjectId,
95-
onUpdateReduxProjectTitle,
9674
onVmInit,
9775
projectHost,
9876
projectId,
99-
projectTitle,
10077
/* eslint-enable no-unused-vars */
10178
children,
10279
fetchingProject,
@@ -131,12 +108,9 @@ GUI.propTypes = {
131108
onSeeCommunity: PropTypes.func,
132109
onStorageInit: PropTypes.func,
133110
onUpdateProjectId: PropTypes.func,
134-
onUpdateProjectTitle: PropTypes.func,
135-
onUpdateReduxProjectTitle: PropTypes.func,
136111
onVmInit: PropTypes.func,
137112
projectHost: PropTypes.string,
138113
projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
139-
projectTitle: PropTypes.string,
140114
telemetryModalVisible: PropTypes.bool,
141115
vm: PropTypes.instanceOf(VM).isRequired
142116
};
@@ -186,8 +160,7 @@ const mapDispatchToProps = dispatch => ({
186160
onActivateSoundsTab: () => dispatch(activateTab(SOUNDS_TAB_INDEX)),
187161
onRequestCloseBackdropLibrary: () => dispatch(closeBackdropLibrary()),
188162
onRequestCloseCostumeLibrary: () => dispatch(closeCostumeLibrary()),
189-
onRequestCloseTelemetryModal: () => dispatch(closeTelemetryModal()),
190-
onUpdateReduxProjectTitle: title => dispatch(setProjectTitle(title))
163+
onRequestCloseTelemetryModal: () => dispatch(closeTelemetryModal())
191164
});
192165

193166
const ConnectedGUI = injectIntl(connect(
@@ -204,6 +177,7 @@ const WrappedGui = compose(
204177
FontLoaderHOC,
205178
QueryParserHOC,
206179
ProjectFetcherHOC,
180+
TitledHOC,
207181
ProjectSaverHOC,
208182
vmListenerHOC,
209183
vmManagerHOC,

src/containers/sb-file-uploader.jsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
33
import React from 'react';
44
import {connect} from 'react-redux';
55
import {defineMessages, injectIntl, intlShape} from 'react-intl';
6+
import {setProjectTitle} from '../reducers/project-title';
67

78
import log from '../lib/log';
89
import sharedMessages from '../lib/shared-messages';
@@ -131,7 +132,7 @@ class SBFileUploader extends React.Component {
131132
// This is necessary in case the user wants to reload a project
132133
if (filename) {
133134
const uploadedProjectTitle = this.getProjectTitleFromFilename(filename);
134-
this.props.onUpdateProjectTitle(uploadedProjectTitle);
135+
this.props.onReceivedProjectTitle(uploadedProjectTitle);
135136
}
136137
this.resetFileInput();
137138
})
@@ -179,9 +180,9 @@ SBFileUploader.propTypes = {
179180
loadingState: PropTypes.oneOf(LoadingStates),
180181
onLoadingFinished: PropTypes.func,
181182
onLoadingStarted: PropTypes.func,
182-
onUpdateProjectTitle: PropTypes.func,
183183
projectChanged: PropTypes.bool,
184184
requestProjectUpload: PropTypes.func,
185+
onReceivedProjectTitle: PropTypes.func,
185186
userOwnsProject: PropTypes.bool,
186187
vm: PropTypes.shape({
187188
loadProject: PropTypes.func
@@ -209,7 +210,8 @@ const mapDispatchToProps = (dispatch, ownProps) => ({
209210
dispatch(closeFileMenu());
210211
},
211212
requestProjectUpload: loadingState => dispatch(requestProjectUpload(loadingState)),
212-
onLoadingStarted: () => dispatch(openLoadingProject())
213+
onLoadingStarted: () => dispatch(openLoadingProject()),
214+
onReceivedProjectTitle: title => dispatch(setProjectTitle(title))
213215
});
214216

215217
// Allow incoming props to override redux-provided props. Used to mock in tests.

src/lib/titled-hoc.jsx

Lines changed: 77 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,98 @@
1+
import PropTypes from 'prop-types';
12
import React from 'react';
2-
import bindAll from 'lodash.bindall';
3+
import {connect} from 'react-redux';
4+
import {defineMessages, injectIntl, intlShape} from 'react-intl';
5+
6+
import {getIsShowingWithoutId} from '../reducers/project-state';
7+
import {setProjectTitle} from '../reducers/project-title';
8+
9+
const messages = defineMessages({
10+
defaultProjectTitle: {
11+
id: 'gui.gui.defaultProjectTitle',
12+
description: 'Default title for project',
13+
defaultMessage: 'Scratch Project'
14+
}
15+
});
316

417
/* Higher Order Component to get and set the project title
518
* @param {React.Component} WrappedComponent component to receive project title related props
619
* @returns {React.Component} component with project loading behavior
720
*/
821
const TitledHOC = function (WrappedComponent) {
922
class TitledComponent extends React.Component {
10-
constructor (props) {
11-
super(props);
12-
bindAll(this, [
13-
'handleUpdateProjectTitle'
14-
]);
15-
this.state = {
16-
projectTitle: null
17-
};
23+
componentDidMount () {
24+
this.handleReceivedProjectTitle(this.props.projectTitle);
25+
}
26+
componentDidUpdate (prevProps) {
27+
if (this.props.projectTitle !== prevProps.projectTitle) {
28+
this.handleReceivedProjectTitle(this.props.projectTitle);
29+
}
30+
// if the projectTitle hasn't changed, but the reduxProjectTitle
31+
// HAS changed, we need to report that change to the projectTitle's owner
32+
if (this.props.reduxProjectTitle !== prevProps.reduxProjectTitle &&
33+
this.props.reduxProjectTitle !== this.props.projectTitle) {
34+
this.props.onUpdateProjectTitle(this.props.reduxProjectTitle);
35+
}
1836
}
19-
handleUpdateProjectTitle (newTitle) {
20-
this.setState({projectTitle: newTitle});
37+
handleReceivedProjectTitle (requestedTitle) {
38+
let newTitle = requestedTitle;
39+
if (newTitle === null || typeof newTitle === 'undefined') {
40+
newTitle = this.props.intl.formatMessage(messages.defaultProjectTitle);
41+
}
42+
this.props.onChangedProjectTitle(newTitle);
2143
}
2244
render () {
45+
const {
46+
/* eslint-disable no-unused-vars */
47+
intl,
48+
isShowingWithoutId,
49+
onChangedProjectTitle,
50+
// for children, we replace onUpdateProjectTitle with our own
51+
onUpdateProjectTitle,
52+
// we don't pass projectTitle prop to children -- they must use
53+
// redux value
54+
projectTitle,
55+
reduxProjectTitle,
56+
/* eslint-enable no-unused-vars */
57+
...componentProps
58+
} = this.props;
2359
return (
2460
<WrappedComponent
25-
canEditTitle
26-
projectTitle={this.state.projectTitle}
27-
onUpdateProjectTitle={this.handleUpdateProjectTitle}
28-
{...this.props}
61+
{...componentProps}
2962
/>
3063
);
3164
}
3265
}
3366

34-
return TitledComponent;
67+
TitledComponent.propTypes = {
68+
intl: intlShape,
69+
isShowingWithoutId: PropTypes.bool,
70+
onChangedProjectTitle: PropTypes.func,
71+
onUpdateProjectTitle: PropTypes.func,
72+
projectTitle: PropTypes.string,
73+
reduxProjectTitle: PropTypes.string
74+
};
75+
76+
TitledComponent.defaultProps = {
77+
onUpdateProjectTitle: () => {}
78+
};
79+
80+
const mapStateToProps = state => {
81+
const loadingState = state.scratchGui.projectState.loadingState;
82+
return {
83+
isShowingWithoutId: getIsShowingWithoutId(loadingState),
84+
reduxProjectTitle: state.scratchGui.projectTitle
85+
};
86+
};
87+
88+
const mapDispatchToProps = dispatch => ({
89+
onChangedProjectTitle: title => dispatch(setProjectTitle(title))
90+
});
91+
92+
return injectIntl(connect(
93+
mapStateToProps,
94+
mapDispatchToProps,
95+
)(TitledComponent));
3596
};
3697

3798
export {

0 commit comments

Comments
 (0)