mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-23 07:38:07 -05:00
Merge pull request #2340 from paulkaplan/visibility-info
Show details about why a project is not public
This commit is contained in:
commit
3fb5dd769a
9 changed files with 163 additions and 74 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,6 +327,11 @@ module.exports.resetComments = () => ({
|
|||
type: 'RESET_COMMENTS'
|
||||
});
|
||||
|
||||
module.exports.setVisibilityInfo = visibilityInfo => ({
|
||||
type: 'SET_VISIBILITY_INFO',
|
||||
visibilityInfo: visibilityInfo
|
||||
});
|
||||
|
||||
module.exports.getProjectInfo = (id, 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 (!body.public) {
|
||||
dispatch(module.exports.getVisibilityInfo(id, body.author.username, token));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.getVisibilityInfo = (id, ownerUsername, token) => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('visibility', module.exports.Status.FETCHING));
|
||||
api({
|
||||
uri: `/users/${ownerUsername}/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: .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.deleted) { // If both censored and deleted, prioritize deleted banner
|
||||
banner = (<Banner
|
||||
className="banner-danger"
|
||||
message={<FormattedMessage id="project.deletedBanner" />}
|
||||
/>);
|
||||
} else 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 (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);
|
||||
|
|
|
@ -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
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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