mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-27 09:35:56 -05:00
Merge pull request #2015 from benjiwheeler/report_project_endpoint
report project POSTs to scratchr2, displays modal reactively
This commit is contained in:
commit
0114d3ea2f
9 changed files with 260 additions and 119 deletions
|
@ -28,8 +28,8 @@ module.exports.validationHOCFactory = defaultValidationErrors => (Component => {
|
||||||
<Component
|
<Component
|
||||||
validationErrors={defaults(
|
validationErrors={defaults(
|
||||||
{},
|
{},
|
||||||
defaultValidationErrors,
|
props.validationErrors,
|
||||||
props.validationErrors
|
defaultValidationErrors
|
||||||
)}
|
)}
|
||||||
{...omit(props, ['validationErrors'])}
|
{...omit(props, ['validationErrors'])}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -35,7 +35,6 @@
|
||||||
.addToStudio-modal-content {
|
.addToStudio-modal-content {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
line-height: 1.5rem;
|
|
||||||
font-size: .875rem;
|
font-size: .875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,11 +64,20 @@ $modal-close-size: 1rem;
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 1.125rem .8275rem .9375rem .8275rem;
|
margin: 1.125rem .8275rem .9375rem .8275rem;
|
||||||
|
line-height: 1.5rem;
|
||||||
justify-content: flex-end !important;
|
justify-content: flex-end !important;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* setting overall modal to contain overflow looks good, but isn't
|
||||||
|
compatible with elements (like validation popups) that need to bleed
|
||||||
|
past modal boundary. This class can be used to force modal button
|
||||||
|
row to appear to contain overflow. */
|
||||||
|
.action-buttons-overflow-fix {
|
||||||
|
margin-bottom: .9375rem;
|
||||||
|
}
|
||||||
|
|
||||||
.action-button {
|
.action-button {
|
||||||
margin: 0 0 0 .54625rem;
|
margin: 0 0 0 .54625rem;
|
||||||
border-radius: .25rem;
|
border-radius: .25rem;
|
||||||
|
@ -83,3 +92,19 @@ $modal-close-size: 1rem;
|
||||||
.action-button-text {
|
.action-button-text {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.action-button.disabled {
|
||||||
|
background-color: $active-dark-gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-text
|
||||||
|
{
|
||||||
|
display: block;
|
||||||
|
border: 1px solid $active-gray;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: $ui-orange;
|
||||||
|
padding: 1rem;
|
||||||
|
min-height: 1rem;
|
||||||
|
overflow: visible;
|
||||||
|
color: $type-white;
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
const bindAll = require('lodash.bindall');
|
const bindAll = require('lodash.bindall');
|
||||||
const PropTypes = require('prop-types');
|
const PropTypes = require('prop-types');
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
const connect = require('react-redux').connect;
|
||||||
const FormattedMessage = require('react-intl').FormattedMessage;
|
const FormattedMessage = require('react-intl').FormattedMessage;
|
||||||
const injectIntl = require('react-intl').injectIntl;
|
const injectIntl = require('react-intl').injectIntl;
|
||||||
const intlShape = require('react-intl').intlShape;
|
const intlShape = require('react-intl').intlShape;
|
||||||
const Modal = require('../base/modal.jsx');
|
const Modal = require('../base/modal.jsx');
|
||||||
|
const classNames = require('classnames');
|
||||||
|
|
||||||
const Form = require('../../forms/form.jsx');
|
const Form = require('../../forms/form.jsx');
|
||||||
const Button = require('../../forms/button.jsx');
|
const Button = require('../../forms/button.jsx');
|
||||||
|
@ -12,6 +14,7 @@ const Select = require('../../forms/select.jsx');
|
||||||
const Spinner = require('../../spinner/spinner.jsx');
|
const Spinner = require('../../spinner/spinner.jsx');
|
||||||
const TextArea = require('../../forms/textarea.jsx');
|
const TextArea = require('../../forms/textarea.jsx');
|
||||||
const FlexRow = require('../../flex-row/flex-row.jsx');
|
const FlexRow = require('../../flex-row/flex-row.jsx');
|
||||||
|
const previewActions = require('../../../redux/preview.js');
|
||||||
|
|
||||||
require('../../forms/button.scss');
|
require('../../forms/button.scss');
|
||||||
require('./modal.scss');
|
require('./modal.scss');
|
||||||
|
@ -68,12 +71,24 @@ class ReportModal extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props);
|
super(props);
|
||||||
bindAll(this, [
|
bindAll(this, [
|
||||||
'handleReportCategorySelect'
|
'handleCategorySelect',
|
||||||
|
'handleValid',
|
||||||
|
'handleInvalid'
|
||||||
]);
|
]);
|
||||||
this.state = {reportCategory: this.props.report.category};
|
this.state = {
|
||||||
|
category: '',
|
||||||
|
notes: '',
|
||||||
|
valid: false
|
||||||
|
};
|
||||||
}
|
}
|
||||||
handleReportCategorySelect (name, value) {
|
handleCategorySelect (name, value) {
|
||||||
this.setState({reportCategory: value});
|
this.setState({category: value});
|
||||||
|
}
|
||||||
|
handleValid () {
|
||||||
|
this.setState({valid: true});
|
||||||
|
}
|
||||||
|
handleInvalid () {
|
||||||
|
this.setState({valid: false});
|
||||||
}
|
}
|
||||||
lookupPrompt (value) {
|
lookupPrompt (value) {
|
||||||
const prompt = REPORT_OPTIONS.find(item => item.value === value).prompt;
|
const prompt = REPORT_OPTIONS.find(item => item.value === value).prompt;
|
||||||
|
@ -82,17 +97,24 @@ class ReportModal extends React.Component {
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
intl,
|
intl,
|
||||||
|
isConfirmed,
|
||||||
|
isError,
|
||||||
|
isOpen,
|
||||||
|
isWaiting,
|
||||||
onReport, // eslint-disable-line no-unused-vars
|
onReport, // eslint-disable-line no-unused-vars
|
||||||
report,
|
onRequestClose,
|
||||||
type,
|
type,
|
||||||
...modalProps
|
...modalProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
const submitEnabled = this.state.valid && !isWaiting;
|
||||||
|
const submitDisabledParam = submitEnabled ? {} : {disabled: 'disabled'};
|
||||||
const contentLabel = intl.formatMessage({id: `report.${type}`});
|
const contentLabel = intl.formatMessage({id: `report.${type}`});
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
className="mod-report"
|
className="mod-report"
|
||||||
contentLabel={contentLabel}
|
contentLabel={contentLabel}
|
||||||
isOpen={report.open}
|
isOpen={isOpen}
|
||||||
|
onRequestClose={onRequestClose}
|
||||||
{...modalProps}
|
{...modalProps}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
|
@ -104,11 +126,24 @@ class ReportModal extends React.Component {
|
||||||
|
|
||||||
<Form
|
<Form
|
||||||
className="report"
|
className="report"
|
||||||
onSubmit={onReport}
|
onInvalid={this.handleInvalid}
|
||||||
|
onValid={this.handleValid}
|
||||||
|
onValidSubmit={onReport}
|
||||||
>
|
>
|
||||||
<div className="report-modal-content">
|
<div className="report-modal-content">
|
||||||
|
{isConfirmed ? (
|
||||||
|
<div className="received">
|
||||||
|
<div className="received-header">
|
||||||
|
<FormattedMessage id="report.receivedHeader" />
|
||||||
|
</div>
|
||||||
|
<FormattedMessage id="report.receivedBody" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<div className="instructions">
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id={`report.${type}Instructions`}
|
id={`report.${type}Instructions`}
|
||||||
|
key={`report.${type}Instructions`}
|
||||||
values={{
|
values={{
|
||||||
CommunityGuidelinesLink: (
|
CommunityGuidelinesLink: (
|
||||||
<a href="/community_guidelines">
|
<a href="/community_guidelines">
|
||||||
|
@ -117,6 +152,7 @@ class ReportModal extends React.Component {
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<Select
|
<Select
|
||||||
required
|
required
|
||||||
elementWrapperClassName="report-modal-field"
|
elementWrapperClassName="report-modal-field"
|
||||||
|
@ -124,10 +160,16 @@ class ReportModal extends React.Component {
|
||||||
name="report_category"
|
name="report_category"
|
||||||
options={REPORT_OPTIONS.map(option => ({
|
options={REPORT_OPTIONS.map(option => ({
|
||||||
value: option.value,
|
value: option.value,
|
||||||
label: this.props.intl.formatMessage(option.label)
|
label: this.props.intl.formatMessage(option.label),
|
||||||
|
key: option.value
|
||||||
}))}
|
}))}
|
||||||
value={this.state.reportCategory}
|
validationErrors={{
|
||||||
onChange={this.handleReportCategorySelect}
|
isDefaultRequiredValue: this.props.intl.formatMessage({
|
||||||
|
id: 'report.reasonMissing'
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
value={this.state.category}
|
||||||
|
onChange={this.handleCategorySelect}
|
||||||
/>
|
/>
|
||||||
<TextArea
|
<TextArea
|
||||||
required
|
required
|
||||||
|
@ -135,8 +177,11 @@ class ReportModal extends React.Component {
|
||||||
elementWrapperClassName="report-modal-field"
|
elementWrapperClassName="report-modal-field"
|
||||||
label={null}
|
label={null}
|
||||||
name="notes"
|
name="notes"
|
||||||
placeholder={this.lookupPrompt(this.state.reportCategory)}
|
placeholder={this.lookupPrompt(this.state.category)}
|
||||||
validationErrors={{
|
validationErrors={{
|
||||||
|
isDefaultRequiredValue: this.props.intl.formatMessage({
|
||||||
|
id: 'report.textMissing'
|
||||||
|
}),
|
||||||
maxLength: this.props.intl.formatMessage({id: 'report.tooLongError'}),
|
maxLength: this.props.intl.formatMessage({id: 'report.tooLongError'}),
|
||||||
minLength: this.props.intl.formatMessage({id: 'report.tooShortError'})
|
minLength: this.props.intl.formatMessage({id: 'report.tooShortError'})
|
||||||
}}
|
}}
|
||||||
|
@ -144,28 +189,52 @@ class ReportModal extends React.Component {
|
||||||
maxLength: 500,
|
maxLength: 500,
|
||||||
minLength: 20
|
minLength: 20
|
||||||
}}
|
}}
|
||||||
value={report.notes}
|
value={this.state.notes}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
{isError && (
|
||||||
|
<div className="error-text">
|
||||||
|
<FormattedMessage id="report.error" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<FlexRow className="action-buttons">
|
<FlexRow className="action-buttons">
|
||||||
{report.waiting ? [
|
<div className="action-buttons-overflow-fix">
|
||||||
|
{isConfirmed ? (
|
||||||
<Button
|
<Button
|
||||||
className="submit-button"
|
className="action-button submit-button"
|
||||||
disabled="disabled"
|
type="button"
|
||||||
key="submitButton"
|
onClick={onRequestClose}
|
||||||
type="submit"
|
|
||||||
>
|
>
|
||||||
<Spinner />
|
<div className="action-button-text">
|
||||||
|
<FormattedMessage id="general.close" />
|
||||||
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
] : [
|
) : (
|
||||||
<Button
|
<Button
|
||||||
className="submit-button"
|
className={classNames(
|
||||||
|
'action-button',
|
||||||
|
'submit-button',
|
||||||
|
{disabled: !submitEnabled}
|
||||||
|
)}
|
||||||
|
{...submitDisabledParam}
|
||||||
key="submitButton"
|
key="submitButton"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
|
{isWaiting ? (
|
||||||
|
<div className="action-button-text">
|
||||||
|
<Spinner mode="smooth" />
|
||||||
|
<FormattedMessage id="report.sending" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="action-button-text">
|
||||||
<FormattedMessage id="report.send" />
|
<FormattedMessage id="report.send" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
]}
|
)}
|
||||||
|
</div>
|
||||||
</FlexRow>
|
</FlexRow>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -176,15 +245,26 @@ class ReportModal extends React.Component {
|
||||||
|
|
||||||
ReportModal.propTypes = {
|
ReportModal.propTypes = {
|
||||||
intl: intlShape,
|
intl: intlShape,
|
||||||
|
isConfirmed: PropTypes.bool,
|
||||||
|
isError: PropTypes.bool,
|
||||||
|
isOpen: PropTypes.bool,
|
||||||
|
isWaiting: PropTypes.bool,
|
||||||
onReport: PropTypes.func,
|
onReport: PropTypes.func,
|
||||||
onRequestClose: PropTypes.func,
|
onRequestClose: PropTypes.func,
|
||||||
report: PropTypes.shape({
|
|
||||||
category: PropTypes.string,
|
|
||||||
notes: PropTypes.string,
|
|
||||||
open: PropTypes.bool,
|
|
||||||
waiting: PropTypes.bool
|
|
||||||
}),
|
|
||||||
type: PropTypes.string
|
type: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = injectIntl(ReportModal);
|
const mapStateToProps = state => ({
|
||||||
|
isConfirmed: state.preview.status.report === previewActions.Status.FETCHED,
|
||||||
|
isError: state.preview.status.report === previewActions.Status.ERROR,
|
||||||
|
isWaiting: state.preview.status.report === previewActions.Status.FETCHING
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = () => ({});
|
||||||
|
|
||||||
|
const ConnectedReportModal = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ReportModal);
|
||||||
|
|
||||||
|
module.exports = injectIntl(ConnectedReportModal);
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
margin: 100px auto;
|
margin: 100px auto;
|
||||||
outline: none;
|
outline: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: 30rem;
|
width: 36.25rem; /* 580px; */
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,26 +34,45 @@
|
||||||
.report-modal-content {
|
.report-modal-content {
|
||||||
margin: 1rem auto;
|
margin: 1rem auto;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
line-height: 1.5rem;
|
|
||||||
font-size: .875rem;
|
font-size: .875rem;
|
||||||
|
|
||||||
|
.instructions {
|
||||||
|
line-height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.received {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 90%;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.65rem;
|
||||||
|
|
||||||
|
.received-header {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-text {
|
||||||
|
margin-top: .9375rem;
|
||||||
|
}
|
||||||
|
|
||||||
.validation-message {
|
.validation-message {
|
||||||
$arrow-border-width: 1rem;
|
$arrow-border-width: 1rem;
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 100%; /* position to the right of parent */
|
||||||
transform: translate(23.5rem, 0);
|
|
||||||
margin-left: $arrow-border-width;
|
margin-left: $arrow-border-width;
|
||||||
border: 1px solid $active-gray;
|
border: 1px solid $active-gray;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
background-color: $ui-orange;
|
background-color: $ui-orange;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
min-width: 12rem;
|
||||||
max-width: 18.75rem;
|
max-width: 18.75rem;
|
||||||
min-height: 1rem;
|
min-height: 1rem;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
color: $type-white;
|
color: $type-white;
|
||||||
|
|
||||||
|
/* arrow on box that points to the left */
|
||||||
&:before {
|
&:before {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -78,3 +97,13 @@
|
||||||
.report-modal-field {
|
.report-modal-field {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-group.has-error {
|
||||||
|
.textarea, select {
|
||||||
|
border: 1px solid $ui-orange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-text .textarea {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
|
@ -172,6 +172,7 @@
|
||||||
"registration.welcomeStepTitle": "Hurray! Welcome to Scratch!",
|
"registration.welcomeStepTitle": "Hurray! Welcome to Scratch!",
|
||||||
|
|
||||||
"thumbnail.by": "by",
|
"thumbnail.by": "by",
|
||||||
|
"report.error": "Something went wrong when trying to send your message. Please try again.",
|
||||||
"report.project": "Report Project",
|
"report.project": "Report Project",
|
||||||
"report.projectInstructions": "From the dropdown below, please select the reason why you feel this project is disrespectful or inappropriate or otherwise breaks the {CommunityGuidelinesLink}.",
|
"report.projectInstructions": "From the dropdown below, please select the reason why you feel this project is disrespectful or inappropriate or otherwise breaks the {CommunityGuidelinesLink}.",
|
||||||
"report.CommunityGuidelinesLinkText": "Scratch Community Guidelines",
|
"report.CommunityGuidelinesLinkText": "Scratch Community Guidelines",
|
||||||
|
@ -181,8 +182,11 @@
|
||||||
"report.reasonScary": "Too Violent or Scary",
|
"report.reasonScary": "Too Violent or Scary",
|
||||||
"report.reasonLanguage": "Inappropriate Language",
|
"report.reasonLanguage": "Inappropriate Language",
|
||||||
"report.reasonMusic": "Inappropriate Music",
|
"report.reasonMusic": "Inappropriate Music",
|
||||||
|
"report.reasonMissing": "Please select a reason",
|
||||||
"report.reasonImage": "Inappropriate Images",
|
"report.reasonImage": "Inappropriate Images",
|
||||||
"report.reasonPersonal": "Sharing Personal Contact Information",
|
"report.reasonPersonal": "Sharing Personal Contact Information",
|
||||||
|
"report.receivedHeader": "We have received your report!",
|
||||||
|
"report.receivedBody": "The Scratch Team will review the project based on the Scratch community guidelines.",
|
||||||
"report.promptPlaceholder": "Select a reason why above.",
|
"report.promptPlaceholder": "Select a reason why above.",
|
||||||
"report.promptCopy": "Please provide a link to the original project",
|
"report.promptCopy": "Please provide a link to the original project",
|
||||||
"report.promptUncredited": "Please provide links to the uncredited content",
|
"report.promptUncredited": "Please provide links to the uncredited content",
|
||||||
|
@ -194,5 +198,7 @@
|
||||||
"report.promptImage": "Please say the name of the sprite or the backdrop with the inappropriate image",
|
"report.promptImage": "Please say the name of the sprite or the backdrop with the inappropriate image",
|
||||||
"report.tooLongError": "That's too long! Please find a way to shorten your text.",
|
"report.tooLongError": "That's too long! Please find a way to shorten your text.",
|
||||||
"report.tooShortError": "That's too short. Please describe in detail what's inappropriate or disrespectful about the project.",
|
"report.tooShortError": "That's too short. Please describe in detail what's inappropriate or disrespectful about the project.",
|
||||||
"report.send": "Send"
|
"report.send": "Send",
|
||||||
|
"report.sending": "Sending...",
|
||||||
|
"report.textMissing": "Please tell us why you are reporting this project"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
const defaults = require('lodash.defaults');
|
||||||
const keyMirror = require('keymirror');
|
const keyMirror = require('keymirror');
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
const merge = require('lodash.merge');
|
const merge = require('lodash.merge');
|
||||||
|
@ -21,6 +22,7 @@ module.exports.getInitialState = () => ({
|
||||||
original: module.exports.Status.NOT_FETCHED,
|
original: module.exports.Status.NOT_FETCHED,
|
||||||
parent: module.exports.Status.NOT_FETCHED,
|
parent: module.exports.Status.NOT_FETCHED,
|
||||||
remixes: module.exports.Status.NOT_FETCHED,
|
remixes: module.exports.Status.NOT_FETCHED,
|
||||||
|
report: module.exports.Status.NOT_FETCHED,
|
||||||
projectStudios: module.exports.Status.NOT_FETCHED,
|
projectStudios: module.exports.Status.NOT_FETCHED,
|
||||||
curatedStudios: module.exports.Status.NOT_FETCHED,
|
curatedStudios: module.exports.Status.NOT_FETCHED,
|
||||||
studioRequests: {}
|
studioRequests: {}
|
||||||
|
@ -324,6 +326,7 @@ module.exports.getReplies = (projectId, commentIds) => (dispatch => {
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports.setFavedStatus = (faved, id, username, token) => (dispatch => {
|
module.exports.setFavedStatus = (faved, id, username, token) => (dispatch => {
|
||||||
|
dispatch(module.exports.setFetchStatus('faved', module.exports.Status.FETCHING));
|
||||||
if (faved) {
|
if (faved) {
|
||||||
api({
|
api({
|
||||||
uri: `/projects/${id}/favorites/user/${username}`,
|
uri: `/projects/${id}/favorites/user/${username}`,
|
||||||
|
@ -383,6 +386,7 @@ module.exports.getLovedStatus = (id, username, token) => (dispatch => {
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports.setLovedStatus = (loved, id, username, token) => (dispatch => {
|
module.exports.setLovedStatus = (loved, id, username, token) => (dispatch => {
|
||||||
|
dispatch(module.exports.setFetchStatus('loved', module.exports.Status.FETCHING));
|
||||||
if (loved) {
|
if (loved) {
|
||||||
api({
|
api({
|
||||||
uri: `/projects/${id}/loves/user/${username}`,
|
uri: `/projects/${id}/loves/user/${username}`,
|
||||||
|
@ -531,6 +535,7 @@ module.exports.leaveStudio = (studioId, projectId, token) => (dispatch => {
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports.updateProject = (id, jsonData, username, token) => (dispatch => {
|
module.exports.updateProject = (id, jsonData, username, token) => (dispatch => {
|
||||||
|
dispatch(module.exports.setFetchStatus('project', module.exports.Status.FETCHING));
|
||||||
api({
|
api({
|
||||||
uri: `/projects/${id}`,
|
uri: `/projects/${id}`,
|
||||||
authentication: token,
|
authentication: token,
|
||||||
|
@ -556,3 +561,27 @@ module.exports.updateProject = (id, jsonData, username, token) => (dispatch => {
|
||||||
dispatch(module.exports.setProjectInfo(body));
|
dispatch(module.exports.setProjectInfo(body));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module.exports.reportProject = (id, jsonData) => (dispatch => {
|
||||||
|
dispatch(module.exports.setFetchStatus('report', module.exports.Status.FETCHING));
|
||||||
|
// scratchr2 will fail if no thumbnail base64 string provided. We don't yet have
|
||||||
|
// a way to get the actual project thumbnail in www/gui, so for now just submit
|
||||||
|
// a minimal base64 png string.
|
||||||
|
defaults(jsonData, {
|
||||||
|
thumbnail: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC' +
|
||||||
|
'0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII='
|
||||||
|
});
|
||||||
|
api({
|
||||||
|
host: '',
|
||||||
|
uri: `/site-api/projects/all/${id}/report/`,
|
||||||
|
method: 'POST',
|
||||||
|
json: jsonData,
|
||||||
|
useCsrf: true
|
||||||
|
}, (err, body, res) => {
|
||||||
|
if (err || res.statusCode !== 200) {
|
||||||
|
dispatch(module.exports.setFetchStatus('report', module.exports.Status.ERROR));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(module.exports.setFetchStatus('report', module.exports.Status.FETCHED));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -43,7 +43,7 @@ const PreviewPresentation = ({
|
||||||
projectId,
|
projectId,
|
||||||
projectInfo,
|
projectInfo,
|
||||||
remixes,
|
remixes,
|
||||||
report,
|
reportOpen,
|
||||||
replies,
|
replies,
|
||||||
addToStudioOpen,
|
addToStudioOpen,
|
||||||
projectStudios,
|
projectStudios,
|
||||||
|
@ -278,8 +278,8 @@ const PreviewPresentation = ({
|
||||||
Report
|
Report
|
||||||
</Button>,
|
</Button>,
|
||||||
<ReportModal
|
<ReportModal
|
||||||
|
isOpen={reportOpen}
|
||||||
key="report-modal"
|
key="report-modal"
|
||||||
report={report}
|
|
||||||
type="project"
|
type="project"
|
||||||
onReport={onReportSubmit}
|
onReport={onReportSubmit}
|
||||||
onRequestClose={onReportClose}
|
onRequestClose={onReportClose}
|
||||||
|
@ -377,12 +377,7 @@ PreviewPresentation.propTypes = {
|
||||||
projectStudios: PropTypes.arrayOf(PropTypes.object),
|
projectStudios: PropTypes.arrayOf(PropTypes.object),
|
||||||
remixes: PropTypes.arrayOf(PropTypes.object),
|
remixes: PropTypes.arrayOf(PropTypes.object),
|
||||||
replies: PropTypes.objectOf(PropTypes.array),
|
replies: PropTypes.objectOf(PropTypes.array),
|
||||||
report: PropTypes.shape({
|
reportOpen: PropTypes.bool,
|
||||||
category: PropTypes.string,
|
|
||||||
notes: PropTypes.string,
|
|
||||||
open: PropTypes.bool,
|
|
||||||
waiting: PropTypes.bool
|
|
||||||
}),
|
|
||||||
studios: PropTypes.arrayOf(PropTypes.object),
|
studios: PropTypes.arrayOf(PropTypes.object),
|
||||||
userOwnsProject: PropTypes.bool
|
userOwnsProject: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
|
@ -57,12 +57,7 @@ class Preview extends React.Component {
|
||||||
loveCount: 0,
|
loveCount: 0,
|
||||||
projectId: parts[1] === 'editor' ? 0 : parts[1],
|
projectId: parts[1] === 'editor' ? 0 : parts[1],
|
||||||
addToStudioOpen: false,
|
addToStudioOpen: false,
|
||||||
report: {
|
reportOpen: false
|
||||||
category: '',
|
|
||||||
notes: '',
|
|
||||||
open: false,
|
|
||||||
waiting: false
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
this.getExtensions(this.state.projectId);
|
this.getExtensions(this.state.projectId);
|
||||||
this.addEventListeners();
|
this.addEventListeners();
|
||||||
|
@ -87,7 +82,6 @@ class Preview extends React.Component {
|
||||||
this.props.getRemixes(this.state.projectId);
|
this.props.getRemixes(this.state.projectId);
|
||||||
this.props.getProjectStudios(this.state.projectId);
|
this.props.getProjectStudios(this.state.projectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if (this.props.projectInfo.id !== prevProps.projectInfo.id) {
|
if (this.props.projectInfo.id !== prevProps.projectInfo.id) {
|
||||||
this.getExtensions(this.state.projectId);
|
this.getExtensions(this.state.projectId);
|
||||||
|
@ -118,7 +112,7 @@ class Preview extends React.Component {
|
||||||
getExtensions (projectId) {
|
getExtensions (projectId) {
|
||||||
storage
|
storage
|
||||||
.load(storage.AssetType.Project, projectId, storage.DataFormat.JSON)
|
.load(storage.AssetType.Project, projectId, storage.DataFormat.JSON)
|
||||||
.then(projectAsset => {
|
.then(projectAsset => { // NOTE: this is turning up null, breaking the line below.
|
||||||
let input = projectAsset.data;
|
let input = projectAsset.data;
|
||||||
if (typeof input === 'object' && !(input instanceof ArrayBuffer) &&
|
if (typeof input === 'object' && !(input instanceof ArrayBuffer) &&
|
||||||
!ArrayBuffer.isView(input)) { // taken from scratch-vm
|
!ArrayBuffer.isView(input)) { // taken from scratch-vm
|
||||||
|
@ -148,10 +142,10 @@ class Preview extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
handleReportClick () {
|
handleReportClick () {
|
||||||
this.setState({report: {...this.state.report, open: true}});
|
this.setState({reportOpen: true});
|
||||||
}
|
}
|
||||||
handleReportClose () {
|
handleReportClose () {
|
||||||
this.setState({report: {...this.state.report, open: false}});
|
this.setState({reportOpen: false});
|
||||||
}
|
}
|
||||||
handleAddToStudioClick () {
|
handleAddToStudioClick () {
|
||||||
this.setState({addToStudioOpen: true});
|
this.setState({addToStudioOpen: true});
|
||||||
|
@ -159,27 +153,8 @@ class Preview extends React.Component {
|
||||||
handleAddToStudioClose () {
|
handleAddToStudioClose () {
|
||||||
this.setState({addToStudioOpen: false});
|
this.setState({addToStudioOpen: false});
|
||||||
}
|
}
|
||||||
// NOTE: this is a copy, change it
|
|
||||||
handleReportSubmit (formData) {
|
handleReportSubmit (formData) {
|
||||||
this.setState({report: {
|
this.props.reportProject(this.state.projectId, formData);
|
||||||
category: formData.report_category,
|
|
||||||
notes: formData.notes,
|
|
||||||
open: this.state.report.open,
|
|
||||||
waiting: true}
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
...formData,
|
|
||||||
id: this.state.projectId,
|
|
||||||
user: this.props.user.username
|
|
||||||
};
|
|
||||||
console.log('submit report data', data); // eslint-disable-line no-console
|
|
||||||
this.setState({report: {
|
|
||||||
category: '',
|
|
||||||
notes: '',
|
|
||||||
open: false,
|
|
||||||
waiting: false}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
handlePopState () {
|
handlePopState () {
|
||||||
const path = window.location.pathname.toLowerCase();
|
const path = window.location.pathname.toLowerCase();
|
||||||
|
@ -335,7 +310,7 @@ class Preview extends React.Component {
|
||||||
projectStudios={this.props.projectStudios}
|
projectStudios={this.props.projectStudios}
|
||||||
remixes={this.props.remixes}
|
remixes={this.props.remixes}
|
||||||
replies={this.props.replies}
|
replies={this.props.replies}
|
||||||
report={this.state.report}
|
reportOpen={this.state.reportOpen}
|
||||||
studios={this.props.studios}
|
studios={this.props.studios}
|
||||||
user={this.props.user}
|
user={this.props.user}
|
||||||
userOwnsProject={this.userOwnsProject()}
|
userOwnsProject={this.userOwnsProject()}
|
||||||
|
@ -383,6 +358,7 @@ Preview.propTypes = {
|
||||||
projectStudios: PropTypes.arrayOf(PropTypes.object),
|
projectStudios: PropTypes.arrayOf(PropTypes.object),
|
||||||
remixes: PropTypes.arrayOf(PropTypes.object),
|
remixes: PropTypes.arrayOf(PropTypes.object),
|
||||||
replies: PropTypes.objectOf(PropTypes.array),
|
replies: PropTypes.objectOf(PropTypes.array),
|
||||||
|
reportProject: PropTypes.func,
|
||||||
sessionStatus: PropTypes.string,
|
sessionStatus: PropTypes.string,
|
||||||
setFavedStatus: PropTypes.func.isRequired,
|
setFavedStatus: PropTypes.func.isRequired,
|
||||||
setFullScreen: PropTypes.func.isRequired,
|
setFullScreen: PropTypes.func.isRequired,
|
||||||
|
@ -461,7 +437,6 @@ const mapStateToProps = state => ({
|
||||||
fullScreen: state.scratchGui.mode.isFullScreen
|
fullScreen: state.scratchGui.mode.isFullScreen
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
getOriginalInfo: id => {
|
getOriginalInfo: id => {
|
||||||
dispatch(previewActions.getOriginalInfo(id));
|
dispatch(previewActions.getOriginalInfo(id));
|
||||||
|
@ -506,6 +481,9 @@ const mapDispatchToProps = dispatch => ({
|
||||||
refreshSession: () => {
|
refreshSession: () => {
|
||||||
dispatch(sessionActions.refreshSession());
|
dispatch(sessionActions.refreshSession());
|
||||||
},
|
},
|
||||||
|
reportProject: (id, formData) => {
|
||||||
|
dispatch(previewActions.reportProject(id, formData));
|
||||||
|
},
|
||||||
setOriginalInfo: info => {
|
setOriginalInfo: info => {
|
||||||
dispatch(previewActions.setOriginalInfo(info));
|
dispatch(previewActions.setOriginalInfo(info));
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue