save on navigating from page; project watcher waits until save is done

This commit is contained in:
Ben Wheeler committed 2018-11-09 09:11:13 -05:00
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;
-16
View File
@@ -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
}
+61 -58
View File
@@ -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>
+10
View File
@@ -0,0 +1,10 @@
@import "../../css/colors.css";
.share-button {
background: $data-primary;
}
.share-button-is-shared {
background: $ui-black-transparent;
cursor: default;
}
+48
View File
@@ -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;
+64
View File
@@ -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);
+74 -54
View File
@@ -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),