diff --git a/src/_colors.scss b/src/_colors.scss index 33c5560c3..08e4cfcae 100644 --- a/src/_colors.scss +++ b/src/_colors.scss @@ -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 diff --git a/src/redux/preview.js b/src/redux/preview.js index 9216209fb..fccdc0830 100644 --- a/src/redux/preview.js +++ b/src/redux/preview.js @@ -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)); }); }); diff --git a/src/views/preview/banner.jsx b/src/views/preview/banner.jsx new file mode 100644 index 000000000..c3ab9bcc6 --- /dev/null +++ b/src/views/preview/banner.jsx @@ -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}) => ( +
+ + + {message} + + {actionMessage && onAction && ( + + )} + +
+); + +Banner.propTypes = { + actionMessage: PropTypes.node, + className: PropTypes.string, + message: PropTypes.node.isRequired, + onAction: PropTypes.func +}; + +module.exports = Banner; diff --git a/src/views/preview/banner.scss b/src/views/preview/banner.scss new file mode 100644 index 000000000..8f753cabd --- /dev/null +++ b/src/views/preview/banner.scss @@ -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; +} diff --git a/src/views/preview/l10n.json b/src/views/preview/l10n.json index e237c2b44..7f0e429e1 100644 --- a/src/views/preview/l10n.json +++ b/src/views/preview/l10n.json @@ -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" } diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index 30b1eb1f5..02a57b255 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -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 + + ); + + let banner; + if (visibilityInfo.deleted) { // If both censored and deleted, prioritize deleted banner + banner = (} + />); + } else if (visibilityInfo.censored) { + if (visibilityInfo.reshareable) { + banner = (} + className="banner-danger" + message={embedCensorMessage(visibilityInfo.censorMessage)} + onAction={onShare} + />); + } else { + banner = (); + } + } else if (canShare && !isShared) { + banner = (} + message={} + onAction={onShare} + />); + } + return (
- {canShare && !isShared && ( - - )} { projectInfo && projectInfo.author && projectInfo.author.id && ( + {banner}
@@ -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); diff --git a/src/views/preview/preview.jsx b/src/views/preview/preview.jsx index 47ae5ae6e..a8740f7cf 100644 --- a/src/views/preview/preview.jsx +++ b/src/views/preview/preview.jsx @@ -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 }; }; diff --git a/src/views/preview/share-banner.jsx b/src/views/preview/share-banner.jsx deleted file mode 100644 index 725cbcf19..000000000 --- a/src/views/preview/share-banner.jsx +++ /dev/null @@ -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}) => ( -
- - - - - - -
-); - -ShareBanner.propTypes = { - onShare: PropTypes.func -}; - -module.exports = ShareBanner; diff --git a/src/views/preview/share-banner.scss b/src/views/preview/share-banner.scss deleted file mode 100644 index 0524699f4..000000000 --- a/src/views/preview/share-banner.scss +++ /dev/null @@ -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: ""; - // } -}