mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2025-02-17 00:21:20 -05:00
Show details about why a project is not public
Follow up the project info request with a request to the visibility endpoint to find out if the project is trashed or censored. The project just not being published is handled by the existing code. This PR generalizes the ShareBanner to a more generic "Banner" that is then filled with the relevant content.
This commit is contained in:
parent
af301ba450
commit
122160726d
9 changed files with 167 additions and 78 deletions
|
@ -8,6 +8,9 @@ $ui-orange: hsla(38, 100, 55, 1); // #FFAB19 Control Primary
|
|||
$ui-orange-10percent: hsla(35, 90, 55, .1);
|
||||
$ui-orange-25percent: hsla(35, 90, 55, .25);
|
||||
|
||||
$ui-red: hsla(20, 100%, 55%, 1); /* #FF661A */
|
||||
$ui-red-25percent: hsla(20, 100%, 55%, .25);
|
||||
|
||||
$ui-light-gray: hsla(0, 0, 98, 1); //#FAFAFA
|
||||
$ui-gray: hsla(0, 0, 95, 1); //#F2F2F2
|
||||
$ui-dark-gray: hsla(0, 0, 70, 1); //#B3B3B3
|
||||
|
|
|
@ -25,6 +25,7 @@ module.exports.getInitialState = () => ({
|
|||
report: module.exports.Status.NOT_FETCHED,
|
||||
projectStudios: module.exports.Status.NOT_FETCHED,
|
||||
curatedStudios: module.exports.Status.NOT_FETCHED,
|
||||
visibility: module.exports.Status.NOT_FETCHED,
|
||||
studioRequests: {}
|
||||
},
|
||||
projectInfo: {},
|
||||
|
@ -39,7 +40,8 @@ module.exports.getInitialState = () => ({
|
|||
curatedStudios: [],
|
||||
currentStudioIds: [],
|
||||
moreCommentsToLoad: false,
|
||||
projectNotAvailable: false
|
||||
projectNotAvailable: false,
|
||||
visibilityInfo: {}
|
||||
});
|
||||
|
||||
module.exports.previewReducer = (state, action) => {
|
||||
|
@ -169,6 +171,10 @@ module.exports.previewReducer = (state, action) => {
|
|||
return Object.assign({}, state, {
|
||||
moreCommentsToLoad: action.moreCommentsToLoad
|
||||
});
|
||||
case 'SET_VISIBILITY_INFO':
|
||||
return Object.assign({}, state, {
|
||||
visibilityInfo: action.visibilityInfo
|
||||
});
|
||||
case 'ERROR':
|
||||
log.error(action.error);
|
||||
return state;
|
||||
|
@ -321,7 +327,12 @@ module.exports.resetComments = () => ({
|
|||
type: 'RESET_COMMENTS'
|
||||
});
|
||||
|
||||
module.exports.getProjectInfo = (id, token) => (dispatch => {
|
||||
module.exports.setVisibilityInfo = visibilityInfo => ({
|
||||
type: 'SET_VISIBILITY_INFO',
|
||||
visibilityInfo: visibilityInfo
|
||||
});
|
||||
|
||||
module.exports.getProjectInfo = (id, username, token) => (dispatch => {
|
||||
const opts = {
|
||||
uri: `/projects/${id}`
|
||||
};
|
||||
|
@ -343,6 +354,27 @@ module.exports.getProjectInfo = (id, token) => (dispatch => {
|
|||
}
|
||||
dispatch(module.exports.setFetchStatus('project', module.exports.Status.FETCHED));
|
||||
dispatch(module.exports.setProjectInfo(body));
|
||||
|
||||
// If the project is not public, make a follow-up request for why
|
||||
if (username && !body.public) {
|
||||
dispatch(module.exports.getVisibilityInfo(id, username, token));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.getVisibilityInfo = (id, username, token) => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('visibility', module.exports.Status.FETCHING));
|
||||
api({
|
||||
uri: `/users/${username}/projects/${id}/visibility`,
|
||||
authentication: token
|
||||
}, (err, body, response) => {
|
||||
if (err || !body || response.statusCode !== 200) {
|
||||
dispatch(module.exports.setFetchStatus('visibility', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError('No visibility info available'));
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setFetchStatus('visibility', module.exports.Status.FETCHED));
|
||||
dispatch(module.exports.setVisibilityInfo(body));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
34
src/views/preview/banner.jsx
Normal file
34
src/views/preview/banner.jsx
Normal file
|
@ -0,0 +1,34 @@
|
|||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||
const Button = require('../../components/forms/button.jsx');
|
||||
|
||||
require('./banner.scss');
|
||||
|
||||
const Banner = ({className, message, actionMessage, onAction}) => (
|
||||
<div className={classNames('banner-outer', className)}>
|
||||
<FlexRow className="inner banner-inner">
|
||||
<span className="banner-text">
|
||||
{message}
|
||||
</span>
|
||||
{actionMessage && onAction && (
|
||||
<Button
|
||||
className="button banner-button"
|
||||
onClick={onAction}
|
||||
>
|
||||
{actionMessage}
|
||||
</Button>
|
||||
)}
|
||||
</FlexRow>
|
||||
</div>
|
||||
);
|
||||
|
||||
Banner.propTypes = {
|
||||
actionMessage: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
message: PropTypes.node.isRequired,
|
||||
onAction: PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = Banner;
|
36
src/views/preview/banner.scss
Normal file
36
src/views/preview/banner.scss
Normal file
|
@ -0,0 +1,36 @@
|
|||
@import "../../colors";
|
||||
|
||||
$navigation-height: 50px;
|
||||
|
||||
.banner-outer {
|
||||
background-color: $ui-orange-25percent;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
color: $ui-orange;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.banner-outer.banner-danger {
|
||||
background-color: $ui-red-25percent;
|
||||
color: $ui-red;
|
||||
}
|
||||
|
||||
.banner-inner {
|
||||
min-height: 60px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.banner-text {
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.banner-button {
|
||||
background-color: $ui-orange;
|
||||
font-size: .875rem;
|
||||
}
|
||||
|
||||
.banner-danger .banner-button {
|
||||
background-color: $ui-red;
|
||||
}
|
|
@ -20,5 +20,6 @@
|
|||
"project.inviteToRemix": "Invite user to remix",
|
||||
"project.instructionsLabel": "Instructions",
|
||||
"project.notesAndCreditsLabel": "Notes and Credits",
|
||||
"project.credit": "Thanks to {userLink} for the original project {projectLink}."
|
||||
"project.credit": "Thanks to {userLink} for the original project {projectLink}.",
|
||||
"project.deletedBanner": "Note: This project is in the trash folder"
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ const decorateText = require('../../lib/decorate-text.jsx');
|
|||
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||
const Button = require('../../components/forms/button.jsx');
|
||||
const Avatar = require('../../components/avatar/avatar.jsx');
|
||||
const ShareBanner = require('./share-banner.jsx');
|
||||
const Banner = require('./banner.jsx');
|
||||
const RemixCredit = require('./remix-credit.jsx');
|
||||
const RemixList = require('./remix-list.jsx');
|
||||
const Stats = require('./stats.jsx');
|
||||
|
@ -96,17 +96,50 @@ const PreviewPresentation = ({
|
|||
replies,
|
||||
reportOpen,
|
||||
singleCommentId,
|
||||
userOwnsProject
|
||||
userOwnsProject,
|
||||
visibilityInfo
|
||||
}) => {
|
||||
const shareDate = ((projectInfo.history && projectInfo.history.shared)) ? projectInfo.history.shared : '';
|
||||
|
||||
// Allow embedding html in banner messages coming from the server
|
||||
const embedCensorMessage = message => (
|
||||
// eslint-disable-next-line react/no-danger
|
||||
<span dangerouslySetInnerHTML={{__html: message}} />
|
||||
);
|
||||
|
||||
let banner;
|
||||
if (visibilityInfo.censored) {
|
||||
if (visibilityInfo.reshareable) {
|
||||
banner = (<Banner
|
||||
actionMessage={<FormattedMessage id="project.share.shareButton" />}
|
||||
className="banner-danger"
|
||||
message={embedCensorMessage(visibilityInfo.censorMessage)}
|
||||
onAction={onShare}
|
||||
/>);
|
||||
} else {
|
||||
banner = (<Banner
|
||||
className="banner-danger"
|
||||
message={embedCensorMessage(visibilityInfo.censorMessage)}
|
||||
/>);
|
||||
}
|
||||
} else if (visibilityInfo.deleted) {
|
||||
banner = (<Banner
|
||||
className="banner-danger"
|
||||
message={<FormattedMessage id="project.deletedBanner" />}
|
||||
/>);
|
||||
} else if (canShare && !isShared) {
|
||||
banner = (<Banner
|
||||
actionMessage={<FormattedMessage id="project.share.shareButton" />}
|
||||
message={<FormattedMessage id="project.share.notShared" />}
|
||||
onAction={onShare}
|
||||
/>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="preview">
|
||||
{canShare && !isShared && (
|
||||
<ShareBanner onShare={onShare} />
|
||||
)}
|
||||
{ projectInfo && projectInfo.author && projectInfo.author.id && (
|
||||
<React.Fragment>
|
||||
{banner}
|
||||
<div className="inner">
|
||||
<FlexRow className="preview-row force-row">
|
||||
<FlexRow className="project-header">
|
||||
|
@ -507,7 +540,13 @@ PreviewPresentation.propTypes = {
|
|||
replies: PropTypes.objectOf(PropTypes.array),
|
||||
reportOpen: PropTypes.bool,
|
||||
singleCommentId: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]),
|
||||
userOwnsProject: PropTypes.bool
|
||||
userOwnsProject: PropTypes.bool,
|
||||
visibilityInfo: PropTypes.shape({
|
||||
censored: PropTypes.bool,
|
||||
censorMessage: PropTypes.string,
|
||||
deleted: PropTypes.bool,
|
||||
reshareable: PropTypes.bool
|
||||
})
|
||||
};
|
||||
|
||||
module.exports = injectIntl(PreviewPresentation);
|
||||
|
|
|
@ -140,7 +140,7 @@ class Preview extends React.Component {
|
|||
this.props.getTopLevelComments(this.state.projectId, this.props.comments.length,
|
||||
this.props.isAdmin, token);
|
||||
}
|
||||
this.props.getProjectInfo(this.state.projectId, token);
|
||||
this.props.getProjectInfo(this.state.projectId, username, token);
|
||||
this.props.getRemixes(this.state.projectId, token);
|
||||
this.props.getProjectStudios(this.state.projectId, token);
|
||||
this.props.getCuratedStudios(username);
|
||||
|
@ -459,6 +459,7 @@ class Preview extends React.Component {
|
|||
reportOpen={this.state.reportOpen}
|
||||
singleCommentId={this.state.singleCommentId}
|
||||
userOwnsProject={this.props.userOwnsProject}
|
||||
visibilityInfo={this.props.visibilityInfo}
|
||||
onAddComment={this.handleAddComment}
|
||||
onAddToStudioClicked={this.handleAddToStudioClick}
|
||||
onAddToStudioClosed={this.handleAddToStudioClose}
|
||||
|
@ -597,7 +598,13 @@ Preview.propTypes = {
|
|||
classroomId: PropTypes.string
|
||||
}),
|
||||
userOwnsProject: PropTypes.bool,
|
||||
userPresent: PropTypes.bool
|
||||
userPresent: PropTypes.bool,
|
||||
visibilityInfo: PropTypes.shape({
|
||||
censored: PropTypes.bool,
|
||||
censorMessage: PropTypes.string,
|
||||
deleted: PropTypes.bool,
|
||||
reshareable: PropTypes.bool
|
||||
})
|
||||
};
|
||||
|
||||
Preview.defaultProps = {
|
||||
|
@ -665,7 +672,8 @@ const mapStateToProps = state => {
|
|||
sessionStatus: state.session.status, // check if used
|
||||
user: state.session.session.user,
|
||||
userOwnsProject: userOwnsProject,
|
||||
userPresent: userPresent
|
||||
userPresent: userPresent,
|
||||
visibilityInfo: state.preview.visibilityInfo
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -707,8 +715,8 @@ const mapDispatchToProps = dispatch => ({
|
|||
getParentInfo: id => {
|
||||
dispatch(previewActions.getParentInfo(id));
|
||||
},
|
||||
getProjectInfo: (id, token) => {
|
||||
dispatch(previewActions.getProjectInfo(id, token));
|
||||
getProjectInfo: (id, username, token) => {
|
||||
dispatch(previewActions.getProjectInfo(id, username, token));
|
||||
},
|
||||
getRemixes: id => {
|
||||
dispatch(previewActions.getRemixes(id));
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
const FormattedMessage = require('react-intl').FormattedMessage;
|
||||
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||
const Button = require('../../components/forms/button.jsx');
|
||||
|
||||
require('./share-banner.scss');
|
||||
|
||||
const ShareBanner = ({onShare}) => (
|
||||
<div className="share-banner-outer">
|
||||
<FlexRow className="inner share-banner">
|
||||
<span className="share-text">
|
||||
<FormattedMessage id="project.share.notShared" />
|
||||
</span>
|
||||
<Button
|
||||
className="button share-button"
|
||||
onClick={onShare}
|
||||
>
|
||||
<FormattedMessage id="project.share.shareButton" />
|
||||
</Button>
|
||||
</FlexRow>
|
||||
</div>
|
||||
);
|
||||
|
||||
ShareBanner.propTypes = {
|
||||
onShare: PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = ShareBanner;
|
|
@ -1,35 +0,0 @@
|
|||
@import "../../colors";
|
||||
|
||||
$navigation-height: 50px;
|
||||
|
||||
.share-banner-outer {
|
||||
background-color: $ui-orange-25percent;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
color: $ui-orange;
|
||||
}
|
||||
|
||||
.share-banner {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.share-button {
|
||||
background-color: $ui-orange;
|
||||
font-size: .875rem;
|
||||
font-weight: normal;
|
||||
|
||||
// don't show an image in share button, for now.
|
||||
// &:before {
|
||||
// display: inline-block;
|
||||
// margin-right: .5rem;
|
||||
// background-image: url("/svgs/project/share-white.svg");
|
||||
// background-repeat: no-repeat;
|
||||
// background-position: center center;
|
||||
// background-size: contain;
|
||||
// width: 1.25rem;
|
||||
// height: 1.25rem;
|
||||
// vertical-align: middle;
|
||||
// content: "";
|
||||
// }
|
||||
}
|
Loading…
Reference in a new issue