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-10percent: hsla(35, 90, 55, .1);
|
||||||
$ui-orange-25percent: hsla(35, 90, 55, .25);
|
$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-light-gray: hsla(0, 0, 98, 1); //#FAFAFA
|
||||||
$ui-gray: hsla(0, 0, 95, 1); //#F2F2F2
|
$ui-gray: hsla(0, 0, 95, 1); //#F2F2F2
|
||||||
$ui-dark-gray: hsla(0, 0, 70, 1); //#B3B3B3
|
$ui-dark-gray: hsla(0, 0, 70, 1); //#B3B3B3
|
||||||
|
|
|
@ -25,6 +25,7 @@ module.exports.getInitialState = () => ({
|
||||||
report: 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,
|
||||||
|
visibility: module.exports.Status.NOT_FETCHED,
|
||||||
studioRequests: {}
|
studioRequests: {}
|
||||||
},
|
},
|
||||||
projectInfo: {},
|
projectInfo: {},
|
||||||
|
@ -39,7 +40,8 @@ module.exports.getInitialState = () => ({
|
||||||
curatedStudios: [],
|
curatedStudios: [],
|
||||||
currentStudioIds: [],
|
currentStudioIds: [],
|
||||||
moreCommentsToLoad: false,
|
moreCommentsToLoad: false,
|
||||||
projectNotAvailable: false
|
projectNotAvailable: false,
|
||||||
|
visibilityInfo: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports.previewReducer = (state, action) => {
|
module.exports.previewReducer = (state, action) => {
|
||||||
|
@ -169,6 +171,10 @@ module.exports.previewReducer = (state, action) => {
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
moreCommentsToLoad: action.moreCommentsToLoad
|
moreCommentsToLoad: action.moreCommentsToLoad
|
||||||
});
|
});
|
||||||
|
case 'SET_VISIBILITY_INFO':
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
visibilityInfo: action.visibilityInfo
|
||||||
|
});
|
||||||
case 'ERROR':
|
case 'ERROR':
|
||||||
log.error(action.error);
|
log.error(action.error);
|
||||||
return state;
|
return state;
|
||||||
|
@ -321,6 +327,11 @@ module.exports.resetComments = () => ({
|
||||||
type: 'RESET_COMMENTS'
|
type: 'RESET_COMMENTS'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module.exports.setVisibilityInfo = visibilityInfo => ({
|
||||||
|
type: 'SET_VISIBILITY_INFO',
|
||||||
|
visibilityInfo: visibilityInfo
|
||||||
|
});
|
||||||
|
|
||||||
module.exports.getProjectInfo = (id, token) => (dispatch => {
|
module.exports.getProjectInfo = (id, token) => (dispatch => {
|
||||||
const opts = {
|
const opts = {
|
||||||
uri: `/projects/${id}`
|
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.setFetchStatus('project', module.exports.Status.FETCHED));
|
||||||
dispatch(module.exports.setProjectInfo(body));
|
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.inviteToRemix": "Invite user to remix",
|
||||||
"project.instructionsLabel": "Instructions",
|
"project.instructionsLabel": "Instructions",
|
||||||
"project.notesAndCreditsLabel": "Notes and Credits",
|
"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 FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||||
const Button = require('../../components/forms/button.jsx');
|
const Button = require('../../components/forms/button.jsx');
|
||||||
const Avatar = require('../../components/avatar/avatar.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 RemixCredit = require('./remix-credit.jsx');
|
||||||
const RemixList = require('./remix-list.jsx');
|
const RemixList = require('./remix-list.jsx');
|
||||||
const Stats = require('./stats.jsx');
|
const Stats = require('./stats.jsx');
|
||||||
|
@ -96,17 +96,50 @@ const PreviewPresentation = ({
|
||||||
replies,
|
replies,
|
||||||
reportOpen,
|
reportOpen,
|
||||||
singleCommentId,
|
singleCommentId,
|
||||||
userOwnsProject
|
userOwnsProject,
|
||||||
|
visibilityInfo
|
||||||
}) => {
|
}) => {
|
||||||
const shareDate = ((projectInfo.history && projectInfo.history.shared)) ? projectInfo.history.shared : '';
|
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 (
|
return (
|
||||||
<div className="preview">
|
<div className="preview">
|
||||||
{canShare && !isShared && (
|
|
||||||
<ShareBanner onShare={onShare} />
|
|
||||||
)}
|
|
||||||
{ projectInfo && projectInfo.author && projectInfo.author.id && (
|
{ projectInfo && projectInfo.author && projectInfo.author.id && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
{banner}
|
||||||
<div className="inner">
|
<div className="inner">
|
||||||
<FlexRow className="preview-row force-row">
|
<FlexRow className="preview-row force-row">
|
||||||
<FlexRow className="project-header">
|
<FlexRow className="project-header">
|
||||||
|
@ -507,7 +540,13 @@ PreviewPresentation.propTypes = {
|
||||||
replies: PropTypes.objectOf(PropTypes.array),
|
replies: PropTypes.objectOf(PropTypes.array),
|
||||||
reportOpen: PropTypes.bool,
|
reportOpen: PropTypes.bool,
|
||||||
singleCommentId: PropTypes.oneOfType([PropTypes.number, 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);
|
module.exports = injectIntl(PreviewPresentation);
|
||||||
|
|
|
@ -459,6 +459,7 @@ class Preview extends React.Component {
|
||||||
reportOpen={this.state.reportOpen}
|
reportOpen={this.state.reportOpen}
|
||||||
singleCommentId={this.state.singleCommentId}
|
singleCommentId={this.state.singleCommentId}
|
||||||
userOwnsProject={this.props.userOwnsProject}
|
userOwnsProject={this.props.userOwnsProject}
|
||||||
|
visibilityInfo={this.props.visibilityInfo}
|
||||||
onAddComment={this.handleAddComment}
|
onAddComment={this.handleAddComment}
|
||||||
onAddToStudioClicked={this.handleAddToStudioClick}
|
onAddToStudioClicked={this.handleAddToStudioClick}
|
||||||
onAddToStudioClosed={this.handleAddToStudioClose}
|
onAddToStudioClosed={this.handleAddToStudioClose}
|
||||||
|
@ -597,7 +598,13 @@ Preview.propTypes = {
|
||||||
classroomId: PropTypes.string
|
classroomId: PropTypes.string
|
||||||
}),
|
}),
|
||||||
userOwnsProject: PropTypes.bool,
|
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 = {
|
Preview.defaultProps = {
|
||||||
|
@ -665,7 +672,8 @@ const mapStateToProps = state => {
|
||||||
sessionStatus: state.session.status, // check if used
|
sessionStatus: state.session.status, // check if used
|
||||||
user: state.session.session.user,
|
user: state.session.session.user,
|
||||||
userOwnsProject: userOwnsProject,
|
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