save on navigating from page; project watcher waits until save is done
This commit is contained in:
1 parent
3c75bfc730
commit
5676cfe7d4
8 files changed
+306
-128
No files matched your search
@@ -0,0 +1,9 @@
|
||||
@import "../../css/colors.css";
|
||||
|
||||
.community-button {
|
||||
box-shadow: 0 0 0 1px $ui-black-transparent;
|
||||
}
|
||||
|
||||
.community-button-icon {
|
||||
height: 1.25rem;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import classNames from 'classnames';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Button from '../button/button.jsx';
|
||||
|
||||
import communityIcon from './icon--see-community.svg';
|
||||
import styles from './community-button.css';
|
||||
|
||||
const CommunityButton = ({
|
||||
className,
|
||||
onClick
|
||||
}) => (
|
||||
<Button
|
||||
className={classNames(
|
||||
className,
|
||||
styles.communityButton
|
||||
)}
|
||||
iconClassName={styles.communityButtonIcon}
|
||||
iconSrc={communityIcon}
|
||||
onClick={onClick}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="See Community"
|
||||
description="Label for see community button"
|
||||
id="gui.menuBar.seeCommunity"
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
|
||||
CommunityButton.propTypes = {
|
||||
className: PropTypes.string,
|
||||
onClick: PropTypes.func
|
||||
};
|
||||
|
||||
CommunityButton.defaultProps = {
|
||||
onClick: () => {}
|
||||
};
|
||||
|
||||
export default CommunityButton;
|
||||
@@ -130,22 +130,6 @@
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.share-button {
|
||||
background: $data-primary;
|
||||
}
|
||||
|
||||
.share-button-is-shared {
|
||||
background: $ui-black-transparent;
|
||||
}
|
||||
|
||||
.community-button {
|
||||
box-shadow: 0 0 0 1px $ui-black-transparent;
|
||||
}
|
||||
|
||||
.community-button-icon {
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
.remix-button {
|
||||
background-color: $pen-primary
|
||||
}
|
||||
|
||||
@@ -7,10 +7,13 @@ import React from 'react';
|
||||
|
||||
import Box from '../box/box.jsx';
|
||||
import Button from '../button/button.jsx';
|
||||
import CommunityButton from './community-button.jsx';
|
||||
import ShareButton from './share-button.jsx';
|
||||
import {ComingSoonTooltip} from '../coming-soon/coming-soon.jsx';
|
||||
import Divider from '../divider/divider.jsx';
|
||||
import LanguageSelector from '../../containers/language-selector.jsx';
|
||||
import SBFileUploader from '../../containers/sb-file-uploader.jsx';
|
||||
import ProjectWatcher from '../../containers/project-watcher.jsx';
|
||||
import MenuBarMenu from './menu-bar-menu.jsx';
|
||||
import {MenuItem, MenuSection} from '../menu/menu.jsx';
|
||||
import ProjectTitleInput from './project-title-input.jsx';
|
||||
@@ -54,7 +57,6 @@ import helpIcon from '../../lib/assets/icon--tutorials.svg';
|
||||
import mystuffIcon from './icon--mystuff.png';
|
||||
import feedbackIcon from './icon--feedback.svg';
|
||||
import profileIcon from './icon--profile.png';
|
||||
import communityIcon from './icon--see-community.svg';
|
||||
import remixIcon from './icon--remix.svg';
|
||||
import dropdownCaret from './dropdown-caret.svg';
|
||||
import languageIcon from '../language-selector/language-icon.svg';
|
||||
@@ -136,6 +138,8 @@ class MenuBar extends React.Component {
|
||||
'handleClickRemix',
|
||||
'handleClickSave',
|
||||
'handleClickSaveAsCopy',
|
||||
'handleClickSeeCommunity',
|
||||
'handleClickShare',
|
||||
'handleCloseFileMenuAndThen',
|
||||
'handleLanguageMouseUp',
|
||||
'handleRestoreOption',
|
||||
@@ -165,6 +169,21 @@ class MenuBar extends React.Component {
|
||||
this.props.onClickSaveAsCopy();
|
||||
this.props.onRequestCloseFile();
|
||||
}
|
||||
handleClickSeeCommunity (requestSeeCommunity) {
|
||||
if (this.props.canSave) { // save before transitioning to project page
|
||||
this.props.onClickSave();
|
||||
}
|
||||
requestSeeCommunity(); // queue the transition to project page
|
||||
}
|
||||
handleClickShare (requestSeeCommunity) {
|
||||
if (this.props.canSave && !this.props.isShared) { // save before transitioning to project page
|
||||
this.props.onClickSave();
|
||||
}
|
||||
if (this.props.canShare && !this.props.isShared) { // save before transitioning to project page
|
||||
this.props.onShare();
|
||||
requestSeeCommunity(); // queue the transition to project page
|
||||
}
|
||||
}
|
||||
handleRestoreOption (restoreFun) {
|
||||
return () => {
|
||||
restoreFun();
|
||||
@@ -240,32 +259,6 @@ class MenuBar extends React.Component {
|
||||
id="gui.menuBar.new"
|
||||
/>
|
||||
);
|
||||
const shareMessage = (
|
||||
<FormattedMessage
|
||||
defaultMessage="Share"
|
||||
description="Label for project share button"
|
||||
id="gui.menuBar.share"
|
||||
/>
|
||||
);
|
||||
const isSharedMessage = (
|
||||
<FormattedMessage
|
||||
defaultMessage="Shared"
|
||||
description="Label for shared project"
|
||||
id="gui.menuBar.isShared"
|
||||
/>
|
||||
);
|
||||
const shareButton = (
|
||||
<Button
|
||||
className={classNames(
|
||||
styles.menuBarButton,
|
||||
styles.shareButton,
|
||||
{[styles.shareButtonIsShared]: this.props.isShared}
|
||||
)}
|
||||
onClick={this.props.onShare}
|
||||
>
|
||||
{this.props.isShared ? isSharedMessage : shareMessage}
|
||||
</Button>
|
||||
);
|
||||
const remixButton = (
|
||||
<Button
|
||||
className={classNames(
|
||||
@@ -467,10 +460,30 @@ class MenuBar extends React.Component {
|
||||
</MenuBarItemTooltip>
|
||||
</div>
|
||||
<div className={classNames(styles.menuBarItem)}>
|
||||
{this.props.canShare ? shareButton : (
|
||||
{this.props.canShare ? (
|
||||
(this.props.isShowingProject || this.props.isUpdating) && (
|
||||
<ProjectWatcher
|
||||
onShowingWithId={this.props.onSeeCommunity}
|
||||
>
|
||||
{
|
||||
setRequesting => (
|
||||
<ShareButton
|
||||
className={styles.menuBarButton}
|
||||
isShared={this.props.isShared}
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
onClick={() => {
|
||||
this.handleClickShare(setRequesting);
|
||||
}}
|
||||
/* eslint-enable react/jsx-no-bind */
|
||||
/>
|
||||
)
|
||||
}
|
||||
</ProjectWatcher>
|
||||
)
|
||||
) : (
|
||||
this.props.showComingSoon ? (
|
||||
<MenuBarItemTooltip id="share-button">
|
||||
{shareButton}
|
||||
<ShareButton className={styles.menuBarButton} />
|
||||
</MenuBarItemTooltip>
|
||||
) : []
|
||||
)}
|
||||
@@ -478,37 +491,27 @@ class MenuBar extends React.Component {
|
||||
</div>
|
||||
<div className={classNames(styles.menuBarItem, styles.communityButtonWrapper)}>
|
||||
{this.props.enableCommunity ? (
|
||||
<Button
|
||||
className={classNames(
|
||||
styles.menuBarButton,
|
||||
styles.communityButton
|
||||
)}
|
||||
iconClassName={styles.communityButtonIcon}
|
||||
iconSrc={communityIcon}
|
||||
onClick={this.props.onSeeCommunity}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="See Community"
|
||||
description="Label for see community button"
|
||||
id="gui.menuBar.seeCommunity"
|
||||
/>
|
||||
</Button>
|
||||
(this.props.isShowingProject || this.props.isUpdating) && (
|
||||
<ProjectWatcher
|
||||
onShowingWithId={this.props.onSeeCommunity}
|
||||
>
|
||||
{
|
||||
setRequesting => (
|
||||
<CommunityButton
|
||||
className={styles.menuBarButton}
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
onClick={() => {
|
||||
this.handleClickSeeCommunity(setRequesting);
|
||||
}}
|
||||
/* eslint-enable react/jsx-no-bind */
|
||||
/>
|
||||
)
|
||||
}
|
||||
</ProjectWatcher>
|
||||
)
|
||||
) : (this.props.showComingSoon ? (
|
||||
<MenuBarItemTooltip id="community-button">
|
||||
<Button
|
||||
className={classNames(
|
||||
styles.menuBarButton,
|
||||
styles.communityButton
|
||||
)}
|
||||
iconClassName={styles.communityButtonIcon}
|
||||
iconSrc={communityIcon}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="See Community"
|
||||
description="Label for see community button"
|
||||
id="gui.menuBar.seeCommunity"
|
||||
/>
|
||||
</Button>
|
||||
<CommunityButton className={styles.menuBarButton} />
|
||||
</MenuBarItemTooltip>
|
||||
) : [])}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
@import "../../css/colors.css";
|
||||
|
||||
.share-button {
|
||||
background: $data-primary;
|
||||
}
|
||||
|
||||
.share-button-is-shared {
|
||||
background: $ui-black-transparent;
|
||||
cursor: default;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import classNames from 'classnames';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Button from '../button/button.jsx';
|
||||
|
||||
import styles from './share-button.css';
|
||||
|
||||
const ShareButton = ({
|
||||
className,
|
||||
isShared,
|
||||
onClick
|
||||
}) => (
|
||||
<Button
|
||||
className={classNames(
|
||||
className,
|
||||
styles.shareButton,
|
||||
{[styles.shareButtonIsShared]: isShared}
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
{isShared ? (
|
||||
<FormattedMessage
|
||||
defaultMessage="Shared"
|
||||
description="Label for shared project"
|
||||
id="gui.menuBar.isShared"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
defaultMessage="Share"
|
||||
description="Label for project share button"
|
||||
id="gui.menuBar.share"
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
|
||||
ShareButton.propTypes = {
|
||||
className: PropTypes.string,
|
||||
isShared: PropTypes.bool,
|
||||
onClick: PropTypes.func
|
||||
};
|
||||
|
||||
ShareButton.defaultProps = {
|
||||
onClick: () => {}
|
||||
};
|
||||
|
||||
export default ShareButton;
|
||||
@@ -0,0 +1,64 @@
|
||||
import bindAll from 'lodash.bindall';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {
|
||||
getIsShowingWithId
|
||||
} from '../reducers/project-state';
|
||||
|
||||
class ProjectWatcher extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'setRequesting'
|
||||
]);
|
||||
|
||||
this.state = {
|
||||
requesting: false
|
||||
};
|
||||
}
|
||||
componentDidUpdate (prevProps) {
|
||||
if (this.state.requesting && this.props.isShowingWithId && !prevProps.isShowingWithId) {
|
||||
this.props.onShowingWithId();
|
||||
this.setState({ // eslint-disable-line react/no-did-update-set-state
|
||||
requesting: false
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
setRequesting () {
|
||||
this.setState({
|
||||
requesting: true
|
||||
});
|
||||
}
|
||||
render () {
|
||||
return this.props.children(
|
||||
this.setRequesting
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ProjectWatcher.propTypes = {
|
||||
children: PropTypes.func,
|
||||
isShowingWithId: PropTypes.bool,
|
||||
onShowingWithId: PropTypes.func
|
||||
};
|
||||
|
||||
ProjectWatcher.defaultProps = {
|
||||
onShowingWithId: () => {}
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const loadingState = state.scratchGui.projectState.loadingState;
|
||||
return {
|
||||
isShowingWithId: getIsShowingWithId(loadingState)
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = () => {};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ProjectWatcher);
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
getIsCreatingNew,
|
||||
getIsManualUpdating,
|
||||
getIsRemixing,
|
||||
getIsShowingProject,
|
||||
getIsShowingWithId,
|
||||
getIsShowingWithoutId,
|
||||
getIsUpdating,
|
||||
manualUpdateProject,
|
||||
@@ -35,63 +35,16 @@ const ProjectSaverHOC = function (WrappedComponent) {
|
||||
class ProjectSaverComponent extends React.Component {
|
||||
componentDidUpdate (prevProps) {
|
||||
if (this.props.isUpdating && !prevProps.isUpdating) {
|
||||
if (this.props.isManualUpdating) {
|
||||
this.props.onShowSavingAlert();
|
||||
}
|
||||
this.storeProject(this.props.reduxProjectId)
|
||||
.then(() => {
|
||||
// there is nothing we expect to find in response that we need to check here
|
||||
if (this.props.isManualUpdating) {
|
||||
this.props.onShowSaveSuccessAlert();
|
||||
}
|
||||
this.props.onUpdatedProject(this.props.loadingState);
|
||||
})
|
||||
.catch(err => {
|
||||
// NOTE: should throw up a notice for user
|
||||
this.props.onProjectError(`Saving the project failed with error: ${err}`);
|
||||
});
|
||||
this.updateProjectToStorage();
|
||||
}
|
||||
// TODO: distinguish between creating new, remixing, and saving as a copy
|
||||
if (this.props.isCreatingNew && !prevProps.isCreatingNew) {
|
||||
this.props.onShowCreatingAlert();
|
||||
this.storeProject(null)
|
||||
.then(response => {
|
||||
this.props.onShowCreateSuccessAlert();
|
||||
this.props.onCreatedProject(response.id.toString(), this.props.loadingState);
|
||||
})
|
||||
.catch(err => {
|
||||
this.props.onProjectError(`Creating a new project failed with error: ${err}`);
|
||||
});
|
||||
this.createNewProjectToStorage();
|
||||
}
|
||||
if (this.props.isCreatingCopy && !prevProps.isCreatingCopy) {
|
||||
this.props.onShowCreatingAlert();
|
||||
this.storeProject(null, {
|
||||
original_id: this.props.reduxProjectId,
|
||||
is_copy: 1,
|
||||
title: this.props.reduxProjectTitle
|
||||
})
|
||||
.then(response => {
|
||||
this.props.onShowCreateSuccessAlert();
|
||||
this.props.onCreatedProject(response.id.toString(), this.props.loadingState);
|
||||
})
|
||||
.catch(err => {
|
||||
this.props.onProjectError(`Creating a project copy failed with error: ${err}`);
|
||||
});
|
||||
this.createCopyToStorage();
|
||||
}
|
||||
if (this.props.isRemixing && !prevProps.isRemixing) {
|
||||
this.props.onShowCreatingAlert();
|
||||
this.storeProject(null, {
|
||||
original_id: this.props.reduxProjectId,
|
||||
is_remix: 1,
|
||||
title: this.props.reduxProjectTitle
|
||||
})
|
||||
.then(response => {
|
||||
this.props.onShowCreateSuccessAlert();
|
||||
this.props.onCreatedProject(response.id.toString(), this.props.loadingState);
|
||||
})
|
||||
.catch(err => {
|
||||
this.props.onProjectError(`Remixing a project failed with error: ${err}`);
|
||||
});
|
||||
this.createRemixToStorage();
|
||||
}
|
||||
|
||||
// check if the project state, and user capabilities, have changed so as to indicate
|
||||
@@ -106,11 +59,76 @@ const ProjectSaverHOC = function (WrappedComponent) {
|
||||
// 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) {
|
||||
const becameShared = this.props.isShared && !prevProps.isShared;
|
||||
if (showingSaveable && (becameAbleToSave || becameShared)) {
|
||||
this.props.onAutoUpdateProject();
|
||||
}
|
||||
}
|
||||
}
|
||||
componentWillUnmount () {
|
||||
const showingSaveable = this.props.canSave && this.props.isShowingWithId;
|
||||
if (showingSaveable) {
|
||||
this.updateProjectToStorage();
|
||||
}
|
||||
}
|
||||
updateProjectToStorage () {
|
||||
if (this.props.isManualUpdating) {
|
||||
this.props.onShowSavingAlert();
|
||||
}
|
||||
return this.storeProject(this.props.reduxProjectId)
|
||||
.then(() => {
|
||||
if (this.props.isManualUpdating) {
|
||||
this.props.onShowSaveSuccessAlert();
|
||||
}
|
||||
// there is nothing we expect to find in response that we need to check here
|
||||
this.props.onUpdatedProject(this.props.loadingState);
|
||||
})
|
||||
.catch(err => {
|
||||
// NOTE: should throw up a notice for user
|
||||
this.props.onProjectError(`Saving the project failed with error: ${err}`);
|
||||
});
|
||||
}
|
||||
createNewProjectToStorage () {
|
||||
this.props.onShowCreatingAlert();
|
||||
return this.storeProject(null)
|
||||
.then(response => {
|
||||
this.props.onShowCreateSuccessAlert();
|
||||
this.props.onCreatedProject(response.id.toString(), this.props.loadingState);
|
||||
})
|
||||
.catch(err => {
|
||||
this.props.onProjectError(`Creating a new project failed with error: ${err}`);
|
||||
});
|
||||
}
|
||||
createCopyToStorage () {
|
||||
this.props.onShowCreatingAlert();
|
||||
return this.storeProject(null, {
|
||||
original_id: this.props.reduxProjectId,
|
||||
is_copy: 1,
|
||||
title: this.props.reduxProjectTitle
|
||||
})
|
||||
.then(response => {
|
||||
this.props.onShowCreateSuccessAlert();
|
||||
this.props.onCreatedProject(response.id.toString(), this.props.loadingState);
|
||||
})
|
||||
.catch(err => {
|
||||
this.props.onProjectError(`Creating a project copy failed with error: ${err}`);
|
||||
});
|
||||
}
|
||||
createRemixToStorage () {
|
||||
this.props.onShowCreatingAlert();
|
||||
return this.storeProject(null, {
|
||||
original_id: this.props.reduxProjectId,
|
||||
is_remix: 1,
|
||||
title: this.props.reduxProjectTitle
|
||||
})
|
||||
.then(response => {
|
||||
this.props.onShowCreateSuccessAlert();
|
||||
this.props.onCreatedProject(response.id.toString(), this.props.loadingState);
|
||||
})
|
||||
.catch(err => {
|
||||
this.props.onProjectError(`Remixing a project failed with error: ${err}`);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* storeProject:
|
||||
* @param {number|string|undefined} projectId - defined value will PUT/update; undefined/null will POST/create
|
||||
@@ -172,6 +190,7 @@ const ProjectSaverHOC = function (WrappedComponent) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ProjectSaverComponent.propTypes = {
|
||||
canCreateNew: PropTypes.bool,
|
||||
canSave: PropTypes.bool,
|
||||
@@ -179,6 +198,7 @@ const ProjectSaverHOC = function (WrappedComponent) {
|
||||
isCreatingNew: PropTypes.bool,
|
||||
isManualUpdating: PropTypes.bool,
|
||||
isRemixing: PropTypes.bool,
|
||||
isShared: PropTypes.bool,
|
||||
isShowingWithId: PropTypes.bool,
|
||||
isShowingWithoutId: PropTypes.bool,
|
||||
isUpdating: PropTypes.bool,
|
||||
@@ -203,7 +223,7 @@ const ProjectSaverHOC = function (WrappedComponent) {
|
||||
isCreatingCopy: getIsCreatingCopy(loadingState),
|
||||
isCreatingNew: getIsCreatingNew(loadingState),
|
||||
isRemixing: getIsRemixing(loadingState),
|
||||
isShowingWithId: getIsShowingProject(loadingState),
|
||||
isShowingWithId: getIsShowingWithId(loadingState),
|
||||
isShowingWithoutId: getIsShowingWithoutId(loadingState),
|
||||
isUpdating: getIsUpdating(loadingState),
|
||||
isManualUpdating: getIsManualUpdating(loadingState),
|
||||
|
||||
Reference in new issue
Block a user