From d6c48a5730102fcc7412794e6e75e125c6fb442e Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Thu, 24 Jan 2019 16:53:21 -0500 Subject: [PATCH] Wrap project update inputs to show server validation --- src/views/preview/formsy-project-updater.jsx | 79 +++++++++ src/views/preview/l10n.json | 3 +- src/views/preview/presentation.jsx | 163 +++++++++++-------- src/views/preview/preview.scss | 2 +- src/views/preview/project-view.jsx | 11 +- 5 files changed, 178 insertions(+), 80 deletions(-) create mode 100644 src/views/preview/formsy-project-updater.jsx diff --git a/src/views/preview/formsy-project-updater.jsx b/src/views/preview/formsy-project-updater.jsx new file mode 100644 index 000000000..970a0532a --- /dev/null +++ b/src/views/preview/formsy-project-updater.jsx @@ -0,0 +1,79 @@ +const PropTypes = require('prop-types'); +const React = require('react'); +const bindAll = require('lodash.bindall'); +const api = require('../../lib/api'); +const connect = require('react-redux').connect; +const injectIntl = require('react-intl').injectIntl; +const intlShape = require('react-intl').intlShape; + +class FormsyProjectUpdater extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'setRef', + 'handleUpdate' + ]); + this.state = { + value: props.initialValue, + error: false + }; + } + componentDidUpdate () { + if (this.state.error !== false) { + const errorMessageId = this.state.error === 400 ? + 'project.inappropriateUpdate' : 'general.notAvailableHeadline'; + this.ref.updateInputsWithError({ + [this.props.field]: this.props.intl.formatMessage({ + id: errorMessageId + }) + }); + } + } + handleUpdate (jsonData) { + // Ignore updates that would not change the value + if (jsonData[this.props.field] === this.state.value) return; + + api({ + uri: `/projects/${this.props.projectInfo.id}`, + authentication: this.props.user.token, + method: 'PUT', + json: jsonData + }, (err, body, res) => { + if (res.statusCode === 200) { + this.setState({value: body[this.props.field], error: false}); + } else { + this.setState({error: res.statusCode}); + } + }); + } + setRef (ref) { + this.ref = ref; + } + render () { + return this.props.children( + this.state.value, + this.setRef, + this.handleUpdate + ); + } +} + +FormsyProjectUpdater.propTypes = { + children: PropTypes.func.isRequired, + field: PropTypes.string, + initialValue: PropTypes.string, + intl: intlShape, + projectInfo: PropTypes.shape({ + id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]) + }), + user: PropTypes.shape({ + token: PropTypes.string + }) +}; + +const mapStateToProps = state => ({ + projectInfo: state.preview.projectInfo, + user: state.session.session.user +}); + +module.exports = connect(mapStateToProps)(injectIntl(FormsyProjectUpdater)); diff --git a/src/views/preview/l10n.json b/src/views/preview/l10n.json index 61b73d35e..155e77797 100644 --- a/src/views/preview/l10n.json +++ b/src/views/preview/l10n.json @@ -43,5 +43,6 @@ "project.cloudDataAlert": "This project uses cloud data - a feature that is only available to signed in Scratchers.", "project.cloudVariables": "Cloud Variables", "project.cloudDataLink": "See Data", - "project.usernameBlockAlert": "This project can detect who is using it, through the \"username\" block. To hide your identity, sign out before using the project." + "project.usernameBlockAlert": "This project can detect who is using it, through the \"username\" block. To hide your identity, sign out before using the project.", + "project.inappropriateUpdate": "Hmm...the bad word detector thinks there is a problem with your text. Please change it and remember to be respectful." } diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index 722321e5f..26db0d392 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -29,6 +29,7 @@ const TopLevelComment = require('./comment/top-level-comment.jsx'); const ComposeComment = require('./comment/compose-comment.jsx'); const ExtensionChip = require('./extension-chip.jsx'); const thumbnailUrl = require('../../lib/user-thumbnail'); +const FormsyProjectUpdater = require('./formsy-project-updater.jsx'); const projectShape = require('./projectshape.jsx').projectShape; require('./preview.scss'); @@ -107,7 +108,6 @@ const PreviewPresentation = ({ onShare, onToggleComments, onToggleStudio, - onUpdate, onUpdateProjectId, onUpdateProjectThumbnail, originalInfo, @@ -233,22 +233,32 @@ const PreviewPresentation = ({
{editable ? - - - : + + {(value, ref, handleUpdate) => ( + + + + )} + :
{editable ? - - - : + {(value, ref, handleUpdate) => ( + + + + )} + :
{decorateText(projectInfo.instructions, { usernames: true, @@ -419,33 +437,41 @@ const PreviewPresentation = ({
{editable ? - - - : + {(value, ref, handleUpdate) => ( + + + + )} + :
{decorateText(projectInfo.description, { usernames: true, @@ -675,7 +701,6 @@ PreviewPresentation.propTypes = { onShare: PropTypes.func, onToggleComments: PropTypes.func, onToggleStudio: PropTypes.func, - onUpdate: PropTypes.func, onUpdateProjectId: PropTypes.func, onUpdateProjectThumbnail: PropTypes.func, originalInfo: projectShape, diff --git a/src/views/preview/preview.scss b/src/views/preview/preview.scss index ec95edadf..9a2ca4021 100644 --- a/src/views/preview/preview.scss +++ b/src/views/preview/preview.scss @@ -149,7 +149,7 @@ $stage-width: 480px; $arrow-border-width: 1rem; display: block; position: absolute; - z-index: 1; + z-index: 10; margin-top: $arrow-border-width; border: 1px solid $active-gray; border-radius: 5px; diff --git a/src/views/preview/project-view.jsx b/src/views/preview/project-view.jsx index d7a3691e6..d09e735cd 100644 --- a/src/views/preview/project-view.jsx +++ b/src/views/preview/project-view.jsx @@ -82,7 +82,6 @@ class Preview extends React.Component { 'handleShare', 'handleUpdateProjectId', 'handleUpdateProjectTitle', - 'handleUpdate', 'handleToggleComments', 'initCounts', 'pushHistory', @@ -523,19 +522,14 @@ class Preview extends React.Component { justShared: true }); } - handleUpdate (jsonData) { + handleUpdateProjectTitle (title) { this.props.updateProject( this.props.projectInfo.id, - jsonData, + {title: title}, this.props.user.username, this.props.user.token ); } - handleUpdateProjectTitle (title) { - this.handleUpdate({ - title: title - }); - } handleSetLanguage (locale) { jar.set('scratchlanguage', locale); } @@ -696,7 +690,6 @@ class Preview extends React.Component { onShare={this.handleShare} onToggleComments={this.handleToggleComments} onToggleStudio={this.handleToggleStudio} - onUpdate={this.handleUpdate} onUpdateProjectId={this.handleUpdateProjectId} onUpdateProjectThumbnail={this.props.handleUpdateProjectThumbnail} />