Merge pull request #2714 from LLK/hotfix/show-update-errors-hoc

[Master] Wrap project inputs to show server validation
This commit is contained in:
Ray Schamp 2019-01-25 11:49:02 -05:00 committed by GitHub
commit db99c12d89
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 178 additions and 80 deletions

View file

@ -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));

View file

@ -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."
}

View file

@ -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 = ({
</a>
<div className="title">
{editable ?
<Formsy onKeyPress={onKeyPress}>
<InplaceInput
className="project-title"
handleUpdate={onUpdate}
name="title"
validationErrors={{
maxLength: intl.formatMessage({
id: 'project.titleMaxLength'
})
}}
validations={{
maxLength: 100
}}
value={projectInfo.title}
/>
</Formsy> :
<FormsyProjectUpdater
field="title"
initialValue={projectInfo.title}
>
{(value, ref, handleUpdate) => (
<Formsy
ref={ref}
onKeyPress={onKeyPress}
>
<InplaceInput
className="project-title"
handleUpdate={handleUpdate}
name="title"
validationErrors={{
maxLength: intl.formatMessage({
id: 'project.titleMaxLength'
})
}}
validations={{
maxLength: 100
}}
value={value}
/>
</Formsy>
)}
</FormsyProjectUpdater> :
<React.Fragment>
<div
className="project-title no-edit"
@ -377,32 +387,40 @@ const PreviewPresentation = ({
<FormattedMessage id="project.instructionsLabel" />
</div>
{editable ?
<Formsy
className="project-description-form"
onKeyPress={onKeyPress}
<FormsyProjectUpdater
field="instructions"
initialValue={projectInfo.instructions}
>
<InplaceInput
className={classNames(
'project-description-edit',
{remixes: parentInfo && parentInfo.author}
)}
handleUpdate={onUpdate}
name="instructions"
placeholder={intl.formatMessage({
id: 'project.descriptionPlaceholder'
})}
type="textarea"
validationErrors={{
maxLength: intl.formatMessage({
id: 'project.descriptionMaxLength'
})
}}
validations={{
maxLength: 5000
}}
value={projectInfo.instructions}
/>
</Formsy> :
{(value, ref, handleUpdate) => (
<Formsy
className="project-description-form"
ref={ref}
onKeyPress={onKeyPress}
>
<InplaceInput
className={classNames(
'project-description-edit',
{remixes: parentInfo && parentInfo.author}
)}
handleUpdate={handleUpdate}
name="instructions"
placeholder={intl.formatMessage({
id: 'project.descriptionPlaceholder'
})}
type="textarea"
validationErrors={{
maxLength: intl.formatMessage({
id: 'project.descriptionMaxLength'
})
}}
validations={{
maxLength: 5000
}}
value={value}
/>
</Formsy>
)}
</FormsyProjectUpdater> :
<div className="project-description">
{decorateText(projectInfo.instructions, {
usernames: true,
@ -419,33 +437,41 @@ const PreviewPresentation = ({
<FormattedMessage id="project.notesAndCreditsLabel" />
</div>
{editable ?
<Formsy
className="project-description-form"
onKeyPress={onKeyPress}
<FormsyProjectUpdater
field="description"
initialValue={projectInfo.description}
>
<InplaceInput
className={classNames(
'project-description-edit',
'last',
{remixes: parentInfo && parentInfo.author}
)}
handleUpdate={onUpdate}
name="description"
placeholder={intl.formatMessage({
id: 'project.notesPlaceholder'
})}
type="textarea"
validationErrors={{
maxLength: intl.formatMessage({
id: 'project.descriptionMaxLength'
})
}}
validations={{
maxLength: 5000
}}
value={projectInfo.description}
/>
</Formsy> :
{(value, ref, handleUpdate) => (
<Formsy
className="project-description-form"
ref={ref}
onKeyPress={onKeyPress}
>
<InplaceInput
className={classNames(
'project-description-edit',
'last',
{remixes: parentInfo && parentInfo.author}
)}
handleUpdate={handleUpdate}
name="description"
placeholder={intl.formatMessage({
id: 'project.notesPlaceholder'
})}
type="textarea"
validationErrors={{
maxLength: intl.formatMessage({
id: 'project.descriptionMaxLength'
})
}}
validations={{
maxLength: 5000
}}
value={value}
/>
</Formsy>
)}
</FormsyProjectUpdater> :
<div className="project-description last">
{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,

View file

@ -149,7 +149,7 @@ $stage-width: 480px;
$arrow-border-width: 1rem;
display: block;
position: absolute;
z-index: 1;
z-index: 5;
margin-top: $arrow-border-width;
border: 1px solid $active-gray;
border-radius: 5px;

View file

@ -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}
/>