From 5676cfe7d4f7a4bdbf2be48289d45a22ff359b2a Mon Sep 17 00:00:00 2001 From: Ben Wheeler Date: Wed, 7 Nov 2018 17:26:58 -0500 Subject: [PATCH] save on navigating from page; project watcher waits until save is done --- src/components/menu-bar/community-button.css | 9 ++ src/components/menu-bar/community-button.jsx | 40 ++++++ src/components/menu-bar/menu-bar.css | 16 --- src/components/menu-bar/menu-bar.jsx | 119 ++++++++--------- src/components/menu-bar/share-button.css | 10 ++ src/components/menu-bar/share-button.jsx | 48 +++++++ src/containers/project-watcher.jsx | 64 ++++++++++ src/lib/project-saver-hoc.jsx | 128 +++++++++++-------- 8 files changed, 306 insertions(+), 128 deletions(-) create mode 100644 src/components/menu-bar/community-button.css create mode 100644 src/components/menu-bar/community-button.jsx create mode 100644 src/components/menu-bar/share-button.css create mode 100644 src/components/menu-bar/share-button.jsx create mode 100644 src/containers/project-watcher.jsx diff --git a/src/components/menu-bar/community-button.css b/src/components/menu-bar/community-button.css new file mode 100644 index 000000000..9facb4bc4 --- /dev/null +++ b/src/components/menu-bar/community-button.css @@ -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; +} diff --git a/src/components/menu-bar/community-button.jsx b/src/components/menu-bar/community-button.jsx new file mode 100644 index 000000000..20aa8e10d --- /dev/null +++ b/src/components/menu-bar/community-button.jsx @@ -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 +}) => ( + +); + +CommunityButton.propTypes = { + className: PropTypes.string, + onClick: PropTypes.func +}; + +CommunityButton.defaultProps = { + onClick: () => {} +}; + +export default CommunityButton; diff --git a/src/components/menu-bar/menu-bar.css b/src/components/menu-bar/menu-bar.css index 29902eabb..3112c90cb 100644 --- a/src/components/menu-bar/menu-bar.css +++ b/src/components/menu-bar/menu-bar.css @@ -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 } diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx index b82ed04ae..c864106ff 100644 --- a/src/components/menu-bar/menu-bar.jsx +++ b/src/components/menu-bar/menu-bar.jsx @@ -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 = ( - - ); - const isSharedMessage = ( - - ); - const shareButton = ( - - ); const remixButton = ( + (this.props.isShowingProject || this.props.isUpdating) && ( + + { + setRequesting => ( + { + this.handleClickSeeCommunity(setRequesting); + }} + /* eslint-enable react/jsx-no-bind */ + /> + ) + } + + ) ) : (this.props.showComingSoon ? ( - + ) : [])} diff --git a/src/components/menu-bar/share-button.css b/src/components/menu-bar/share-button.css new file mode 100644 index 000000000..09452684c --- /dev/null +++ b/src/components/menu-bar/share-button.css @@ -0,0 +1,10 @@ +@import "../../css/colors.css"; + +.share-button { + background: $data-primary; +} + +.share-button-is-shared { + background: $ui-black-transparent; + cursor: default; +} diff --git a/src/components/menu-bar/share-button.jsx b/src/components/menu-bar/share-button.jsx new file mode 100644 index 000000000..7513867ee --- /dev/null +++ b/src/components/menu-bar/share-button.jsx @@ -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 +}) => ( + +); + +ShareButton.propTypes = { + className: PropTypes.string, + isShared: PropTypes.bool, + onClick: PropTypes.func +}; + +ShareButton.defaultProps = { + onClick: () => {} +}; + +export default ShareButton; diff --git a/src/containers/project-watcher.jsx b/src/containers/project-watcher.jsx new file mode 100644 index 000000000..a65ce72fc --- /dev/null +++ b/src/containers/project-watcher.jsx @@ -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); diff --git a/src/lib/project-saver-hoc.jsx b/src/lib/project-saver-hoc.jsx index ce209be89..4d44e143a 100644 --- a/src/lib/project-saver-hoc.jsx +++ b/src/lib/project-saver-hoc.jsx @@ -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),