remix and save as a copy functionality
made remixing, saving as copy states; made project state naming more consistent changed meaning of canCreateNew to true creation fixed project-saver-hoc test removed Remix option from standalone player menu; renamed canSaveAsCopy to canCreateCopy added parenthesis missing from rebase fixed create new test
This commit is contained in:
1 parent
4fd0019d5d
commit
80f4e01d47
6 files changed
+298
-157
No files matched your search
@@ -66,7 +66,7 @@ const GUIComponent = props => {
|
||||
canCreateNew,
|
||||
canRemix,
|
||||
canSave,
|
||||
canSaveAsCopy,
|
||||
canCreateCopy,
|
||||
canShare,
|
||||
children,
|
||||
connectionModalVisible,
|
||||
@@ -178,10 +178,10 @@ const GUIComponent = props => {
|
||||
) : null}
|
||||
<MenuBar
|
||||
accountNavOpen={accountNavOpen}
|
||||
canCreateCopy={canCreateCopy}
|
||||
canCreateNew={canCreateNew}
|
||||
canRemix={canRemix}
|
||||
canSave={canSave}
|
||||
canSaveAsCopy={canSaveAsCopy}
|
||||
canShare={canShare}
|
||||
className={styles.menuBarPosition}
|
||||
enableCommunity={enableCommunity}
|
||||
@@ -328,10 +328,10 @@ GUIComponent.propTypes = {
|
||||
}),
|
||||
basePath: PropTypes.string,
|
||||
blocksTabVisible: PropTypes.bool,
|
||||
canCreateCopy: PropTypes.bool,
|
||||
canCreateNew: PropTypes.bool,
|
||||
canRemix: PropTypes.bool,
|
||||
canSave: PropTypes.bool,
|
||||
canSaveAsCopy: PropTypes.bool,
|
||||
canShare: PropTypes.bool,
|
||||
cardsVisible: PropTypes.bool,
|
||||
children: PropTypes.node,
|
||||
@@ -373,10 +373,10 @@ GUIComponent.defaultProps = {
|
||||
visible: false
|
||||
},
|
||||
basePath: './',
|
||||
canCreateNew: true,
|
||||
canCreateNew: false,
|
||||
canRemix: false,
|
||||
canSave: false,
|
||||
canSaveAsCopy: false,
|
||||
canCreateCopy: false,
|
||||
canShare: false,
|
||||
onUpdateProjectTitle: () => {},
|
||||
showComingSoon: false,
|
||||
|
||||
@@ -26,7 +26,9 @@ import {
|
||||
getIsUpdating,
|
||||
getIsShowingProject,
|
||||
requestNewProject,
|
||||
saveProject
|
||||
remixProject,
|
||||
updateProject,
|
||||
saveProjectAsCopy
|
||||
} from '../../reducers/project-state';
|
||||
import {
|
||||
openAccountMenu,
|
||||
@@ -130,7 +132,9 @@ class MenuBar extends React.Component {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleClickNew',
|
||||
'handleClickRemix',
|
||||
'handleClickSave',
|
||||
'handleClickSaveAsCopy',
|
||||
'handleCloseFileMenuAndThen',
|
||||
'handleLanguageMouseUp',
|
||||
'handleRestoreOption',
|
||||
@@ -145,16 +149,23 @@ class MenuBar extends React.Component {
|
||||
}
|
||||
}
|
||||
handleClickNew () {
|
||||
// if canSave===true, it's safe to replace current project, since we will auto-save first
|
||||
const readyToReplaceProject =
|
||||
this.props.canSave || confirm('Replace contents of the current project?'); // eslint-disable-line no-alert
|
||||
// if canCreateNew===true, it's safe to replace current project, since we will auto-save first.
|
||||
// else confirm first.
|
||||
const readyToReplaceProject = this.props.canCreateNew ||
|
||||
confirm('Replace contents of the current project?'); // eslint-disable-line no-alert
|
||||
if (readyToReplaceProject) {
|
||||
this.props.onClickNew(this.props.canSave);
|
||||
this.props.onClickNew(this.props.canCreateNew);
|
||||
}
|
||||
}
|
||||
handleClickRemix () {
|
||||
this.props.onClickRemix();
|
||||
}
|
||||
handleClickSave () {
|
||||
this.props.onClickSave();
|
||||
}
|
||||
handleClickSaveAsCopy () {
|
||||
this.props.onClickSaveAsCopy();
|
||||
}
|
||||
handleRestoreOption (restoreFun) {
|
||||
return () => {
|
||||
restoreFun();
|
||||
@@ -209,13 +220,20 @@ class MenuBar extends React.Component {
|
||||
id="gui.menuBar.saveNow"
|
||||
/>
|
||||
);
|
||||
const saveAsCopyMessage = (
|
||||
const createCopyMessage = (
|
||||
<FormattedMessage
|
||||
defaultMessage="Save as a copy"
|
||||
description="Menu bar item for saving as a copy"
|
||||
id="gui.menuBar.saveAsCopy"
|
||||
/>
|
||||
);
|
||||
const remixMessage = (
|
||||
<FormattedMessage
|
||||
defaultMessage="Remix"
|
||||
description="Menu bar item for remixing"
|
||||
id="gui.menuBar.remix"
|
||||
/>
|
||||
);
|
||||
const newProjectMessage = (
|
||||
<FormattedMessage
|
||||
defaultMessage="New"
|
||||
@@ -291,21 +309,12 @@ class MenuBar extends React.Component {
|
||||
place={this.props.isRtl ? 'left' : 'right'}
|
||||
onRequestClose={this.props.onRequestCloseFile}
|
||||
>
|
||||
{this.props.canCreateNew ? (
|
||||
<MenuItem
|
||||
isRtl={this.props.isRtl}
|
||||
onClick={this.handleClickNew}
|
||||
>
|
||||
{newProjectMessage}
|
||||
</MenuItem>
|
||||
) : (this.props.showComingSoon ? (
|
||||
<MenuItemTooltip
|
||||
id="new"
|
||||
isRtl={this.props.isRtl}
|
||||
>
|
||||
<MenuItem>{newProjectMessage}</MenuItem>
|
||||
</MenuItemTooltip>
|
||||
) : [])}
|
||||
<MenuItem
|
||||
isRtl={this.props.isRtl}
|
||||
onClick={this.handleClickNew}
|
||||
>
|
||||
{newProjectMessage}
|
||||
</MenuItem>
|
||||
<MenuSection>
|
||||
{this.props.canSave ? (
|
||||
<MenuItem onClick={this.handleClickSave}>
|
||||
@@ -319,18 +328,23 @@ class MenuBar extends React.Component {
|
||||
<MenuItem>{saveNowMessage}</MenuItem>
|
||||
</MenuItemTooltip>
|
||||
) : [])}
|
||||
{this.props.canSaveAsCopy ? (
|
||||
{this.props.canCreateCopy ? (
|
||||
<MenuItem onClick={this.handleClickSaveAsCopy}>
|
||||
{saveAsCopyMessage}
|
||||
{createCopyMessage}
|
||||
</MenuItem>
|
||||
) : (this.props.showComingSoon ? (
|
||||
<MenuItemTooltip
|
||||
id="copy"
|
||||
isRtl={this.props.isRtl}
|
||||
>
|
||||
<MenuItem>{saveAsCopyMessage}</MenuItem>
|
||||
<MenuItem>{createCopyMessage}</MenuItem>
|
||||
</MenuItemTooltip>
|
||||
) : [])}
|
||||
{this.props.canRemix ? (
|
||||
<MenuItem onClick={this.handleClickRemix}>
|
||||
{remixMessage}
|
||||
</MenuItem>
|
||||
) : []}
|
||||
</MenuSection>
|
||||
<MenuSection>
|
||||
<SBFileUploader onUpdateProjectTitle={this.props.onUpdateProjectTitle}>
|
||||
@@ -627,10 +641,10 @@ class MenuBar extends React.Component {
|
||||
|
||||
MenuBar.propTypes = {
|
||||
accountMenuOpen: PropTypes.bool,
|
||||
canCreateCopy: PropTypes.bool,
|
||||
canCreateNew: PropTypes.bool,
|
||||
canRemix: PropTypes.bool,
|
||||
canSave: PropTypes.bool,
|
||||
canSaveAsCopy: PropTypes.bool,
|
||||
canShare: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
editMenuOpen: PropTypes.bool,
|
||||
@@ -648,7 +662,9 @@ MenuBar.propTypes = {
|
||||
onClickLanguage: PropTypes.func,
|
||||
onClickLogin: PropTypes.func,
|
||||
onClickNew: PropTypes.func,
|
||||
onClickRemix: PropTypes.func,
|
||||
onClickSave: PropTypes.func,
|
||||
onClickSaveAsCopy: PropTypes.func,
|
||||
onLogOut: PropTypes.func,
|
||||
onOpenRegistration: PropTypes.func,
|
||||
onOpenTipLibrary: PropTypes.func,
|
||||
@@ -700,8 +716,10 @@ const mapDispatchToProps = dispatch => ({
|
||||
onRequestCloseLanguage: () => dispatch(closeLanguageMenu()),
|
||||
onClickLogin: () => dispatch(openLoginMenu()),
|
||||
onRequestCloseLogin: () => dispatch(closeLoginMenu()),
|
||||
onClickNew: canSave => dispatch(requestNewProject(canSave)),
|
||||
onClickSave: () => dispatch(saveProject()),
|
||||
onClickNew: canCreateNew => dispatch(requestNewProject(canCreateNew)),
|
||||
onClickRemix: () => dispatch(remixProject()),
|
||||
onClickSave: () => dispatch(updateProject()),
|
||||
onClickSaveAsCopy: () => dispatch(saveProjectAsCopy()),
|
||||
onSeeCommunity: () => dispatch(setPlayer(true))
|
||||
});
|
||||
|
||||
|
||||
@@ -7,14 +7,14 @@ import storage from '../lib/storage';
|
||||
import {
|
||||
LoadingStates,
|
||||
createProject,
|
||||
doneCreatingProject,
|
||||
doneUpdatingProject,
|
||||
getIsCreating,
|
||||
getIsShowingProject,
|
||||
getIsShowingWithoutId,
|
||||
getIsUpdating,
|
||||
onCreated,
|
||||
onUpdated,
|
||||
onError,
|
||||
saveProject
|
||||
projectError,
|
||||
updateProject
|
||||
} from '../reducers/project-state';
|
||||
|
||||
/**
|
||||
@@ -33,34 +33,39 @@ const ProjectSaverHOC = function (WrappedComponent) {
|
||||
this.storeProject(this.props.reduxProjectId)
|
||||
.then(() => {
|
||||
// there is nothing we expect to find in response that we need to check here
|
||||
this.props.onUpdated(this.props.loadingState);
|
||||
this.props.onUpdatedProject(this.props.loadingState);
|
||||
})
|
||||
.catch(err => {
|
||||
// NOTE: should throw up a notice for user
|
||||
this.props.onError(`Saving the project failed with error: ${err}`);
|
||||
this.props.onProjectError(`Saving the project failed with error: ${err}`);
|
||||
});
|
||||
}
|
||||
// TODO: distinguish between creating new, remixing, and saving as a copy
|
||||
if (this.props.isCreating && !prevProps.isCreating) {
|
||||
this.storeProject()
|
||||
.then(response => {
|
||||
this.props.onCreated(response.id.toString());
|
||||
this.props.onCreatedProject(response.id.toString(), this.props.loadingState);
|
||||
})
|
||||
.catch(err => {
|
||||
// NOTE: should throw up a notice for user
|
||||
this.props.onError(`Creating a new project failed with error: ${err}`);
|
||||
this.props.onProjectError(`Creating a new project failed with error: ${err}`);
|
||||
});
|
||||
}
|
||||
// if this is the first time we're able to create this project on the server, create it!
|
||||
const showingCreateable = this.props.canSave && this.props.isShowingWithoutId;
|
||||
const prevShowingCreateable = prevProps.canSave && prevProps.isShowingWithoutId;
|
||||
|
||||
// check if the project state, and user capabilities, have changed so as to indicate
|
||||
// that we should create or update project
|
||||
//
|
||||
// if we're newly able to create this project on the server, create it!
|
||||
const showingCreateable = this.props.canCreateNew && this.props.isShowingWithoutId;
|
||||
const prevShowingCreateable = prevProps.canCreateNew && prevProps.isShowingWithoutId;
|
||||
if (showingCreateable && !prevShowingCreateable) {
|
||||
this.props.createProject();
|
||||
this.props.onCreateProject();
|
||||
} else {
|
||||
// if we're newly *able* to save this project, save it!
|
||||
// if we're newly able to save this project, save it!
|
||||
const showingSaveable = this.props.canSave && this.props.isShowingWithId;
|
||||
const becameAbleToSave = this.props.canSave && !prevProps.canSave;
|
||||
if (showingSaveable && becameAbleToSave) {
|
||||
this.props.saveProject();
|
||||
this.props.onUpdateProject();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,17 +94,17 @@ const ProjectSaverHOC = function (WrappedComponent) {
|
||||
render () {
|
||||
const {
|
||||
/* eslint-disable no-unused-vars */
|
||||
createProject: createProjectProp,
|
||||
isCreating: isCreatingProp,
|
||||
isShowingWithId: isShowingWithIdProp,
|
||||
isShowingWithoutId: isShowingWithoutIdProp,
|
||||
isUpdating: isUpdatingProp,
|
||||
loadingState,
|
||||
onCreated: onCreatedProp,
|
||||
onError: onErrorProp,
|
||||
onUpdated: onUpdatedProp,
|
||||
onCreatedProject: onCreatedProjectProp,
|
||||
onCreateProject: onCreateProjectProp,
|
||||
onProjectError: onProjectErrorProp,
|
||||
onUpdatedProject: onUpdatedProjectProp,
|
||||
onUpdateProject: onUpdateProjectProp,
|
||||
reduxProjectId,
|
||||
saveProject: saveProjectProp,
|
||||
/* eslint-enable no-unused-vars */
|
||||
...componentProps
|
||||
} = this.props;
|
||||
@@ -111,18 +116,19 @@ const ProjectSaverHOC = function (WrappedComponent) {
|
||||
}
|
||||
}
|
||||
ProjectSaverComponent.propTypes = {
|
||||
canCreateNew: PropTypes.bool,
|
||||
canSave: PropTypes.bool,
|
||||
createProject: PropTypes.func,
|
||||
isCreating: PropTypes.bool,
|
||||
isShowingWithId: PropTypes.bool,
|
||||
isShowingWithoutId: PropTypes.bool,
|
||||
isUpdating: PropTypes.bool,
|
||||
loadingState: PropTypes.oneOf(LoadingStates),
|
||||
onCreated: PropTypes.func,
|
||||
onError: PropTypes.func,
|
||||
onUpdated: PropTypes.func,
|
||||
onCreateProject: PropTypes.func,
|
||||
onCreatedProject: PropTypes.func,
|
||||
onProjectError: PropTypes.func,
|
||||
onUpdateProject: PropTypes.func,
|
||||
onUpdatedProject: PropTypes.func,
|
||||
reduxProjectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
saveProject: PropTypes.func,
|
||||
vm: PropTypes.instanceOf(VM).isRequired
|
||||
};
|
||||
const mapStateToProps = state => {
|
||||
@@ -138,11 +144,11 @@ const ProjectSaverHOC = function (WrappedComponent) {
|
||||
};
|
||||
};
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
createProject: () => dispatch(createProject()),
|
||||
onCreated: projectId => dispatch(onCreated(projectId)),
|
||||
onUpdated: (projectId, loadingState) => dispatch(onUpdated(projectId, loadingState)),
|
||||
onError: error => dispatch(onError(error)),
|
||||
saveProject: () => dispatch(saveProject())
|
||||
onCreatedProject: (projectId, loadingState) => dispatch(doneCreatingProject(projectId, loadingState)),
|
||||
onCreateProject: () => dispatch(createProject()),
|
||||
onProjectError: error => dispatch(projectError(error)),
|
||||
onUpdateProject: () => dispatch(updateProject()),
|
||||
onUpdatedProject: (projectId, loadingState) => dispatch(doneUpdatingProject(projectId, loadingState))
|
||||
});
|
||||
// Allow incoming props to override redux-provided props. Used to mock in tests.
|
||||
const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign(
|
||||
|
||||
+125
-52
@@ -1,20 +1,24 @@
|
||||
import keyMirror from 'keymirror';
|
||||
|
||||
const START_CREATING_NEW = 'scratch-gui/project-state/START_CREATING_NEW';
|
||||
const DONE_CREATING_COPY = 'scratch-gui/project-state/DONE_CREATING_COPY';
|
||||
const DONE_CREATING_NEW = 'scratch-gui/project-state/DONE_CREATING_NEW';
|
||||
const DONE_FETCHING_WITH_ID = 'scratch-gui/project-state/DONE_FETCHING_WITH_ID';
|
||||
const DONE_FETCHING_DEFAULT = 'scratch-gui/project-state/DONE_FETCHING_DEFAULT';
|
||||
const DONE_FETCHING_WITH_ID = 'scratch-gui/project-state/DONE_FETCHING_WITH_ID';
|
||||
const DONE_LOADING_VM_TO_SAVE = 'scratch-gui/project-state/DONE_LOADING_VM_TO_SAVE';
|
||||
const DONE_LOADING_VM_WITH_ID = 'scratch-gui/project-state/DONE_LOADING_VM_WITH_ID';
|
||||
const DONE_LOADING_VM_WITHOUT_ID = 'scratch-gui/project-state/DONE_LOADING_VM_WITHOUT_ID';
|
||||
const DONE_LOADING_VM_TO_SAVE = 'scratch-gui/project-state/DONE_LOADING_VM_TO_SAVE';
|
||||
const DONE_SAVING_WITH_ID = 'scratch-gui/project-state/DONE_SAVING_WITH_ID';
|
||||
const DONE_SAVING_WITH_ID_BEFORE_NEW = 'scratch-gui/project-state/DONE_SAVING_WITH_ID_BEFORE_NEW';
|
||||
const GO_TO_ERROR_STATE = 'scratch-gui/project-state/GO_TO_ERROR_STATE';
|
||||
const DONE_REMIXING = 'scratch-gui/project-state/DONE_REMIXING';
|
||||
const DONE_UPDATING = 'scratch-gui/project-state/DONE_UPDATING';
|
||||
const DONE_UPDATING_BEFORE_NEW = 'scratch-gui/project-state/DONE_UPDATING_BEFORE_NEW';
|
||||
const SET_PROJECT_ID = 'scratch-gui/project-state/SET_PROJECT_ID';
|
||||
const START_CREATING_COPY = 'scratch-gui/project-state/START_CREATING_COPY';
|
||||
const START_CREATING_NEW = 'scratch-gui/project-state/START_CREATING_NEW';
|
||||
const START_ERROR = 'scratch-gui/project-state/START_ERROR';
|
||||
const START_FETCHING_NEW = 'scratch-gui/project-state/START_FETCHING_NEW';
|
||||
const START_LOADING_VM_FILE_UPLOAD = 'scratch-gui/project-state/START_LOADING_FILE_UPLOAD';
|
||||
const START_SAVING = 'scratch-gui/project-state/START_SAVING';
|
||||
const START_SAVING_BEFORE_CREATING_NEW = 'scratch-gui/project-state/START_SAVING_BEFORE_CREATING_NEW';
|
||||
const START_REMIXING = 'scratch-gui/project-state/START_REMIXING';
|
||||
const START_UPDATING = 'scratch-gui/project-state/START_UPDATING';
|
||||
const START_UPDATING_BEFORE_CREATING_NEW = 'scratch-gui/project-state/START_UPDATING_BEFORE_CREATING_NEW';
|
||||
|
||||
const defaultProjectId = '0'; // hardcoded id of default project
|
||||
|
||||
@@ -26,10 +30,12 @@ const LoadingState = keyMirror({
|
||||
LOADING_VM_WITH_ID: null,
|
||||
LOADING_VM_FILE_UPLOAD: null,
|
||||
LOADING_VM_NEW_DEFAULT: null,
|
||||
REMIXING: null,
|
||||
SHOWING_WITH_ID: null,
|
||||
SHOWING_WITHOUT_ID: null,
|
||||
SAVING_WITH_ID: null,
|
||||
SAVING_WITH_ID_BEFORE_NEW: null,
|
||||
CREATING_COPY: null,
|
||||
UPDATING: null,
|
||||
UPDATING_BEFORE_NEW: null,
|
||||
CREATING_NEW: null
|
||||
});
|
||||
|
||||
@@ -49,11 +55,13 @@ const getIsLoadingWithId = loadingState => (
|
||||
loadingState === LoadingState.LOADING_VM_NEW_DEFAULT
|
||||
);
|
||||
const getIsCreating = loadingState => (
|
||||
loadingState === LoadingState.CREATING_NEW
|
||||
loadingState === LoadingState.CREATING_NEW ||
|
||||
loadingState === LoadingState.REMIXING ||
|
||||
loadingState === LoadingState.CREATING_COPY
|
||||
);
|
||||
const getIsUpdating = loadingState => (
|
||||
loadingState === LoadingState.SAVING_WITH_ID ||
|
||||
loadingState === LoadingState.SAVING_WITH_ID_BEFORE_NEW
|
||||
loadingState === LoadingState.UPDATING ||
|
||||
loadingState === LoadingState.UPDATING_BEFORE_NEW
|
||||
);
|
||||
const getIsShowingProject = loadingState => (
|
||||
loadingState === LoadingState.SHOWING_WITH_ID ||
|
||||
@@ -86,7 +94,7 @@ const reducer = function (state, action) {
|
||||
if (state.loadingState === LoadingState.CREATING_NEW) {
|
||||
return Object.assign({}, state, {
|
||||
loadingState: LoadingState.SHOWING_WITH_ID,
|
||||
projectId: action.id
|
||||
projectId: action.projectId
|
||||
});
|
||||
}
|
||||
return state;
|
||||
@@ -124,19 +132,39 @@ const reducer = function (state, action) {
|
||||
case DONE_LOADING_VM_TO_SAVE:
|
||||
if (state.loadingState === LoadingState.LOADING_VM_FILE_UPLOAD) {
|
||||
return Object.assign({}, state, {
|
||||
loadingState: LoadingState.SAVING_WITH_ID
|
||||
loadingState: LoadingState.UPDATING
|
||||
});
|
||||
}
|
||||
return state;
|
||||
case DONE_SAVING_WITH_ID:
|
||||
if (state.loadingState === LoadingState.SAVING_WITH_ID) {
|
||||
case DONE_REMIXING:
|
||||
// We need to set project id since we just created new project on the server.
|
||||
// No need to load, we should have data already in vm.
|
||||
if (state.loadingState === LoadingState.REMIXING) {
|
||||
return Object.assign({}, state, {
|
||||
loadingState: LoadingState.SHOWING_WITH_ID,
|
||||
projectId: action.projectId
|
||||
});
|
||||
}
|
||||
return state;
|
||||
case DONE_CREATING_COPY:
|
||||
// We need to set project id since we just created new project on the server.
|
||||
// No need to load, we should have data already in vm.
|
||||
if (state.loadingState === LoadingState.CREATING_COPY) {
|
||||
return Object.assign({}, state, {
|
||||
loadingState: LoadingState.SHOWING_WITH_ID,
|
||||
projectId: action.projectId
|
||||
});
|
||||
}
|
||||
return state;
|
||||
case DONE_UPDATING:
|
||||
if (state.loadingState === LoadingState.UPDATING) {
|
||||
return Object.assign({}, state, {
|
||||
loadingState: LoadingState.SHOWING_WITH_ID
|
||||
});
|
||||
}
|
||||
return state;
|
||||
case DONE_SAVING_WITH_ID_BEFORE_NEW:
|
||||
if (state.loadingState === LoadingState.SAVING_WITH_ID_BEFORE_NEW) {
|
||||
case DONE_UPDATING_BEFORE_NEW:
|
||||
if (state.loadingState === LoadingState.UPDATING_BEFORE_NEW) {
|
||||
return Object.assign({}, state, {
|
||||
loadingState: LoadingState.FETCHING_NEW_DEFAULT,
|
||||
projectId: defaultProjectId
|
||||
@@ -145,7 +173,7 @@ const reducer = function (state, action) {
|
||||
return state;
|
||||
case SET_PROJECT_ID:
|
||||
// if setting the default project id, specifically fetch that project
|
||||
if (action.id === defaultProjectId) {
|
||||
if (action.projectId === defaultProjectId) {
|
||||
return Object.assign({}, state, {
|
||||
loadingState: LoadingState.FETCHING_NEW_DEFAULT,
|
||||
projectId: defaultProjectId
|
||||
@@ -154,16 +182,16 @@ const reducer = function (state, action) {
|
||||
// if we were already showing a project, and a different projectId is set, only fetch that project if
|
||||
// projectId has changed. This prevents re-fetching projects unnecessarily.
|
||||
if (state.loadingState === LoadingState.SHOWING_WITH_ID) {
|
||||
if (state.projectId !== action.id) {
|
||||
if (state.projectId !== action.projectId) {
|
||||
return Object.assign({}, state, {
|
||||
loadingState: LoadingState.FETCHING_WITH_ID,
|
||||
projectId: action.id
|
||||
projectId: action.projectId
|
||||
});
|
||||
}
|
||||
} else { // allow any other states to transition to fetching project
|
||||
return Object.assign({}, state, {
|
||||
loadingState: LoadingState.FETCHING_WITH_ID,
|
||||
projectId: action.id
|
||||
projectId: action.projectId
|
||||
});
|
||||
}
|
||||
return state;
|
||||
@@ -197,30 +225,48 @@ const reducer = function (state, action) {
|
||||
});
|
||||
}
|
||||
return state;
|
||||
case START_SAVING:
|
||||
case START_REMIXING:
|
||||
// do not set projectId to null, because we nay reference it in creating project
|
||||
if (state.loadingState === LoadingState.SHOWING_WITH_ID) {
|
||||
return Object.assign({}, state, {
|
||||
loadingState: LoadingState.SAVING_WITH_ID
|
||||
loadingState: LoadingState.REMIXING
|
||||
});
|
||||
}
|
||||
return state;
|
||||
case START_SAVING_BEFORE_CREATING_NEW:
|
||||
case START_UPDATING:
|
||||
if (state.loadingState === LoadingState.SHOWING_WITH_ID) {
|
||||
return Object.assign({}, state, {
|
||||
loadingState: LoadingState.SAVING_WITH_ID_BEFORE_NEW
|
||||
loadingState: LoadingState.UPDATING
|
||||
});
|
||||
}
|
||||
return state;
|
||||
case GO_TO_ERROR_STATE:
|
||||
case START_CREATING_COPY:
|
||||
// do not set projectId to null, because we nay reference it in creating project
|
||||
if (state.loadingState === LoadingState.SHOWING_WITH_ID) {
|
||||
return Object.assign({}, state, {
|
||||
loadingState: LoadingState.CREATING_COPY
|
||||
});
|
||||
}
|
||||
return state;
|
||||
case START_UPDATING_BEFORE_CREATING_NEW:
|
||||
if (state.loadingState === LoadingState.SHOWING_WITH_ID) {
|
||||
return Object.assign({}, state, {
|
||||
loadingState: LoadingState.UPDATING_BEFORE_NEW
|
||||
});
|
||||
}
|
||||
return state;
|
||||
case START_ERROR:
|
||||
// NOTE: we should introduce handling in components for showing ERROR state
|
||||
if ([
|
||||
LoadingState.CREATING_NEW,
|
||||
LoadingState.FETCHING_NEW_DEFAULT,
|
||||
LoadingState.FETCHING_WITH_ID,
|
||||
LoadingState.LOADING_VM_NEW_DEFAULT,
|
||||
LoadingState.LOADING_VM_WITH_ID,
|
||||
LoadingState.FETCHING_WITH_ID,
|
||||
LoadingState.FETCHING_NEW_DEFAULT,
|
||||
LoadingState.SAVING_WITH_ID,
|
||||
LoadingState.SAVING_WITH_ID_BEFORE_NEW,
|
||||
LoadingState.CREATING_NEW
|
||||
LoadingState.REMIXING,
|
||||
LoadingState.CREATING_COPY,
|
||||
LoadingState.UPDATING_BEFORE_NEW,
|
||||
LoadingState.UPDATING
|
||||
].includes(state.loadingState)) {
|
||||
return Object.assign({}, state, {
|
||||
loadingState: LoadingState.ERROR,
|
||||
@@ -237,10 +283,27 @@ const createProject = () => ({
|
||||
type: START_CREATING_NEW
|
||||
});
|
||||
|
||||
const onCreated = id => ({
|
||||
type: DONE_CREATING_NEW,
|
||||
id: id
|
||||
});
|
||||
const doneCreatingProject = (id, loadingState) => {
|
||||
switch (loadingState) {
|
||||
case LoadingState.CREATING_NEW:
|
||||
return {
|
||||
type: DONE_CREATING_NEW,
|
||||
projectId: id
|
||||
};
|
||||
case LoadingState.CREATING_COPY:
|
||||
return {
|
||||
type: DONE_CREATING_COPY,
|
||||
projectId: id
|
||||
};
|
||||
case LoadingState.REMIXING:
|
||||
return {
|
||||
type: DONE_REMIXING,
|
||||
projectId: id
|
||||
};
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const onFetchedProjectData = (projectData, loadingState) => {
|
||||
switch (loadingState) {
|
||||
@@ -283,15 +346,15 @@ const onLoadedProject = (loadingState, canSave) => {
|
||||
}
|
||||
};
|
||||
|
||||
const onUpdated = loadingState => {
|
||||
const doneUpdatingProject = loadingState => {
|
||||
switch (loadingState) {
|
||||
case LoadingState.SAVING_WITH_ID:
|
||||
case LoadingState.UPDATING:
|
||||
return {
|
||||
type: DONE_SAVING_WITH_ID
|
||||
type: DONE_UPDATING
|
||||
};
|
||||
case LoadingState.SAVING_WITH_ID_BEFORE_NEW:
|
||||
case LoadingState.UPDATING_BEFORE_NEW:
|
||||
return {
|
||||
type: DONE_SAVING_WITH_ID_BEFORE_NEW
|
||||
type: DONE_UPDATING_BEFORE_NEW
|
||||
};
|
||||
default:
|
||||
break;
|
||||
@@ -299,17 +362,17 @@ const onUpdated = loadingState => {
|
||||
};
|
||||
|
||||
const onError = error => ({
|
||||
type: GO_TO_ERROR_STATE,
|
||||
type: START_ERROR,
|
||||
error: error
|
||||
});
|
||||
|
||||
const setProjectId = id => ({
|
||||
type: SET_PROJECT_ID,
|
||||
id: id
|
||||
projectId: id
|
||||
});
|
||||
|
||||
const requestNewProject = canSave => {
|
||||
if (canSave) return {type: START_SAVING_BEFORE_CREATING_NEW};
|
||||
const requestNewProject = canCreateNew => {
|
||||
if (canCreateNew) return {type: START_UPDATING_BEFORE_CREATING_NEW};
|
||||
return {type: START_FETCHING_NEW};
|
||||
};
|
||||
|
||||
@@ -317,8 +380,16 @@ const onProjectUploadStarted = () => ({
|
||||
type: START_LOADING_VM_FILE_UPLOAD
|
||||
});
|
||||
|
||||
const saveProject = () => ({
|
||||
type: START_SAVING
|
||||
const updateProject = () => ({
|
||||
type: START_UPDATING
|
||||
});
|
||||
|
||||
const saveProjectAsCopy = () => ({
|
||||
type: START_CREATING_COPY
|
||||
});
|
||||
|
||||
const remixProject = () => ({
|
||||
type: START_REMIXING
|
||||
});
|
||||
|
||||
export {
|
||||
@@ -337,13 +408,15 @@ export {
|
||||
getIsShowingProject,
|
||||
getIsShowingWithId,
|
||||
getIsShowingWithoutId,
|
||||
onCreated,
|
||||
onError,
|
||||
doneCreatingProject,
|
||||
projectError,
|
||||
onFetchedProjectData,
|
||||
onLoadedProject,
|
||||
onProjectUploadStarted,
|
||||
onUpdated,
|
||||
doneUpdatingProject,
|
||||
requestNewProject,
|
||||
saveProject,
|
||||
remixProject,
|
||||
updateProject,
|
||||
saveProjectAsCopy,
|
||||
setProjectId
|
||||
};
|
||||
@@ -2,15 +2,17 @@
|
||||
import projectStateReducer from '../../../src/reducers/project-state';
|
||||
import {
|
||||
LoadingState,
|
||||
onCreated,
|
||||
onError,
|
||||
doneCreatingProject,
|
||||
doneUpdatingProject,
|
||||
onFetchedProjectData,
|
||||
onLoadedProject,
|
||||
onProjectUploadStarted,
|
||||
onUpdated,
|
||||
projectError,
|
||||
remixProject,
|
||||
requestNewProject,
|
||||
saveProject,
|
||||
setProjectId
|
||||
saveProjectAsCopy,
|
||||
setProjectId,
|
||||
updateProject
|
||||
} from '../../../src/reducers/project-state';
|
||||
|
||||
test('initialState', () => {
|
||||
@@ -23,28 +25,50 @@ test('initialState', () => {
|
||||
expect(projectStateReducer(defaultState, {type: 'anything'}).loadingState).toBe(LoadingState.NOT_LOADED);
|
||||
});
|
||||
|
||||
test('onCreated with projectId type string shows project with that id', () => {
|
||||
test('doneCreatingProject for new project with projectId type string shows project with that id', () => {
|
||||
const initialState = {
|
||||
projectId: null,
|
||||
loadingState: LoadingState.CREATING_NEW
|
||||
};
|
||||
const action = onCreated('100');
|
||||
const action = doneCreatingProject('100', initialState.loadingState);
|
||||
const resultState = projectStateReducer(initialState, action);
|
||||
expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID);
|
||||
expect(resultState.projectId).toBe('100');
|
||||
});
|
||||
|
||||
test('onCreated with projectId type number shows project with id of type number', () => {
|
||||
test('doneCreatingProject for new project with projectId type number shows project with id of type number', () => {
|
||||
const initialState = {
|
||||
projectId: null,
|
||||
loadingState: LoadingState.CREATING_NEW
|
||||
};
|
||||
const action = onCreated(100);
|
||||
const action = doneCreatingProject(100, initialState.loadingState);
|
||||
const resultState = projectStateReducer(initialState, action);
|
||||
expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID);
|
||||
expect(resultState.projectId).toBe(100);
|
||||
});
|
||||
|
||||
test('doneCreatingProject for remix shows project with that id', () => {
|
||||
const initialState = {
|
||||
projectId: null,
|
||||
loadingState: LoadingState.REMIXING
|
||||
};
|
||||
const action = doneCreatingProject('100', initialState.loadingState);
|
||||
const resultState = projectStateReducer(initialState, action);
|
||||
expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID);
|
||||
expect(resultState.projectId).toBe('100');
|
||||
});
|
||||
|
||||
test('doneCreatingProject for save as copy shows project with that id', () => {
|
||||
const initialState = {
|
||||
projectId: null,
|
||||
loadingState: LoadingState.CREATING_COPY
|
||||
};
|
||||
const action = doneCreatingProject('100', initialState.loadingState);
|
||||
const resultState = projectStateReducer(initialState, action);
|
||||
expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID);
|
||||
expect(resultState.projectId).toBe('100');
|
||||
});
|
||||
|
||||
test('onFetchedProjectData with id loads project data into vm', () => {
|
||||
const initialState = {
|
||||
projectData: null,
|
||||
@@ -82,7 +106,7 @@ test('onLoadedProject upload, with canSave true, prepares to save', () => {
|
||||
};
|
||||
const action = onLoadedProject(initialState.loadingState, true);
|
||||
const resultState = projectStateReducer(initialState, action);
|
||||
expect(resultState.loadingState).toBe(LoadingState.SAVING_WITH_ID);
|
||||
expect(resultState.loadingState).toBe(LoadingState.UPDATING);
|
||||
});
|
||||
|
||||
test('onLoadedProject with id shows with id', () => {
|
||||
@@ -112,20 +136,20 @@ test('onLoadedProject new, to save shows without id', () => {
|
||||
expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITHOUT_ID);
|
||||
});
|
||||
|
||||
test('onUpdated with id shows with id', () => {
|
||||
test('doneUpdatingProject with id shows with id', () => {
|
||||
const initialState = {
|
||||
loadingState: LoadingState.SAVING_WITH_ID
|
||||
loadingState: LoadingState.UPDATING
|
||||
};
|
||||
const action = onUpdated(initialState.loadingState);
|
||||
const action = doneUpdatingProject(initialState.loadingState);
|
||||
const resultState = projectStateReducer(initialState, action);
|
||||
expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID);
|
||||
});
|
||||
|
||||
test('onUpdated with id, before new, fetches default project', () => {
|
||||
test('doneUpdatingProject with id, before new, fetches default project', () => {
|
||||
const initialState = {
|
||||
loadingState: LoadingState.SAVING_WITH_ID_BEFORE_NEW
|
||||
loadingState: LoadingState.UPDATING_BEFORE_NEW
|
||||
};
|
||||
const action = onUpdated(initialState.loadingState);
|
||||
const action = doneUpdatingProject(initialState.loadingState);
|
||||
const resultState = projectStateReducer(initialState, action);
|
||||
expect(resultState.loadingState).toBe(LoadingState.FETCHING_NEW_DEFAULT);
|
||||
});
|
||||
@@ -163,7 +187,7 @@ test('setProjectId, with same id as before, but not same type, should fetch beca
|
||||
expect(resultState.projectId).toBe(100);
|
||||
});
|
||||
|
||||
test('requestNewProject, when can\'t save, should fetch default project without id', () => {
|
||||
test('requestNewProject, when can\'t create new, should fetch default project without id', () => {
|
||||
const initialState = {
|
||||
loadingState: LoadingState.SHOWING_WITHOUT_ID
|
||||
};
|
||||
@@ -172,13 +196,13 @@ test('requestNewProject, when can\'t save, should fetch default project without
|
||||
expect(resultState.loadingState).toBe(LoadingState.FETCHING_NEW_DEFAULT);
|
||||
});
|
||||
|
||||
test('requestNewProject, when can save, should save and prepare to fetch default project', () => {
|
||||
test('requestNewProject, when can create new, should save and prepare to fetch default project', () => {
|
||||
const initialState = {
|
||||
loadingState: LoadingState.SHOWING_WITH_ID
|
||||
};
|
||||
const action = requestNewProject(true);
|
||||
const resultState = projectStateReducer(initialState, action);
|
||||
expect(resultState.loadingState).toBe(LoadingState.SAVING_WITH_ID_BEFORE_NEW);
|
||||
expect(resultState.loadingState).toBe(LoadingState.UPDATING_BEFORE_NEW);
|
||||
});
|
||||
|
||||
test('onProjectUploadStarted when project not loaded should load', () => {
|
||||
@@ -208,24 +232,44 @@ test('onProjectUploadStarted when showing project without id should load', () =>
|
||||
expect(resultState.loadingState).toBe(LoadingState.LOADING_VM_FILE_UPLOAD);
|
||||
});
|
||||
|
||||
test('saveProject should prepare to save', () => {
|
||||
test('updateProject should prepare to update', () => {
|
||||
const initialState = {
|
||||
loadingState: LoadingState.SHOWING_WITH_ID
|
||||
};
|
||||
const action = saveProject();
|
||||
const action = updateProject();
|
||||
const resultState = projectStateReducer(initialState, action);
|
||||
expect(resultState.loadingState).toBe(LoadingState.SAVING_WITH_ID);
|
||||
expect(resultState.loadingState).toBe(LoadingState.UPDATING);
|
||||
});
|
||||
|
||||
test('onError from various states should show error', () => {
|
||||
test('saveProjectAsCopy should prepare to save as a copy', () => {
|
||||
const initialState = {
|
||||
loadingState: LoadingState.SHOWING_WITH_ID
|
||||
};
|
||||
const action = saveProjectAsCopy();
|
||||
const resultState = projectStateReducer(initialState, action);
|
||||
expect(resultState.loadingState).toBe(LoadingState.CREATING_COPY);
|
||||
});
|
||||
|
||||
test('remixProject should prepare to remix', () => {
|
||||
const initialState = {
|
||||
loadingState: LoadingState.SHOWING_WITH_ID
|
||||
};
|
||||
const action = remixProject();
|
||||
const resultState = projectStateReducer(initialState, action);
|
||||
expect(resultState.loadingState).toBe(LoadingState.REMIXING);
|
||||
});
|
||||
|
||||
test('projectError from various states should show error', () => {
|
||||
const startStates = [
|
||||
LoadingState.CREATING_NEW,
|
||||
LoadingState.FETCHING_NEW_DEFAULT,
|
||||
LoadingState.FETCHING_WITH_ID,
|
||||
LoadingState.LOADING_VM_NEW_DEFAULT,
|
||||
LoadingState.LOADING_VM_WITH_ID,
|
||||
LoadingState.FETCHING_WITH_ID,
|
||||
LoadingState.FETCHING_NEW_DEFAULT,
|
||||
LoadingState.SAVING_WITH_ID,
|
||||
LoadingState.SAVING_WITH_ID_BEFORE_NEW,
|
||||
LoadingState.CREATING_NEW
|
||||
LoadingState.REMIXING,
|
||||
LoadingState.CREATING_COPY,
|
||||
LoadingState.UPDATING_BEFORE_NEW,
|
||||
LoadingState.UPDATING
|
||||
];
|
||||
for (const startState of startStates) {
|
||||
const initialState = {
|
||||
@@ -239,7 +283,7 @@ test('onError from various states should show error', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('onError from showing project should show error', () => {
|
||||
test('projectError from showing project should show error', () => {
|
||||
const initialState = {
|
||||
error: null,
|
||||
loadingState: LoadingState.FETCHING_WITH_ID
|
||||
|
||||
@@ -23,7 +23,7 @@ describe('projectSaverHOC', () => {
|
||||
});
|
||||
|
||||
test('if canSave becomes true when showing a project with an id, project will be saved', () => {
|
||||
const mockedSaveProject = jest.fn();
|
||||
const mockedUpdateProject = jest.fn();
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
const mounted = mount(
|
||||
@@ -34,15 +34,15 @@ describe('projectSaverHOC', () => {
|
||||
isShowingWithoutId={false}
|
||||
isUpdating={false}
|
||||
loadingState={LoadingState.SHOWING_WITH_ID}
|
||||
saveProject={mockedSaveProject}
|
||||
store={store}
|
||||
vm={vm}
|
||||
onUpdateProject={mockedUpdateProject}
|
||||
/>
|
||||
);
|
||||
mounted.setProps({
|
||||
canSave: true
|
||||
});
|
||||
expect(mockedSaveProject).toHaveBeenCalled();
|
||||
expect(mockedUpdateProject).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('if canSave is alreatdy true and we show a project with an id, project will NOT be saved', () => {
|
||||
@@ -57,9 +57,9 @@ describe('projectSaverHOC', () => {
|
||||
isShowingWithoutId={false}
|
||||
isUpdating={false}
|
||||
loadingState={LoadingState.LOADING_VM_WITH_ID}
|
||||
saveProject={mockedSaveProject}
|
||||
store={store}
|
||||
vm={vm}
|
||||
onUpdateProject={mockedSaveProject}
|
||||
/>
|
||||
);
|
||||
mounted.setProps({
|
||||
@@ -78,13 +78,13 @@ describe('projectSaverHOC', () => {
|
||||
<WrappedComponent
|
||||
isShowingWithoutId
|
||||
canSave={false}
|
||||
createProject={mockedCreateProject}
|
||||
isCreating={false}
|
||||
isShowingWithId={false}
|
||||
isUpdating={false}
|
||||
loadingState={LoadingState.LOADING_VM_NEW_DEFAULT}
|
||||
store={store}
|
||||
vm={vm}
|
||||
onCreateProject={mockedCreateProject}
|
||||
/>
|
||||
);
|
||||
mounted.setProps({
|
||||
@@ -94,37 +94,36 @@ describe('projectSaverHOC', () => {
|
||||
expect(mockedCreateProject).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('if canSave becomes true when showing a project without an id, project will be created', () => {
|
||||
test('if canCreateNew becomes true when showing a project without an id, project will be created', () => {
|
||||
const mockedCreateProject = jest.fn();
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
const mounted = mount(
|
||||
<WrappedComponent
|
||||
isShowingWithoutId
|
||||
canSave={false}
|
||||
createProject={mockedCreateProject}
|
||||
canCreateNew={false}
|
||||
isCreating={false}
|
||||
isShowingWithId={false}
|
||||
isUpdating={false}
|
||||
loadingState={LoadingState.SHOWING_WITHOUT_ID}
|
||||
store={store}
|
||||
vm={vm}
|
||||
onCreateProject={mockedCreateProject}
|
||||
/>
|
||||
);
|
||||
mounted.setProps({
|
||||
canSave: true
|
||||
canCreateNew: true
|
||||
});
|
||||
expect(mockedCreateProject).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('if canSave is true and we transition to showing new project, project will be created', () => {
|
||||
test('if canCreateNew is true and we transition to showing new project, project will be created', () => {
|
||||
const mockedCreateProject = jest.fn();
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
const mounted = mount(
|
||||
<WrappedComponent
|
||||
canSave
|
||||
createProject={mockedCreateProject}
|
||||
canCreateNew
|
||||
isCreating={false}
|
||||
isShowingWithId={false}
|
||||
isShowingWithoutId={false}
|
||||
@@ -132,6 +131,7 @@ describe('projectSaverHOC', () => {
|
||||
loadingState={LoadingState.LOADING_VM_NEW_DEFAULT}
|
||||
store={store}
|
||||
vm={vm}
|
||||
onCreateProject={mockedCreateProject}
|
||||
/>
|
||||
);
|
||||
mounted.setProps({
|
||||
@@ -184,7 +184,7 @@ describe('projectSaverHOC', () => {
|
||||
);
|
||||
mounted.setProps({
|
||||
isUpdating: true,
|
||||
loadingState: LoadingState.SAVING_WITH_ID
|
||||
loadingState: LoadingState.UPDATING
|
||||
});
|
||||
expect(vm.saveProjectSb3).toHaveBeenCalled();
|
||||
});
|
||||
@@ -200,7 +200,7 @@ describe('projectSaverHOC', () => {
|
||||
isCreating={false}
|
||||
isShowingWithId={false}
|
||||
isShowingWithoutId={false}
|
||||
loadingState={LoadingState.SAVING_WITH_ID}
|
||||
loadingState={LoadingState.UPDATING}
|
||||
reduxProjectId={'100'}
|
||||
store={store}
|
||||
vm={vm}
|
||||
@@ -208,7 +208,7 @@ describe('projectSaverHOC', () => {
|
||||
);
|
||||
mounted.setProps({
|
||||
isUpdating: true,
|
||||
loadingState: LoadingState.SAVING_WITH_ID,
|
||||
loadingState: LoadingState.UPDATING,
|
||||
reduxProjectId: '99' // random change to force a re-render and componentDidUpdate
|
||||
});
|
||||
expect(vm.saveProjectSb3).not.toHaveBeenCalled();
|
||||
|
||||
Reference in new issue
Block a user