Skip to content

Commit 5b3e7c6

Browse files
authored
Merge pull request #3391 from benjiwheeler/create-new-project
handle many modes of new project creation
2 parents e810c5e + f9780fb commit 5b3e7c6

File tree

10 files changed

+421
-120
lines changed

10 files changed

+421
-120
lines changed

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,6 @@ MenuBar.propTypes = {
656656
onUpdateProjectTitle: PropTypes.func,
657657
renderLogin: PropTypes.func,
658658
sessionExists: PropTypes.bool,
659-
startSaving: PropTypes.func,
660659
username: PropTypes.string
661660
};
662661

src/containers/gui.jsx

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +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 {injectIntl, intlShape} from 'react-intl';
78

89
import ErrorBoundaryHOC from '../lib/error-boundary-hoc.jsx';
910
import {openExtensionLibrary} from '../reducers/modals';
11+
import {
12+
getIsShowingProject
13+
} from '../reducers/project-state';
1014
import {setProjectTitle} from '../reducers/project-title';
1115
import {
1216
activateTab,
@@ -25,18 +29,29 @@ import ProjectFetcherHOC from '../lib/project-fetcher-hoc.jsx';
2529
import ProjectSaverHOC from '../lib/project-saver-hoc.jsx';
2630
import vmListenerHOC from '../lib/vm-listener-hoc.jsx';
2731
import vmManagerHOC from '../lib/vm-manager-hoc.jsx';
32+
import {defaultProjectTitleMessages} from '../reducers/project-title';
2833

2934
import GUIComponent from '../components/gui/gui.jsx';
3035

3136
class GUI extends React.Component {
3237
componentDidMount () {
33-
if (this.props.projectTitle) {
34-
this.props.onUpdateReduxProjectTitle(this.props.projectTitle);
38+
this.setReduxTitle(this.props.projectTitle);
39+
}
40+
componentDidUpdate (prevProps) {
41+
if (this.props.projectId !== prevProps.projectId && this.props.projectId !== null) {
42+
this.props.onUpdateProjectId(this.props.projectId);
43+
}
44+
if (this.props.projectTitle !== prevProps.projectTitle) {
45+
this.setReduxTitle(this.props.projectTitle);
3546
}
3647
}
37-
componentWillReceiveProps (nextProps) {
38-
if (this.props.projectTitle !== nextProps.projectTitle) {
39-
this.props.onUpdateReduxProjectTitle(nextProps.projectTitle);
48+
setReduxTitle (newTitle) {
49+
if (newTitle === null || typeof newTitle === 'undefined') {
50+
this.props.onUpdateReduxProjectTitle(
51+
this.props.intl.formatMessage(defaultProjectTitleMessages.defaultProjectTitle)
52+
);
53+
} else {
54+
this.props.onUpdateReduxProjectTitle(newTitle);
4055
}
4156
}
4257
render () {
@@ -50,8 +65,11 @@ class GUI extends React.Component {
5065
errorMessage,
5166
hideIntro,
5267
loadingError,
68+
isShowingProject,
69+
onUpdateProjectId,
5370
onUpdateReduxProjectTitle,
5471
projectHost,
72+
projectId,
5573
projectTitle,
5674
/* eslint-enable no-unused-vars */
5775
children,
@@ -78,40 +96,53 @@ GUI.propTypes = {
7896
fetchingProject: PropTypes.bool,
7997
hideIntro: PropTypes.bool,
8098
importInfoVisible: PropTypes.bool,
99+
intl: intlShape,
81100
isLoading: PropTypes.bool,
101+
isShowingProject: PropTypes.bool,
82102
loadingError: PropTypes.bool,
83103
loadingStateVisible: PropTypes.bool,
84104
onChangeProjectInfo: PropTypes.func,
85105
onSeeCommunity: PropTypes.func,
106+
onUpdateProjectId: PropTypes.func,
86107
onUpdateProjectTitle: PropTypes.func,
87108
onUpdateReduxProjectTitle: PropTypes.func,
88109
previewInfoVisible: PropTypes.bool,
89110
projectHost: PropTypes.string,
111+
projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
90112
projectTitle: PropTypes.string,
91113
vm: PropTypes.instanceOf(VM).isRequired
92114
};
93115

94-
const mapStateToProps = (state, ownProps) => ({
95-
activeTabIndex: state.scratchGui.editorTab.activeTabIndex,
96-
alertsVisible: state.scratchGui.alerts.visible,
97-
backdropLibraryVisible: state.scratchGui.modals.backdropLibrary,
98-
blocksTabVisible: state.scratchGui.editorTab.activeTabIndex === BLOCKS_TAB_INDEX,
99-
cardsVisible: state.scratchGui.cards.visible,
100-
costumeLibraryVisible: state.scratchGui.modals.costumeLibrary,
101-
costumesTabVisible: state.scratchGui.editorTab.activeTabIndex === COSTUMES_TAB_INDEX,
102-
importInfoVisible: state.scratchGui.modals.importInfo,
103-
isPlayerOnly: state.scratchGui.mode.isPlayerOnly,
104-
isRtl: state.locales.isRtl,
105-
loadingStateVisible: state.scratchGui.modals.loadingProject,
106-
previewInfoVisible: state.scratchGui.modals.previewInfo && !ownProps.hideIntro,
107-
targetIsStage: (
108-
state.scratchGui.targets.stage &&
109-
state.scratchGui.targets.stage.id === state.scratchGui.targets.editingTarget
110-
),
111-
soundsTabVisible: state.scratchGui.editorTab.activeTabIndex === SOUNDS_TAB_INDEX,
112-
tipsLibraryVisible: state.scratchGui.modals.tipsLibrary,
113-
vm: state.scratchGui.vm
114-
});
116+
GUI.defaultProps = {
117+
onUpdateProjectId: () => {}
118+
};
119+
120+
const mapStateToProps = (state, ownProps) => {
121+
const loadingState = state.scratchGui.projectState.loadingState;
122+
return {
123+
activeTabIndex: state.scratchGui.editorTab.activeTabIndex,
124+
alertsVisible: state.scratchGui.alerts.visible,
125+
backdropLibraryVisible: state.scratchGui.modals.backdropLibrary,
126+
blocksTabVisible: state.scratchGui.editorTab.activeTabIndex === BLOCKS_TAB_INDEX,
127+
cardsVisible: state.scratchGui.cards.visible,
128+
costumeLibraryVisible: state.scratchGui.modals.costumeLibrary,
129+
costumesTabVisible: state.scratchGui.editorTab.activeTabIndex === COSTUMES_TAB_INDEX,
130+
importInfoVisible: state.scratchGui.modals.importInfo,
131+
isPlayerOnly: state.scratchGui.mode.isPlayerOnly,
132+
isRtl: state.locales.isRtl,
133+
isShowingProject: getIsShowingProject(loadingState),
134+
loadingStateVisible: state.scratchGui.modals.loadingProject,
135+
previewInfoVisible: state.scratchGui.modals.previewInfo && !ownProps.hideIntro,
136+
projectId: state.scratchGui.projectState.projectId,
137+
targetIsStage: (
138+
state.scratchGui.targets.stage &&
139+
state.scratchGui.targets.stage.id === state.scratchGui.targets.editingTarget
140+
),
141+
soundsTabVisible: state.scratchGui.editorTab.activeTabIndex === SOUNDS_TAB_INDEX,
142+
tipsLibraryVisible: state.scratchGui.modals.tipsLibrary,
143+
vm: state.scratchGui.vm
144+
};
145+
};
115146

116147
const mapDispatchToProps = dispatch => ({
117148
onExtensionButtonClick: () => dispatch(openExtensionLibrary()),
@@ -123,10 +154,10 @@ const mapDispatchToProps = dispatch => ({
123154
onUpdateReduxProjectTitle: title => dispatch(setProjectTitle(title))
124155
});
125156

126-
const ConnectedGUI = connect(
157+
const ConnectedGUI = injectIntl(connect(
127158
mapStateToProps,
128159
mapDispatchToProps,
129-
)(GUI);
160+
)(GUI));
130161

131162
// note that redux's 'compose' function is just being used as a general utility to make
132163
// the hierarchy of HOC constructor calls clearer here; it has nothing to do with redux's

src/containers/sb-file-uploader.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ class SBFileUploader extends React.Component {
112112
}
113113

114114
SBFileUploader.propTypes = {
115+
canSave: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
115116
children: PropTypes.func,
116117
intl: intlShape.isRequired,
117118
loadingState: PropTypes.oneOf(LoadingStates),
@@ -127,9 +128,9 @@ const mapStateToProps = state => ({
127128
vm: state.scratchGui.vm
128129
});
129130

130-
const mapDispatchToProps = dispatch => ({
131+
const mapDispatchToProps = (dispatch, ownProps) => ({
131132
onLoadingFinished: loadingState => {
132-
dispatch(onLoadedProject(loadingState));
133+
dispatch(onLoadedProject(loadingState, ownProps.canSave));
133134
dispatch(closeLoadingProject());
134135
},
135136
onLoadingStarted: () => {

src/lib/project-fetcher-hoc.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import {connect} from 'react-redux';
77
import {
88
LoadingStates,
99
defaultProjectId,
10-
onFetchedProjectData,
1110
getIsFetchingWithId,
11+
onFetchedProjectData,
1212
setProjectId
1313
} from '../reducers/project-state';
1414

@@ -102,6 +102,7 @@ const ProjectFetcherHOC = function (WrappedComponent) {
102102
}
103103
ProjectFetcherComponent.propTypes = {
104104
assetHost: PropTypes.string,
105+
canSave: PropTypes.bool,
105106
intl: intlShape.isRequired,
106107
isFetchingWithId: PropTypes.bool,
107108
loadingState: PropTypes.oneOf(LoadingStates),

src/lib/project-saver-hoc.jsx

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ import VM from 'scratch-vm';
66
import storage from '../lib/storage';
77
import {
88
LoadingStates,
9+
createProject,
910
getIsCreating,
11+
getIsShowingProject,
12+
getIsShowingWithoutId,
1013
getIsUpdating,
1114
onCreated,
1215
onUpdated,
13-
onError
16+
onError,
17+
saveProject
1418
} from '../reducers/project-state';
1519

1620
/**
@@ -27,7 +31,7 @@ const ProjectSaverHOC = function (WrappedComponent) {
2731
componentDidUpdate (prevProps) {
2832
if (this.props.isUpdating && !prevProps.isUpdating) {
2933
this.storeProject(this.props.reduxProjectId)
30-
.then(() => { // eslint-disable-line no-unused-vars
34+
.then(() => {
3135
// there is nothing we expect to find in response that we need to check here
3236
this.props.onUpdated(this.props.loadingState);
3337
})
@@ -46,6 +50,19 @@ const ProjectSaverHOC = function (WrappedComponent) {
4650
this.props.onError(`Creating a new project failed with error: ${err}`);
4751
});
4852
}
53+
// if this is the first time we're able to create this project on the server, create it!
54+
const showingCreateable = this.props.canSave && this.props.isShowingWithoutId;
55+
const prevShowingCreateable = prevProps.canSave && prevProps.isShowingWithoutId;
56+
if (showingCreateable && !prevShowingCreateable) {
57+
this.props.createProject();
58+
} else {
59+
// if we're newly *able* to save this project, save it!
60+
const showingSaveable = this.props.canSave && this.props.isShowingWithId;
61+
const becameAbleToSave = this.props.canSave && !prevProps.canSave;
62+
if (showingSaveable && becameAbleToSave) {
63+
this.props.saveProject();
64+
}
65+
}
4966
}
5067
/**
5168
* storeProject:
@@ -72,13 +89,17 @@ const ProjectSaverHOC = function (WrappedComponent) {
7289
render () {
7390
const {
7491
/* eslint-disable no-unused-vars */
92+
createProject: createProjectProp,
7593
onCreated: onCreatedProp,
7694
onUpdated: onUpdatedProp,
7795
onError: onErrorProp,
7896
isCreating: isCreatingProp,
97+
isShowingWithId: isShowingWithIdProp,
98+
isShowingWithoutId: isShowingWithoutIdProp,
7999
isUpdating: isUpdatingProp,
80100
loadingState,
81101
reduxProjectId,
102+
saveProject: saveProjectProp,
82103
/* eslint-enable no-unused-vars */
83104
...componentProps
84105
} = this.props;
@@ -90,33 +111,47 @@ const ProjectSaverHOC = function (WrappedComponent) {
90111
}
91112
}
92113
ProjectSaverComponent.propTypes = {
114+
canSave: PropTypes.bool,
115+
createProject: PropTypes.func,
93116
isCreating: PropTypes.bool,
117+
isShowingWithId: PropTypes.bool,
118+
isShowingWithoutId: PropTypes.bool,
94119
isUpdating: PropTypes.bool,
95120
loadingState: PropTypes.oneOf(LoadingStates),
96121
onCreated: PropTypes.func,
97122
onError: PropTypes.func,
98123
onUpdated: PropTypes.func,
99124
reduxProjectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
125+
saveProject: PropTypes.func,
100126
vm: PropTypes.instanceOf(VM).isRequired
101127
};
102128
const mapStateToProps = state => {
103129
const loadingState = state.scratchGui.projectState.loadingState;
104130
return {
105131
isCreating: getIsCreating(loadingState),
132+
isShowingWithId: getIsShowingProject(loadingState),
133+
isShowingWithoutId: getIsShowingWithoutId(loadingState),
106134
isUpdating: getIsUpdating(loadingState),
107135
loadingState: loadingState,
108136
reduxProjectId: state.scratchGui.projectState.projectId,
109137
vm: state.scratchGui.vm
110138
};
111139
};
112140
const mapDispatchToProps = dispatch => ({
141+
createProject: () => dispatch(createProject()),
113142
onCreated: projectId => dispatch(onCreated(projectId)),
114143
onUpdated: (projectId, loadingState) => dispatch(onUpdated(projectId, loadingState)),
115-
onError: errStr => dispatch(onError(errStr))
144+
onError: errStr => dispatch(onError(errStr)),
145+
saveProject: () => dispatch(saveProject())
116146
});
147+
// Allow incoming props to override redux-provided props. Used to mock in tests.
148+
const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign(
149+
{}, stateProps, dispatchProps, ownProps
150+
);
117151
return connect(
118152
mapStateToProps,
119-
mapDispatchToProps
153+
mapDispatchToProps,
154+
mergeProps
120155
)(ProjectSaverComponent);
121156
};
122157

src/lib/vm-manager-hoc.jsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ const vmManagerHOC = function (WrappedComponent) {
4242
// and they weren't both that way until now... load project!
4343
if (this.props.isLoadingWithId && this.props.fontsLoaded &&
4444
(!prevProps.isLoadingWithId || !prevProps.fontsLoaded)) {
45-
this.loadProject(this.props.projectData, this.props.loadingState);
45+
this.loadProject();
4646
}
4747
}
48-
loadProject (projectData, loadingState) {
49-
return this.props.vm.loadProject(projectData)
48+
loadProject () {
49+
return this.props.vm.loadProject(this.props.projectData)
5050
.then(() => {
51-
this.props.onLoadedProject(loadingState);
51+
this.props.onLoadedProject(this.props.loadingState, this.props.canSave);
5252
})
5353
.catch(e => {
5454
// Need to catch this error and update component state so that
@@ -82,6 +82,7 @@ const vmManagerHOC = function (WrappedComponent) {
8282
}
8383

8484
VMManager.propTypes = {
85+
canSave: PropTypes.bool,
8586
fontsLoaded: PropTypes.bool,
8687
isLoadingWithId: PropTypes.bool,
8788
loadingState: PropTypes.oneOf(LoadingStates),
@@ -102,7 +103,8 @@ const vmManagerHOC = function (WrappedComponent) {
102103
};
103104

104105
const mapDispatchToProps = dispatch => ({
105-
onLoadedProject: loadingState => dispatch(onLoadedProject(loadingState))
106+
onLoadedProject: (loadingState, canSave) =>
107+
dispatch(onLoadedProject(loadingState, canSave))
106108
});
107109

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

0 commit comments

Comments
 (0)