mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-23 07:38:07 -05:00
Merge pull request #2714 from LLK/hotfix/show-update-errors-hoc
[Master] Wrap project inputs to show server validation
This commit is contained in:
commit
db99c12d89
5 changed files with 178 additions and 80 deletions
79
src/views/preview/formsy-project-updater.jsx
Normal file
79
src/views/preview/formsy-project-updater.jsx
Normal 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));
|
|
@ -43,5 +43,6 @@
|
||||||
"project.cloudDataAlert": "This project uses cloud data - a feature that is only available to signed in Scratchers.",
|
"project.cloudDataAlert": "This project uses cloud data - a feature that is only available to signed in Scratchers.",
|
||||||
"project.cloudVariables": "Cloud Variables",
|
"project.cloudVariables": "Cloud Variables",
|
||||||
"project.cloudDataLink": "See Data",
|
"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."
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ const TopLevelComment = require('./comment/top-level-comment.jsx');
|
||||||
const ComposeComment = require('./comment/compose-comment.jsx');
|
const ComposeComment = require('./comment/compose-comment.jsx');
|
||||||
const ExtensionChip = require('./extension-chip.jsx');
|
const ExtensionChip = require('./extension-chip.jsx');
|
||||||
const thumbnailUrl = require('../../lib/user-thumbnail');
|
const thumbnailUrl = require('../../lib/user-thumbnail');
|
||||||
|
const FormsyProjectUpdater = require('./formsy-project-updater.jsx');
|
||||||
|
|
||||||
const projectShape = require('./projectshape.jsx').projectShape;
|
const projectShape = require('./projectshape.jsx').projectShape;
|
||||||
require('./preview.scss');
|
require('./preview.scss');
|
||||||
|
@ -107,7 +108,6 @@ const PreviewPresentation = ({
|
||||||
onShare,
|
onShare,
|
||||||
onToggleComments,
|
onToggleComments,
|
||||||
onToggleStudio,
|
onToggleStudio,
|
||||||
onUpdate,
|
|
||||||
onUpdateProjectId,
|
onUpdateProjectId,
|
||||||
onUpdateProjectThumbnail,
|
onUpdateProjectThumbnail,
|
||||||
originalInfo,
|
originalInfo,
|
||||||
|
@ -233,22 +233,32 @@ const PreviewPresentation = ({
|
||||||
</a>
|
</a>
|
||||||
<div className="title">
|
<div className="title">
|
||||||
{editable ?
|
{editable ?
|
||||||
<Formsy onKeyPress={onKeyPress}>
|
<FormsyProjectUpdater
|
||||||
<InplaceInput
|
field="title"
|
||||||
className="project-title"
|
initialValue={projectInfo.title}
|
||||||
handleUpdate={onUpdate}
|
>
|
||||||
name="title"
|
{(value, ref, handleUpdate) => (
|
||||||
validationErrors={{
|
<Formsy
|
||||||
maxLength: intl.formatMessage({
|
ref={ref}
|
||||||
id: 'project.titleMaxLength'
|
onKeyPress={onKeyPress}
|
||||||
})
|
>
|
||||||
}}
|
<InplaceInput
|
||||||
validations={{
|
className="project-title"
|
||||||
maxLength: 100
|
handleUpdate={handleUpdate}
|
||||||
}}
|
name="title"
|
||||||
value={projectInfo.title}
|
validationErrors={{
|
||||||
/>
|
maxLength: intl.formatMessage({
|
||||||
</Formsy> :
|
id: 'project.titleMaxLength'
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
validations={{
|
||||||
|
maxLength: 100
|
||||||
|
}}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
</Formsy>
|
||||||
|
)}
|
||||||
|
</FormsyProjectUpdater> :
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<div
|
<div
|
||||||
className="project-title no-edit"
|
className="project-title no-edit"
|
||||||
|
@ -377,32 +387,40 @@ const PreviewPresentation = ({
|
||||||
<FormattedMessage id="project.instructionsLabel" />
|
<FormattedMessage id="project.instructionsLabel" />
|
||||||
</div>
|
</div>
|
||||||
{editable ?
|
{editable ?
|
||||||
<Formsy
|
<FormsyProjectUpdater
|
||||||
className="project-description-form"
|
field="instructions"
|
||||||
onKeyPress={onKeyPress}
|
initialValue={projectInfo.instructions}
|
||||||
>
|
>
|
||||||
<InplaceInput
|
{(value, ref, handleUpdate) => (
|
||||||
className={classNames(
|
<Formsy
|
||||||
'project-description-edit',
|
className="project-description-form"
|
||||||
{remixes: parentInfo && parentInfo.author}
|
ref={ref}
|
||||||
)}
|
onKeyPress={onKeyPress}
|
||||||
handleUpdate={onUpdate}
|
>
|
||||||
name="instructions"
|
<InplaceInput
|
||||||
placeholder={intl.formatMessage({
|
className={classNames(
|
||||||
id: 'project.descriptionPlaceholder'
|
'project-description-edit',
|
||||||
})}
|
{remixes: parentInfo && parentInfo.author}
|
||||||
type="textarea"
|
)}
|
||||||
validationErrors={{
|
handleUpdate={handleUpdate}
|
||||||
maxLength: intl.formatMessage({
|
name="instructions"
|
||||||
id: 'project.descriptionMaxLength'
|
placeholder={intl.formatMessage({
|
||||||
})
|
id: 'project.descriptionPlaceholder'
|
||||||
}}
|
})}
|
||||||
validations={{
|
type="textarea"
|
||||||
maxLength: 5000
|
validationErrors={{
|
||||||
}}
|
maxLength: intl.formatMessage({
|
||||||
value={projectInfo.instructions}
|
id: 'project.descriptionMaxLength'
|
||||||
/>
|
})
|
||||||
</Formsy> :
|
}}
|
||||||
|
validations={{
|
||||||
|
maxLength: 5000
|
||||||
|
}}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
</Formsy>
|
||||||
|
)}
|
||||||
|
</FormsyProjectUpdater> :
|
||||||
<div className="project-description">
|
<div className="project-description">
|
||||||
{decorateText(projectInfo.instructions, {
|
{decorateText(projectInfo.instructions, {
|
||||||
usernames: true,
|
usernames: true,
|
||||||
|
@ -419,33 +437,41 @@ const PreviewPresentation = ({
|
||||||
<FormattedMessage id="project.notesAndCreditsLabel" />
|
<FormattedMessage id="project.notesAndCreditsLabel" />
|
||||||
</div>
|
</div>
|
||||||
{editable ?
|
{editable ?
|
||||||
<Formsy
|
<FormsyProjectUpdater
|
||||||
className="project-description-form"
|
field="description"
|
||||||
onKeyPress={onKeyPress}
|
initialValue={projectInfo.description}
|
||||||
>
|
>
|
||||||
<InplaceInput
|
{(value, ref, handleUpdate) => (
|
||||||
className={classNames(
|
<Formsy
|
||||||
'project-description-edit',
|
className="project-description-form"
|
||||||
'last',
|
ref={ref}
|
||||||
{remixes: parentInfo && parentInfo.author}
|
onKeyPress={onKeyPress}
|
||||||
)}
|
>
|
||||||
handleUpdate={onUpdate}
|
<InplaceInput
|
||||||
name="description"
|
className={classNames(
|
||||||
placeholder={intl.formatMessage({
|
'project-description-edit',
|
||||||
id: 'project.notesPlaceholder'
|
'last',
|
||||||
})}
|
{remixes: parentInfo && parentInfo.author}
|
||||||
type="textarea"
|
)}
|
||||||
validationErrors={{
|
handleUpdate={handleUpdate}
|
||||||
maxLength: intl.formatMessage({
|
name="description"
|
||||||
id: 'project.descriptionMaxLength'
|
placeholder={intl.formatMessage({
|
||||||
})
|
id: 'project.notesPlaceholder'
|
||||||
}}
|
})}
|
||||||
validations={{
|
type="textarea"
|
||||||
maxLength: 5000
|
validationErrors={{
|
||||||
}}
|
maxLength: intl.formatMessage({
|
||||||
value={projectInfo.description}
|
id: 'project.descriptionMaxLength'
|
||||||
/>
|
})
|
||||||
</Formsy> :
|
}}
|
||||||
|
validations={{
|
||||||
|
maxLength: 5000
|
||||||
|
}}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
</Formsy>
|
||||||
|
)}
|
||||||
|
</FormsyProjectUpdater> :
|
||||||
<div className="project-description last">
|
<div className="project-description last">
|
||||||
{decorateText(projectInfo.description, {
|
{decorateText(projectInfo.description, {
|
||||||
usernames: true,
|
usernames: true,
|
||||||
|
@ -675,7 +701,6 @@ PreviewPresentation.propTypes = {
|
||||||
onShare: PropTypes.func,
|
onShare: PropTypes.func,
|
||||||
onToggleComments: PropTypes.func,
|
onToggleComments: PropTypes.func,
|
||||||
onToggleStudio: PropTypes.func,
|
onToggleStudio: PropTypes.func,
|
||||||
onUpdate: PropTypes.func,
|
|
||||||
onUpdateProjectId: PropTypes.func,
|
onUpdateProjectId: PropTypes.func,
|
||||||
onUpdateProjectThumbnail: PropTypes.func,
|
onUpdateProjectThumbnail: PropTypes.func,
|
||||||
originalInfo: projectShape,
|
originalInfo: projectShape,
|
||||||
|
|
|
@ -149,7 +149,7 @@ $stage-width: 480px;
|
||||||
$arrow-border-width: 1rem;
|
$arrow-border-width: 1rem;
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 5;
|
||||||
margin-top: $arrow-border-width;
|
margin-top: $arrow-border-width;
|
||||||
border: 1px solid $active-gray;
|
border: 1px solid $active-gray;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
|
|
@ -82,7 +82,6 @@ class Preview extends React.Component {
|
||||||
'handleShare',
|
'handleShare',
|
||||||
'handleUpdateProjectId',
|
'handleUpdateProjectId',
|
||||||
'handleUpdateProjectTitle',
|
'handleUpdateProjectTitle',
|
||||||
'handleUpdate',
|
|
||||||
'handleToggleComments',
|
'handleToggleComments',
|
||||||
'initCounts',
|
'initCounts',
|
||||||
'pushHistory',
|
'pushHistory',
|
||||||
|
@ -523,19 +522,14 @@ class Preview extends React.Component {
|
||||||
justShared: true
|
justShared: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
handleUpdate (jsonData) {
|
handleUpdateProjectTitle (title) {
|
||||||
this.props.updateProject(
|
this.props.updateProject(
|
||||||
this.props.projectInfo.id,
|
this.props.projectInfo.id,
|
||||||
jsonData,
|
{title: title},
|
||||||
this.props.user.username,
|
this.props.user.username,
|
||||||
this.props.user.token
|
this.props.user.token
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
handleUpdateProjectTitle (title) {
|
|
||||||
this.handleUpdate({
|
|
||||||
title: title
|
|
||||||
});
|
|
||||||
}
|
|
||||||
handleSetLanguage (locale) {
|
handleSetLanguage (locale) {
|
||||||
jar.set('scratchlanguage', locale);
|
jar.set('scratchlanguage', locale);
|
||||||
}
|
}
|
||||||
|
@ -696,7 +690,6 @@ class Preview extends React.Component {
|
||||||
onShare={this.handleShare}
|
onShare={this.handleShare}
|
||||||
onToggleComments={this.handleToggleComments}
|
onToggleComments={this.handleToggleComments}
|
||||||
onToggleStudio={this.handleToggleStudio}
|
onToggleStudio={this.handleToggleStudio}
|
||||||
onUpdate={this.handleUpdate}
|
|
||||||
onUpdateProjectId={this.handleUpdateProjectId}
|
onUpdateProjectId={this.handleUpdateProjectId}
|
||||||
onUpdateProjectThumbnail={this.props.handleUpdateProjectThumbnail}
|
onUpdateProjectThumbnail={this.props.handleUpdateProjectThumbnail}
|
||||||
/>
|
/>
|
||||||
|
|
Loading…
Reference in a new issue