mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2025-02-17 00:21:20 -05:00
Merge branch 'develop' into responsive-project-page/container-width
This commit is contained in:
commit
502ead072f
17 changed files with 980 additions and 642 deletions
|
@ -1,52 +1,46 @@
|
|||
@import "../../../colors";
|
||||
@import "../../../frameless";
|
||||
|
||||
.mod-addToStudio * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
$small: "only screen and (max-width : #{$mobile})";
|
||||
$medium: "only screen and (min-width : #{$mobile}+1) and (max-width : #{$tablet}-1)";
|
||||
$small-height: "only screen and (max-height : #{$mobile})";
|
||||
$medium-height: "only screen and (min-height : #{$mobile} + 1) and (max-height : #{$tablet} - 1)";
|
||||
|
||||
.mod-addToStudio {
|
||||
margin: 100px auto;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
width: 36.25rem; /* 580px; */
|
||||
height: 388px; /* 24.25rem; */
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
|
||||
@media #{$small}, #{$small-height} {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.addToStudio-modal-header {
|
||||
box-shadow: inset 0 -1px 0 0 $ui-blue-dark;
|
||||
background-color: $ui-blue;
|
||||
padding-top: .75rem;
|
||||
width: 100%;
|
||||
height: 3rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.addToStudio-content-label {
|
||||
text-align: center;
|
||||
color: $type-white;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.addToStudio-modal-content {
|
||||
margin: 0 auto;
|
||||
box-shadow: none;
|
||||
width: 100%;
|
||||
font-size: .875rem;
|
||||
}
|
||||
|
||||
.studio-list-outer-scrollbox {
|
||||
position: relative;
|
||||
background-color: $ui-blue-10percent;
|
||||
min-height: 15rem;
|
||||
max-height: calc(100% - 8rem);
|
||||
flex: 1;
|
||||
|
||||
@media #{$small-height} {
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.studio-list-inner-scrollbox {
|
||||
margin-right: .5rem;
|
||||
padding-right: .5rem;
|
||||
height: 16.9375rem;
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
overflow-x: hidden;
|
||||
|
||||
|
@ -93,35 +87,32 @@
|
|||
display: flex;
|
||||
position: relative;
|
||||
transition: all .5s;
|
||||
margin: .21875rem .21875rem;
|
||||
margin: .21875rem;
|
||||
border-radius: .5rem;
|
||||
background-color: $ui-white;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 16.1875rem; /* 259px */
|
||||
width: 48%;
|
||||
height: 2.5rem;
|
||||
box-sizing: border-box;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
@media #{$small} {
|
||||
min-width: 98%;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.studio-selector-button-text {
|
||||
position: absolute;
|
||||
/* per spec, should be:
|
||||
margin: .375rem 2.18375rem .375rem .6875rem
|
||||
but in practice, our css seems to vertically align text to top, where
|
||||
invision spec aligned to middle.
|
||||
*/
|
||||
margin: .575rem 2.18375rem .175rem .6875rem;
|
||||
width: 13.3125rem;
|
||||
height: 1.25rem; /* diff from spec, in case we ever do valign to middle; changed to match line-height because else with overflow hidden it cuts off some letters */
|
||||
margin: auto 2.18375rem auto .6875rem;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.25rem;
|
||||
white-space: nowrap;
|
||||
font-family: "Helvetica Neue";
|
||||
font-size: .875rem;
|
||||
font-weight: regular;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.studio-selector-button-selected {
|
||||
|
@ -144,7 +135,7 @@
|
|||
|
||||
.studio-status-icon {
|
||||
position: absolute;
|
||||
margin: .5rem .625rem .5rem 14.0625rem;
|
||||
right: .625rem;
|
||||
border-radius: .75rem;
|
||||
padding: .0625rem .075rem;
|
||||
width: 1.5rem;
|
||||
|
|
|
@ -37,70 +37,69 @@ const AddToStudioModalPresentation = ({
|
|||
|
||||
return (
|
||||
<Modal
|
||||
useStandardSizes
|
||||
className="mod-addToStudio"
|
||||
contentLabel={contentLabel}
|
||||
isOpen={isOpen}
|
||||
onRequestClose={onRequestClose}
|
||||
>
|
||||
<div>
|
||||
<div className="addToStudio-modal-header">
|
||||
<div className="addToStudio-content-label">
|
||||
{contentLabel}
|
||||
<div className="addToStudio-modal-header modal-header">
|
||||
<div className="addToStudio-content-label content-label">
|
||||
{contentLabel}
|
||||
</div>
|
||||
</div>
|
||||
<div className="addToStudio-modal-content modal-content">
|
||||
<div className="studio-list-outer-scrollbox">
|
||||
<div className="studio-list-inner-scrollbox">
|
||||
<div className="studio-list-container">
|
||||
{studioButtons}
|
||||
</div>
|
||||
<div className="studio-list-bottom-gradient" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="addToStudio-modal-content">
|
||||
<div className="studio-list-outer-scrollbox">
|
||||
<div className="studio-list-inner-scrollbox">
|
||||
<div className="studio-list-container">
|
||||
{studioButtons}
|
||||
|
||||
|
||||
<Form
|
||||
className="add-to-studio"
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<FlexRow className="action-buttons">
|
||||
<Button
|
||||
className="action-button close-button white"
|
||||
key="closeButton"
|
||||
name="closeButton"
|
||||
type="button"
|
||||
onClick={onRequestClose}
|
||||
>
|
||||
<div className="action-button-text">
|
||||
<FormattedMessage id="general.close" />
|
||||
</div>
|
||||
<div className="studio-list-bottom-gradient" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<Form
|
||||
className="add-to-studio"
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<FlexRow className="action-buttons">
|
||||
</Button>
|
||||
{waitingToClose ? [
|
||||
<Button
|
||||
className="action-button close-button white"
|
||||
key="closeButton"
|
||||
name="closeButton"
|
||||
type="button"
|
||||
onClick={onRequestClose}
|
||||
className="action-button submit-button submit-button-waiting"
|
||||
disabled="disabled"
|
||||
key="submitButton"
|
||||
type="submit"
|
||||
>
|
||||
<div className="action-button-text">
|
||||
<FormattedMessage id="general.close" />
|
||||
<Spinner />
|
||||
<FormattedMessage id="addToStudio.finishing" />
|
||||
</div>
|
||||
</Button>
|
||||
{waitingToClose ? [
|
||||
<Button
|
||||
className="action-button submit-button submit-button-waiting"
|
||||
disabled="disabled"
|
||||
key="submitButton"
|
||||
type="submit"
|
||||
>
|
||||
<div className="action-button-text">
|
||||
<Spinner />
|
||||
<FormattedMessage id="addToStudio.finishing" />
|
||||
</div>
|
||||
</Button>
|
||||
] : [
|
||||
<Button
|
||||
className="action-button submit-button"
|
||||
key="submitButton"
|
||||
type="submit"
|
||||
>
|
||||
<div className="action-button-text">
|
||||
<FormattedMessage id="general.okay" />
|
||||
</div>
|
||||
</Button>
|
||||
]}
|
||||
</FlexRow>
|
||||
</Form>
|
||||
</div>
|
||||
] : [
|
||||
<Button
|
||||
className="action-button submit-button"
|
||||
key="submitButton"
|
||||
type="submit"
|
||||
>
|
||||
<div className="action-button-text">
|
||||
<FormattedMessage id="general.okay" />
|
||||
</div>
|
||||
</Button>
|
||||
]}
|
||||
</FlexRow>
|
||||
</Form>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
@ -26,8 +26,11 @@ class Modal extends React.Component {
|
|||
return (
|
||||
<ReactModal
|
||||
appElement={document.getElementById('app')}
|
||||
bodyOpenClassName={this.props.useStandardSizes ? classNames('overflow-hidden') : null}
|
||||
className={{
|
||||
base: classNames('modal-content', this.props.className),
|
||||
base: classNames('modal-content', this.props.className, {
|
||||
'modal-sizes': this.props.useStandardSizes
|
||||
}),
|
||||
afterOpen: classNames('modal-content', this.props.className),
|
||||
beforeClose: classNames('modal-content', this.props.className)
|
||||
}}
|
||||
|
@ -60,7 +63,8 @@ class Modal extends React.Component {
|
|||
Modal.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
overlayClassName: PropTypes.string
|
||||
overlayClassName: PropTypes.string,
|
||||
useStandardSizes: PropTypes.bool
|
||||
};
|
||||
|
||||
module.exports = Modal;
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
@import "../../../colors";
|
||||
@import "../../../frameless";
|
||||
|
||||
$small: "only screen and (max-width : #{$mobile})";
|
||||
$medium: "only screen and (min-width : #{$mobile}+1) and (max-width : #{$tablet}-1)";
|
||||
$big: "only screen and (min-width : #{$tablet}) and (max-width : #{$desktop}-1)";
|
||||
$small-height: "only screen and (max-height : #{$mobile})";
|
||||
$medium-height: "only screen and (min-height : #{$mobile} + 1) and (max-height : #{$tablet} - 1)";
|
||||
|
||||
.overflow-hidden {
|
||||
/* to avoid double scroll bars this
|
||||
gets added to body while modal is open */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
position: relative;
|
||||
margin: 3.75rem auto;
|
||||
|
@ -10,9 +22,27 @@
|
|||
padding: 0;
|
||||
width: 48.75rem;
|
||||
|
||||
.modal-content { /* content inside of content */
|
||||
display: flex;
|
||||
border-radius: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@media #{$small}, #{$medium}, #{$big} {
|
||||
margin-top: 0;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media #{$small}, #{$small-height} {
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
|
@ -43,23 +73,6 @@ $modal-close-size: 1rem;
|
|||
padding-top: $modal-close-size / 2;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $desktop - 1) {
|
||||
.modal-content {
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin-top: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.modal-content-close {
|
||||
position: fixed;
|
||||
}
|
||||
}
|
||||
|
||||
/* Close button, Submit button, etc. */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
|
@ -68,6 +81,11 @@ $modal-close-size: 1rem;
|
|||
justify-content: flex-end !important;
|
||||
align-items: flex-start;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
@media #{$small}, #{$medium}, #{$big} {
|
||||
justify-content: center !important; //overwriting flex row properties
|
||||
flex-direction: row !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* setting overall modal to contain overflow looks good, but isn't
|
||||
|
@ -108,3 +126,46 @@ row to appear to contain overflow. */
|
|||
overflow: visible;
|
||||
color: $type-white;
|
||||
}
|
||||
|
||||
.modal-sizes * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.modal-sizes {
|
||||
margin: 100px auto;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
max-width: 36.25rem; /* 580px; */
|
||||
user-select: none;
|
||||
|
||||
@media #{$medium}, #{$medium-height} {
|
||||
margin: 40px auto;
|
||||
}
|
||||
|
||||
@media #{$small}, #{$small-height} {
|
||||
margin: 0 auto;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding-top: .75rem;
|
||||
width: 100%;
|
||||
height: 3rem;
|
||||
|
||||
@media #{$small}, #{$small-height} {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.content-label {
|
||||
text-align: center;
|
||||
color: $type-white;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
font-size: .875rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,6 +111,7 @@ class ReportModal extends React.Component {
|
|||
const contentLabel = intl.formatMessage({id: `report.${type}`});
|
||||
return (
|
||||
<Modal
|
||||
useStandardSizes
|
||||
className="mod-report"
|
||||
contentLabel={contentLabel}
|
||||
isOpen={isOpen}
|
||||
|
@ -118,8 +119,8 @@ class ReportModal extends React.Component {
|
|||
{...modalProps}
|
||||
>
|
||||
<div>
|
||||
<div className="report-modal-header">
|
||||
<div className="report-content-label">
|
||||
<div className="report-modal-header modal-header">
|
||||
<div className="report-content-label content-label">
|
||||
{contentLabel}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -130,7 +131,7 @@ class ReportModal extends React.Component {
|
|||
onValid={this.handleValid}
|
||||
onValidSubmit={onReport}
|
||||
>
|
||||
<div className="report-modal-content">
|
||||
<div className="report-modal-content modal-content">
|
||||
{isConfirmed ? (
|
||||
<div className="received">
|
||||
<div className="received-header">
|
||||
|
|
|
@ -1,40 +1,16 @@
|
|||
@import "../../../colors";
|
||||
@import "../../../frameless";
|
||||
|
||||
.mod-report * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.mod-report {
|
||||
margin: 100px auto;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
width: 36.25rem; /* 580px; */
|
||||
user-select: none;
|
||||
}
|
||||
$medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||
|
||||
.report-modal-header {
|
||||
border-radius: 1rem 1rem 0 0;
|
||||
box-shadow: inset 0 -1px 0 0 $ui-coral-dark;
|
||||
background-color: $ui-coral;
|
||||
padding-top: .75rem;
|
||||
width: 100%;
|
||||
height: 3rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.report-content-label {
|
||||
text-align: center;
|
||||
color: $type-white;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.report-modal-content {
|
||||
margin: 1rem auto;
|
||||
width: 80%;
|
||||
font-size: .875rem;
|
||||
|
||||
.instructions {
|
||||
line-height: 1.5rem;
|
||||
|
@ -58,28 +34,33 @@
|
|||
.validation-message {
|
||||
$arrow-border-width: 1rem;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 100%; /* position to the right of parent */
|
||||
margin-left: $arrow-border-width;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-top: $arrow-border-width;
|
||||
|
||||
border: 1px solid $active-gray;
|
||||
border-radius: 5px;
|
||||
background-color: $ui-orange;
|
||||
padding: 1rem;
|
||||
min-width: 12rem;
|
||||
max-width: 18.75rem;
|
||||
min-height: 1rem;
|
||||
overflow: visible;
|
||||
color: $type-white;
|
||||
|
||||
@media #{$medium-and-small} {
|
||||
position: relative;
|
||||
margin-top: calc($arrow-border-width / 2);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* arrow on box that points to the left */
|
||||
&:before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
left: -$arrow-border-width / 2;
|
||||
top: -.5rem;
|
||||
left: calc(50% - calc(#{$arrow-border-width} / 2));
|
||||
|
||||
transform: rotate(45deg);
|
||||
transform: rotate(135deg);
|
||||
|
||||
border-bottom: 1px solid $active-gray;
|
||||
border-left: 1px solid $active-gray;
|
||||
|
@ -90,6 +71,10 @@
|
|||
height: $arrow-border-width;
|
||||
|
||||
content: "";
|
||||
|
||||
@media #{$medium-and-small} {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,10 +85,13 @@
|
|||
|
||||
.form-group.has-error {
|
||||
.textarea, select {
|
||||
margin: 0;
|
||||
border: 1px solid $ui-orange;
|
||||
}
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.report-text .textarea {
|
||||
margin-bottom: 0;
|
||||
min-height: 8rem;
|
||||
}
|
||||
|
|
|
@ -86,6 +86,15 @@ module.exports.previewReducer = (state, action) => {
|
|||
return Object.assign({}, state, {
|
||||
comments: [...state.comments, ...action.items] // TODO: consider a different way of doing this?
|
||||
});
|
||||
case 'SET_COMMENT_DELETED':
|
||||
return Object.assign({}, state, {
|
||||
comments: state.comments.map(comment => {
|
||||
if (comment.id === action.commentId) {
|
||||
return Object.assign({}, comment, {deleted: true});
|
||||
}
|
||||
return comment;
|
||||
})
|
||||
});
|
||||
case 'SET_REPLIES':
|
||||
return Object.assign({}, state, {
|
||||
replies: merge({}, state.replies, action.replies)
|
||||
|
@ -191,6 +200,11 @@ module.exports.setStudioFetchStatus = (studioId, status) => ({
|
|||
status: status
|
||||
});
|
||||
|
||||
module.exports.setCommentDeleted = commentId => ({
|
||||
type: 'SET_COMMENT_DELETED',
|
||||
commentId: commentId
|
||||
});
|
||||
|
||||
module.exports.getProjectInfo = (id, token) => (dispatch => {
|
||||
const opts = {
|
||||
uri: `/projects/${id}`
|
||||
|
@ -562,6 +576,26 @@ module.exports.updateProject = (id, jsonData, username, token) => (dispatch => {
|
|||
});
|
||||
});
|
||||
|
||||
module.exports.deleteComment = (projectId, commentId, token) => (dispatch => {
|
||||
/* TODO fetching/fetched/error states updates for comment deleting */
|
||||
api({
|
||||
uri: `/proxy/comments/project/${projectId}`,
|
||||
authentication: token,
|
||||
withCredentials: true,
|
||||
method: 'DELETE',
|
||||
useCsrf: true,
|
||||
json: {
|
||||
id: commentId
|
||||
}
|
||||
}, (err, body, res) => {
|
||||
if (err || res.statusCode !== 200) {
|
||||
log.error(err || res.body);
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setCommentDeleted(commentId));
|
||||
});
|
||||
});
|
||||
|
||||
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
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classNames = require('classnames');
|
||||
|
||||
const FlexRow = require('../../../components/flex-row/flex-row.jsx');
|
||||
const Avatar = require('../../../components/avatar/avatar.jsx');
|
||||
|
@ -9,8 +10,11 @@ require('./comment.scss');
|
|||
|
||||
const Comment = ({
|
||||
author,
|
||||
deletable,
|
||||
deleted,
|
||||
content,
|
||||
datetimeCreated,
|
||||
onDelete,
|
||||
id
|
||||
}) => (
|
||||
<div
|
||||
|
@ -27,12 +31,25 @@ const Comment = ({
|
|||
href={`/users/${author.username}`}
|
||||
>{author.username}</a>
|
||||
<div className="action-list">
|
||||
{/* TODO: Hook these up to API calls/logic */}
|
||||
<span className="comment-delete">Delete</span>
|
||||
<span className="comment-report">Report</span>
|
||||
{deletable ? (
|
||||
<span
|
||||
className="comment-delete"
|
||||
onClick={onDelete}
|
||||
>
|
||||
Delete {/* TODO internationalize */}
|
||||
</span>
|
||||
) : null}
|
||||
<span className="comment-report">
|
||||
Report {/* TODO internationalize */}
|
||||
</span>
|
||||
</div>
|
||||
</FlexRow>
|
||||
<div className="comment-bubble">
|
||||
<div
|
||||
className={classNames({
|
||||
'comment-bubble': true,
|
||||
'comment-bubble-deleted': deleted
|
||||
})}
|
||||
>
|
||||
{/* TODO: at the moment, comment content does not properly display
|
||||
* emojis/easter eggs
|
||||
* @user links in replies
|
||||
|
@ -63,7 +80,10 @@ Comment.propTypes = {
|
|||
}),
|
||||
content: PropTypes.string,
|
||||
datetimeCreated: PropTypes.string,
|
||||
id: PropTypes.number
|
||||
deletable: PropTypes.bool,
|
||||
deleted: PropTypes.bool,
|
||||
id: PropTypes.number,
|
||||
onDelete: PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = Comment;
|
||||
|
|
|
@ -131,6 +131,19 @@
|
|||
height: 9px;
|
||||
content: "";
|
||||
}
|
||||
|
||||
&.comment-bubble-deleted {
|
||||
$deleted-outline: #ff6680;
|
||||
$deleted-background: rgb(236, 206, 223);
|
||||
|
||||
border-color: $deleted-outline;
|
||||
background-color: $deleted-background;
|
||||
|
||||
&:before {
|
||||
border-color: $deleted-outline transparent $deleted-outline $deleted-outline;
|
||||
background: $deleted-background;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.comment-content {
|
||||
|
|
|
@ -12,7 +12,8 @@ class TopLevelComment extends React.Component {
|
|||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleExpandThread'
|
||||
'handleExpandThread',
|
||||
'handleDelete'
|
||||
]);
|
||||
this.state = {
|
||||
expanded: false
|
||||
|
@ -25,18 +26,27 @@ class TopLevelComment extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
handleDelete () {
|
||||
this.props.onDelete(this.props.id);
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
author,
|
||||
content,
|
||||
datetimeCreated,
|
||||
deletable,
|
||||
deleted,
|
||||
id,
|
||||
replies
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<FlexRow className="comment-container">
|
||||
<Comment {...{author, content, datetimeCreated, id}} />
|
||||
<Comment
|
||||
onDelete={this.handleDelete}
|
||||
{...{author, content, datetimeCreated, deletable, deleted, id}}
|
||||
/>
|
||||
{replies.length > 0 &&
|
||||
<FlexRow
|
||||
className={classNames(
|
||||
|
@ -51,8 +61,11 @@ class TopLevelComment extends React.Component {
|
|||
author={reply.author}
|
||||
content={reply.content}
|
||||
datetimeCreated={reply.datetime_created}
|
||||
deletable={deletable}
|
||||
deleted={reply.deleted}
|
||||
id={reply.id}
|
||||
key={reply.id}
|
||||
onDelete={this.handleDelete}
|
||||
/>
|
||||
))}
|
||||
</FlexRow>
|
||||
|
@ -76,7 +89,10 @@ TopLevelComment.propTypes = {
|
|||
}),
|
||||
content: PropTypes.string,
|
||||
datetimeCreated: PropTypes.string,
|
||||
deletable: PropTypes.bool,
|
||||
deleted: PropTypes.bool,
|
||||
id: PropTypes.number,
|
||||
onDelete: PropTypes.func,
|
||||
parentId: PropTypes.number,
|
||||
projectId: PropTypes.string,
|
||||
replies: PropTypes.arrayOf(PropTypes.object)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
const FormattedDate = require('react-intl').FormattedDate;
|
||||
const injectIntl = require('react-intl').injectIntl;
|
||||
const PropTypes = require('prop-types');
|
||||
const intlShape = require('react-intl').intlShape;
|
||||
|
@ -6,7 +5,6 @@ const MediaQuery = require('react-responsive').default;
|
|||
const React = require('react');
|
||||
const Formsy = require('formsy-react').default;
|
||||
const classNames = require('classnames');
|
||||
const approx = require('approximate-number');
|
||||
|
||||
const GUI = require('scratch-gui').default;
|
||||
const IntlGUI = injectIntl(GUI);
|
||||
|
@ -15,14 +13,13 @@ 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 CappedNumber = require('../../components/cappednumber/cappednumber.jsx');
|
||||
const ShareBanner = require('./share-banner.jsx');
|
||||
const RemixCredit = require('./remix-credit.jsx');
|
||||
const RemixList = require('./remix-list.jsx');
|
||||
const Stats = require('./stats.jsx');
|
||||
const StudioList = require('./studio-list.jsx');
|
||||
const Subactions = require('./subactions.jsx');
|
||||
const InplaceInput = require('../../components/forms/inplace-input.jsx');
|
||||
const AddToStudioModal = require('../../components/modal/addtostudio/container.jsx');
|
||||
const ReportModal = require('../../components/modal/report/modal.jsx');
|
||||
const TopLevelComment = require('./comment/top-level-comment.jsx');
|
||||
const ExtensionChip = require('./extension-chip.jsx');
|
||||
|
||||
|
@ -67,6 +64,7 @@ const PreviewPresentation = ({
|
|||
projectStudios,
|
||||
studios,
|
||||
userOwnsProject,
|
||||
onDeleteComment,
|
||||
onFavoriteClicked,
|
||||
onLoadMore,
|
||||
onLoveClicked,
|
||||
|
@ -78,53 +76,50 @@ const PreviewPresentation = ({
|
|||
onToggleStudio,
|
||||
onSeeInside,
|
||||
onUpdate
|
||||
}) => {
|
||||
const shareDate = ((projectInfo.history && projectInfo.history.shared)) ? projectInfo.history.shared : '';
|
||||
return (
|
||||
<div className="preview">
|
||||
<ShareBanner shared={isShared} />
|
||||
|
||||
{ projectInfo && projectInfo.author && projectInfo.author.id && (
|
||||
<Formsy onKeyPress={onKeyPress}>
|
||||
<div className="inner">
|
||||
<FlexRow className="preview-row">
|
||||
<FlexRow className="project-header">
|
||||
<a href={`/users/${projectInfo.author.username}`}>
|
||||
<Avatar
|
||||
alt={projectInfo.author.username}
|
||||
src={`https://cdn2.scratch.mit.edu/get_image/user/${projectInfo.author.id}_48x48.png`}
|
||||
/>
|
||||
</a>
|
||||
<div className="title">
|
||||
{editable ?
|
||||
|
||||
<InplaceInput
|
||||
className="project-title"
|
||||
handleUpdate={onUpdate}
|
||||
name="title"
|
||||
validationErrors={{
|
||||
maxLength: intl.formatMessage({
|
||||
id: 'preview.titleMaxLength'
|
||||
})
|
||||
}}
|
||||
validations={{
|
||||
maxLength: 100
|
||||
}}
|
||||
value={projectInfo.title}
|
||||
/> :
|
||||
<React.Fragment>
|
||||
<div
|
||||
className="project-title no-edit"
|
||||
title={projectInfo.title}
|
||||
>{projectInfo.title}</div>
|
||||
{'by '}
|
||||
<a href={`/users/${projectInfo.author.username}`}>
|
||||
{projectInfo.author.username}
|
||||
</a>
|
||||
</React.Fragment>
|
||||
}
|
||||
</div>
|
||||
</FlexRow>
|
||||
}) => (
|
||||
<div className="preview">
|
||||
<ShareBanner shared={isShared} />
|
||||
{ projectInfo && projectInfo.author && projectInfo.author.id && (
|
||||
<Formsy onKeyPress={onKeyPress}>
|
||||
<div className="inner">
|
||||
<FlexRow className="preview-row force-row">
|
||||
<FlexRow className="project-header">
|
||||
<a href={`/users/${projectInfo.author.username}`}>
|
||||
<Avatar
|
||||
alt={projectInfo.author.username}
|
||||
src={`https://cdn2.scratch.mit.edu/get_image/user/${projectInfo.author.id}_48x48.png`}
|
||||
/>
|
||||
</a>
|
||||
<div className="title">
|
||||
{editable ?
|
||||
<InplaceInput
|
||||
className="project-title"
|
||||
handleUpdate={onUpdate}
|
||||
name="title"
|
||||
validationErrors={{
|
||||
maxLength: intl.formatMessage({
|
||||
id: 'preview.titleMaxLength'
|
||||
})
|
||||
}}
|
||||
validations={{
|
||||
maxLength: 100
|
||||
}}
|
||||
value={projectInfo.title}
|
||||
/> :
|
||||
<React.Fragment>
|
||||
<div
|
||||
className="project-title no-edit"
|
||||
title={projectInfo.title}
|
||||
>{projectInfo.title}</div>
|
||||
{'by '}
|
||||
<a href={`/users/${projectInfo.author.username}`}>
|
||||
{projectInfo.author.username}
|
||||
</a>
|
||||
</React.Fragment>
|
||||
}
|
||||
</div>
|
||||
</FlexRow>
|
||||
<MediaQuery minWidth={frameless.mobile}>
|
||||
<div className="project-buttons">
|
||||
{/* TODO: Hide Remix button for now until implemented */}
|
||||
{(!userOwnsProject && false) &&
|
||||
|
@ -139,253 +134,222 @@ const PreviewPresentation = ({
|
|||
See Inside
|
||||
</Button>
|
||||
</div>
|
||||
</FlexRow>
|
||||
<FlexRow className="preview-row">
|
||||
<div className="guiPlayer">
|
||||
<IntlGUI
|
||||
isPlayerOnly
|
||||
assetHost={assetHost}
|
||||
backpackOptions={backpackOptions}
|
||||
basePath="/"
|
||||
className="guiPlayer"
|
||||
isFullScreen={isFullScreen}
|
||||
previewInfoVisible="false"
|
||||
projectHost={projectHost}
|
||||
projectId={projectId}
|
||||
</MediaQuery>
|
||||
</FlexRow>
|
||||
<FlexRow className="preview-row">
|
||||
<div className="guiPlayer">
|
||||
<IntlGUI
|
||||
isPlayerOnly
|
||||
assetHost={assetHost}
|
||||
backpackOptions={backpackOptions}
|
||||
basePath="/"
|
||||
className="guiPlayer"
|
||||
isFullScreen={isFullScreen}
|
||||
previewInfoVisible="false"
|
||||
projectHost={projectHost}
|
||||
projectId={projectId}
|
||||
/>
|
||||
</div>
|
||||
<MediaQuery maxWidth={frameless.tablet - 1}>
|
||||
<FlexRow className="preview-row force-center">
|
||||
<Stats
|
||||
faved={faved}
|
||||
favoriteCount={favoriteCount}
|
||||
loveCount={loveCount}
|
||||
loved={loved}
|
||||
projectInfo={projectInfo}
|
||||
onFavoriteClicked={onFavoriteClicked}
|
||||
onLoveClicked={onLoveClicked}
|
||||
/>
|
||||
<Subactions
|
||||
addToStudioOpen={addToStudioOpen}
|
||||
isLoggedIn={isLoggedIn}
|
||||
projectInfo={projectInfo}
|
||||
reportOpen={reportOpen}
|
||||
studios={studios}
|
||||
userOwnsProject={userOwnsProject}
|
||||
onAddToStudioClicked={onAddToStudioClicked}
|
||||
onAddToStudioClosed={onAddToStudioClosed}
|
||||
onReportClicked={onReportClicked}
|
||||
onReportClose={onReportClose}
|
||||
onReportSubmit={onReportSubmit}
|
||||
onToggleStudio={onToggleStudio}
|
||||
/>
|
||||
</div>
|
||||
<FlexRow className="project-notes">
|
||||
<RemixCredit projectInfo={parentInfo} />
|
||||
<RemixCredit projectInfo={originalInfo} />
|
||||
{/* eslint-disable max-len */}
|
||||
<MediaQuery maxWidth={frameless.tablet - 1}>
|
||||
<FlexRow className="preview-row">
|
||||
<FlexRow className="extension-list">
|
||||
{extensions && extensions.map(extension => (
|
||||
<ExtensionChip
|
||||
extensionL10n={extension.l10nId}
|
||||
extensionName={extension.name}
|
||||
hasStatus={extension.hasStatus}
|
||||
iconURI={extension.icon && `/svgs/project/${extension.icon}`}
|
||||
key={extension.name || extension.l10nId}
|
||||
/>
|
||||
))}
|
||||
</FlexRow>
|
||||
</FlexRow>
|
||||
</MediaQuery>
|
||||
<FlexRow className="description-block">
|
||||
<div className="project-textlabel">
|
||||
Instructions
|
||||
</div>
|
||||
{editable ?
|
||||
<InplaceInput
|
||||
className={classNames(
|
||||
'project-description-edit',
|
||||
{remixes: parentInfo && parentInfo.author}
|
||||
)}
|
||||
handleUpdate={onUpdate}
|
||||
name="instructions"
|
||||
placeholder="Tell people how to use your project (such as which keys to press)."
|
||||
type="textarea"
|
||||
validationErrors={{
|
||||
maxLength: 'Sorry description is too long'
|
||||
// maxLength: props.intl.formatMessage({
|
||||
// id: 'project.descriptionMaxLength'
|
||||
// })
|
||||
}}
|
||||
validations={{
|
||||
// TODO: actual 5000
|
||||
maxLength: 1000
|
||||
}}
|
||||
value={projectInfo.instructions}
|
||||
/> :
|
||||
<div className="project-description">
|
||||
{decorateText(projectInfo.instructions)}
|
||||
</div>
|
||||
}
|
||||
</FlexRow>
|
||||
<FlexRow className="description-block">
|
||||
<div className="project-textlabel">
|
||||
Notes and Credits
|
||||
</div>
|
||||
{editable ?
|
||||
<InplaceInput
|
||||
className={classNames(
|
||||
'project-description-edit',
|
||||
'last',
|
||||
{remixes: parentInfo && parentInfo.author}
|
||||
)}
|
||||
handleUpdate={onUpdate}
|
||||
name="description"
|
||||
placeholder="How did you make this project? Did you use ideas scripts or artwork from other people? Thank them here."
|
||||
type="textarea"
|
||||
validationErrors={{
|
||||
maxLength: 'Sorry description is too long'
|
||||
// maxLength: props.intl.formatMessage({
|
||||
// id: 'project.descriptionMaxLength'
|
||||
// })
|
||||
}}
|
||||
validations={{
|
||||
// TODO: actual 5000
|
||||
maxLength: 1000
|
||||
}}
|
||||
value={projectInfo.description}
|
||||
/> :
|
||||
<div className="project-description last">
|
||||
{decorateText(projectInfo.description)}
|
||||
</div>
|
||||
}
|
||||
</FlexRow>
|
||||
{/* eslint-enable max-len */}
|
||||
</FlexRow>
|
||||
</FlexRow>
|
||||
<FlexRow className="preview-row">
|
||||
<FlexRow className="stats">
|
||||
<div
|
||||
className={classNames('project-loves', {loved: loved})}
|
||||
key="loves"
|
||||
onClick={onLoveClicked}
|
||||
>
|
||||
{approx(loveCount, {decimal: false})}
|
||||
</div>
|
||||
<div
|
||||
className={classNames('project-favorites', {favorited: faved})}
|
||||
key="favorites"
|
||||
onClick={onFavoriteClicked}
|
||||
>
|
||||
{approx(favoriteCount, {decimal: false})}
|
||||
</div>
|
||||
<div
|
||||
className="project-remixes"
|
||||
key="remixes"
|
||||
>
|
||||
{approx(projectInfo.stats.remixes, {decimal: false})}
|
||||
</div>
|
||||
<div
|
||||
className="project-views"
|
||||
key="views"
|
||||
>
|
||||
<CappedNumber value={projectInfo.stats.views} />
|
||||
</div>
|
||||
</FlexRow>
|
||||
<FlexRow className="subactions">
|
||||
<div className="share-date">
|
||||
<div className="copyleft">©</div>
|
||||
{' '}
|
||||
{/* eslint-disable react/jsx-sort-props */}
|
||||
{shareDate === null ?
|
||||
'Unshared' :
|
||||
<FormattedDate
|
||||
value={Date.parse(shareDate)}
|
||||
day="2-digit"
|
||||
month="short"
|
||||
year="numeric"
|
||||
/>
|
||||
}
|
||||
{/* eslint-enable react/jsx-sort-props */}
|
||||
</div>
|
||||
<FlexRow className="action-buttons">
|
||||
{(isLoggedIn && userOwnsProject) &&
|
||||
<React.Fragment>
|
||||
<Button
|
||||
className="action-button studio-button"
|
||||
key="add-to-studio-button"
|
||||
onClick={onAddToStudioClicked}
|
||||
>
|
||||
Add to Studio
|
||||
</Button>,
|
||||
<AddToStudioModal
|
||||
isOpen={addToStudioOpen}
|
||||
key="add-to-studio-modal"
|
||||
studios={studios}
|
||||
onRequestClose={onAddToStudioClosed}
|
||||
onToggleStudio={onToggleStudio}
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
<Button className="action-button copy-link-button">
|
||||
Copy Link
|
||||
</Button>
|
||||
{(isLoggedIn && !userOwnsProject) &&
|
||||
<React.Fragment>
|
||||
<Button
|
||||
className="action-button report-button"
|
||||
key="report-button"
|
||||
onClick={onReportClicked}
|
||||
>
|
||||
Report
|
||||
</Button>,
|
||||
<ReportModal
|
||||
isOpen={reportOpen}
|
||||
key="report-modal"
|
||||
type="project"
|
||||
onReport={onReportSubmit}
|
||||
onRequestClose={onReportClose}
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
</FlexRow>
|
||||
</FlexRow>
|
||||
</FlexRow>
|
||||
<MediaQuery minWidth={frameless.tablet}>
|
||||
<FlexRow className="preview-row">
|
||||
<FlexRow className="extension-list">
|
||||
{extensions && extensions.map(extension => (
|
||||
<ExtensionChip
|
||||
extensionL10n={extension.l10nId}
|
||||
extensionName={extension.name}
|
||||
hasStatus={extension.hasStatus}
|
||||
iconURI={extension.icon && `/svgs/project/${extension.icon}`}
|
||||
key={extension.name || extension.l10nId}
|
||||
/>
|
||||
))}
|
||||
</FlexRow>
|
||||
</FlexRow>
|
||||
</MediaQuery>
|
||||
</div>
|
||||
<div className="project-lower-container">
|
||||
<div className="inner">
|
||||
<FlexRow className="preview-row">
|
||||
<div className="comments-container">
|
||||
<FlexRow className="comments-header">
|
||||
<h4>Comments</h4>
|
||||
{/* TODO: Add toggle comments component and logic*/}
|
||||
</FlexRow>
|
||||
<FlexRow className="comments-list">
|
||||
{comments.map(comment => (
|
||||
<TopLevelComment
|
||||
author={comment.author}
|
||||
content={comment.content}
|
||||
datetimeCreated={comment.datetime_created}
|
||||
id={comment.id}
|
||||
key={comment.id}
|
||||
parentId={comment.parent_id}
|
||||
projectId={projectId}
|
||||
replies={replies && replies[comment.id] ? replies[comment.id] : []}
|
||||
<FlexRow className="project-notes">
|
||||
<RemixCredit projectInfo={parentInfo} />
|
||||
<RemixCredit projectInfo={originalInfo} />
|
||||
{/* eslint-disable max-len */}
|
||||
<MediaQuery maxWidth={frameless.tablet - 1}>
|
||||
<FlexRow className="preview-row">
|
||||
<FlexRow className="extension-list">
|
||||
{extensions && extensions.map(extension => (
|
||||
<ExtensionChip
|
||||
extensionL10n={extension.l10nId}
|
||||
extensionName={extension.name}
|
||||
hasStatus={extension.hasStatus}
|
||||
iconURI={extension.icon && `/svgs/project/${extension.icon}`}
|
||||
key={extension.name || extension.l10nId}
|
||||
/>
|
||||
))}
|
||||
{comments.length < projectInfo.stats.comments &&
|
||||
<Button
|
||||
className="button load-more-button"
|
||||
onClick={onLoadMore}
|
||||
>
|
||||
Load More
|
||||
</Button>
|
||||
}
|
||||
</FlexRow>
|
||||
</div>
|
||||
<FlexRow className="column">
|
||||
<RemixList remixes={remixes} />
|
||||
<StudioList studios={projectStudios} />
|
||||
</FlexRow>
|
||||
</MediaQuery>
|
||||
<FlexRow className="description-block">
|
||||
<div className="project-textlabel">
|
||||
Instructions
|
||||
</div>
|
||||
{editable ?
|
||||
<InplaceInput
|
||||
className={classNames(
|
||||
'project-description-edit',
|
||||
{remixes: parentInfo && parentInfo.author}
|
||||
)}
|
||||
handleUpdate={onUpdate}
|
||||
name="instructions"
|
||||
placeholder="Tell people how to use your project (such as which keys to press)."
|
||||
type="textarea"
|
||||
validationErrors={{
|
||||
maxLength: 'Sorry description is too long'
|
||||
// maxLength: props.intl.formatMessage({
|
||||
// id: 'project.descriptionMaxLength'
|
||||
// })
|
||||
}}
|
||||
validations={{
|
||||
// TODO: actual 5000
|
||||
maxLength: 1000
|
||||
}}
|
||||
value={projectInfo.instructions}
|
||||
/> :
|
||||
<div className="project-description">
|
||||
{decorateText(projectInfo.instructions)}
|
||||
</div>
|
||||
}
|
||||
</FlexRow>
|
||||
</div>
|
||||
<FlexRow className="description-block">
|
||||
<div className="project-textlabel">
|
||||
Notes and Credits
|
||||
</div>
|
||||
{editable ?
|
||||
<InplaceInput
|
||||
className={classNames(
|
||||
'project-description-edit',
|
||||
'last',
|
||||
{remixes: parentInfo && parentInfo.author}
|
||||
)}
|
||||
handleUpdate={onUpdate}
|
||||
name="description"
|
||||
placeholder="How did you make this project? Did you use ideas scripts or artwork from other people? Thank them here."
|
||||
type="textarea"
|
||||
validationErrors={{
|
||||
maxLength: 'Sorry description is too long'
|
||||
// maxLength: props.intl.formatMessage({
|
||||
// id: 'project.descriptionMaxLength'
|
||||
// })
|
||||
}}
|
||||
validations={{
|
||||
// TODO: actual 5000
|
||||
maxLength: 1000
|
||||
}}
|
||||
value={projectInfo.description}
|
||||
/> :
|
||||
<div className="project-description last">
|
||||
{decorateText(projectInfo.description)}
|
||||
</div>
|
||||
}
|
||||
</FlexRow>
|
||||
{/* eslint-enable max-len */}
|
||||
</FlexRow>
|
||||
</FlexRow>
|
||||
<MediaQuery minWidth={frameless.tablet}>
|
||||
<FlexRow className="preview-row">
|
||||
<Stats
|
||||
faved={faved}
|
||||
favoriteCount={favoriteCount}
|
||||
loveCount={loveCount}
|
||||
loved={loved}
|
||||
projectInfo={projectInfo}
|
||||
onFavoriteClicked={onFavoriteClicked}
|
||||
onLoveClicked={onLoveClicked}
|
||||
/>
|
||||
<Subactions
|
||||
addToStudioOpen={addToStudioOpen}
|
||||
isLoggedIn={isLoggedIn}
|
||||
projectInfo={projectInfo}
|
||||
reportOpen={reportOpen}
|
||||
studios={studios}
|
||||
userOwnsProject={userOwnsProject}
|
||||
onAddToStudioClicked={onAddToStudioClicked}
|
||||
onAddToStudioClosed={onAddToStudioClosed}
|
||||
onReportClicked={onReportClicked}
|
||||
onReportClose={onReportClose}
|
||||
onReportSubmit={onReportSubmit}
|
||||
onToggleStudio={onToggleStudio}
|
||||
/>
|
||||
</FlexRow>
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={frameless.tablet}>
|
||||
<FlexRow className="preview-row">
|
||||
<FlexRow className="extension-list">
|
||||
{extensions && extensions.map(extension => (
|
||||
<ExtensionChip
|
||||
extensionL10n={extension.l10nId}
|
||||
extensionName={extension.name}
|
||||
hasStatus={extension.hasStatus}
|
||||
iconURI={extension.icon && `/svgs/project/${extension.icon}`}
|
||||
key={extension.name || extension.l10nId}
|
||||
/>
|
||||
))}
|
||||
</FlexRow>
|
||||
</FlexRow>
|
||||
</MediaQuery>
|
||||
</div>
|
||||
<div className="project-lower-container">
|
||||
<div className="inner">
|
||||
<FlexRow className="preview-row">
|
||||
<div className="comments-container">
|
||||
<FlexRow className="comments-header">
|
||||
<h4>Comments</h4>
|
||||
{/* TODO: Add toggle comments component and logic*/}
|
||||
</FlexRow>
|
||||
<FlexRow className="comments-list">
|
||||
{comments.map(comment => (
|
||||
<TopLevelComment
|
||||
author={comment.author}
|
||||
content={comment.content}
|
||||
datetimeCreated={comment.datetime_created}
|
||||
deletable={userOwnsProject}
|
||||
deleted={comment.deleted}
|
||||
id={comment.id}
|
||||
key={comment.id}
|
||||
parentId={comment.parent_id}
|
||||
projectId={projectId}
|
||||
replies={replies && replies[comment.id] ? replies[comment.id] : []}
|
||||
onDelete={onDeleteComment}
|
||||
/>
|
||||
))}
|
||||
{comments.length < projectInfo.stats.comments &&
|
||||
<Button
|
||||
className="button load-more-button"
|
||||
onClick={onLoadMore}
|
||||
>
|
||||
Load More
|
||||
</Button>
|
||||
}
|
||||
</FlexRow>
|
||||
</div>
|
||||
<FlexRow className="column">
|
||||
<RemixList remixes={remixes} />
|
||||
<StudioList studios={projectStudios} />
|
||||
</FlexRow>
|
||||
</FlexRow>
|
||||
</div>
|
||||
</Formsy>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</div>
|
||||
</Formsy>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
PreviewPresentation.propTypes = {
|
||||
addToStudioOpen: PropTypes.bool,
|
||||
|
@ -407,6 +371,7 @@ PreviewPresentation.propTypes = {
|
|||
loved: PropTypes.bool,
|
||||
onAddToStudioClicked: PropTypes.func,
|
||||
onAddToStudioClosed: PropTypes.func,
|
||||
onDeleteComment: PropTypes.func,
|
||||
onFavoriteClicked: PropTypes.func,
|
||||
onLoadMore: PropTypes.func,
|
||||
onLoveClicked: PropTypes.func,
|
||||
|
|
|
@ -23,6 +23,8 @@ const sessionActions = require('../../redux/session.js');
|
|||
const navigationActions = require('../../redux/navigation.js');
|
||||
const previewActions = require('../../redux/preview.js');
|
||||
|
||||
const frameless = require('../../lib/frameless');
|
||||
|
||||
const GUI = require('scratch-gui');
|
||||
const IntlGUI = injectIntl(GUI.default);
|
||||
|
||||
|
@ -31,6 +33,7 @@ class Preview extends React.Component {
|
|||
super(props);
|
||||
bindAll(this, [
|
||||
'addEventListeners',
|
||||
'handleDeleteComment',
|
||||
'handleToggleStudio',
|
||||
'handleFavoriteToggle',
|
||||
'handleLoadMore',
|
||||
|
@ -46,7 +49,8 @@ class Preview extends React.Component {
|
|||
'handleUpdate',
|
||||
'initCounts',
|
||||
'pushHistory',
|
||||
'renderLogin'
|
||||
'renderLogin',
|
||||
'setScreenFromOrientation'
|
||||
]);
|
||||
const pathname = window.location.pathname.toLowerCase();
|
||||
const parts = pathname.split('/').filter(Boolean);
|
||||
|
@ -63,6 +67,8 @@ class Preview extends React.Component {
|
|||
};
|
||||
this.getExtensions(this.state.projectId);
|
||||
this.addEventListeners();
|
||||
/* In the beginning, if user is on mobile and landscape, go to fullscreen */
|
||||
this.setScreenFromOrientation();
|
||||
}
|
||||
componentDidUpdate (prevProps) {
|
||||
if (this.props.sessionStatus !== prevProps.sessionStatus &&
|
||||
|
@ -106,9 +112,26 @@ class Preview extends React.Component {
|
|||
}
|
||||
addEventListeners () {
|
||||
window.addEventListener('popstate', this.handlePopState);
|
||||
window.addEventListener('orientationchange', this.setScreenFromOrientation);
|
||||
}
|
||||
removeEventListeners () {
|
||||
window.removeEventListener('popstate', this.handlePopState);
|
||||
window.removeEventListener('orientationchange', this.setScreenFromOrientation);
|
||||
}
|
||||
setScreenFromOrientation () {
|
||||
/*
|
||||
* If the user is on a mobile device, switching to
|
||||
* landscape format should make the fullscreen mode active
|
||||
*/
|
||||
const isMobileDevice = screen.height <= frameless.mobile || screen.width <= frameless.mobile;
|
||||
if (this.props.playerMode && isMobileDevice) {
|
||||
const isLandscape = screen.height < screen.width;
|
||||
if (isLandscape) {
|
||||
this.props.setFullScreen(true);
|
||||
} else {
|
||||
this.props.setFullScreen(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
getExtensions (projectId) {
|
||||
storage
|
||||
|
@ -142,6 +165,9 @@ class Preview extends React.Component {
|
|||
});
|
||||
});
|
||||
}
|
||||
handleDeleteComment (id) {
|
||||
this.props.handleDeleteComment(this.state.projectId, id, this.props.user.token);
|
||||
}
|
||||
handleReportClick () {
|
||||
this.setState({reportOpen: true});
|
||||
}
|
||||
|
@ -316,6 +342,7 @@ class Preview extends React.Component {
|
|||
userOwnsProject={this.props.userOwnsProject}
|
||||
onAddToStudioClicked={this.handleAddToStudioClick}
|
||||
onAddToStudioClosed={this.handleAddToStudioClose}
|
||||
onDeleteComment={this.handleDeleteComment}
|
||||
onFavoriteClicked={this.handleFavoriteToggle}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
onLoveClicked={this.handleLoveToggle}
|
||||
|
@ -371,6 +398,7 @@ Preview.propTypes = {
|
|||
getProjectStudios: PropTypes.func.isRequired,
|
||||
getRemixes: PropTypes.func.isRequired,
|
||||
getTopLevelComments: PropTypes.func.isRequired,
|
||||
handleDeleteComment: PropTypes.func,
|
||||
handleLogIn: PropTypes.func,
|
||||
handleLogOut: PropTypes.func,
|
||||
handleOpenRegistration: PropTypes.func,
|
||||
|
@ -497,6 +525,9 @@ const mapStateToProps = state => {
|
|||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleDeleteComment: (projectId, commentId, token) => {
|
||||
dispatch(previewActions.deleteComment(projectId, commentId, token));
|
||||
},
|
||||
handleOpenRegistration: event => {
|
||||
event.preventDefault();
|
||||
dispatch(navigationActions.setRegistrationOpen(true));
|
||||
|
|
|
@ -6,7 +6,6 @@ $player-width: 482px;
|
|||
$player-height: 406px;
|
||||
$stage-width: 480px;
|
||||
|
||||
/* screen sizes */
|
||||
$small: "screen and (max-width : #{$mobile}-1)";
|
||||
$medium: "screen and (min-width : #{$mobile}) and (max-width : #{$tablet}-1)";
|
||||
$intermediate: "screen and (min-width : #{$tablet}) and (max-width : #{$desktop}-1)";
|
||||
|
@ -46,7 +45,6 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
|||
font-weight: 500;
|
||||
|
||||
&.has-error {
|
||||
|
||||
.validation-message {
|
||||
right: 0;
|
||||
}
|
||||
|
@ -72,6 +70,10 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
|||
.inplace-input {
|
||||
height: calc(3rem - 4px);
|
||||
}
|
||||
|
||||
@media #{$medium-and-small} {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
|
@ -96,13 +98,17 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
|||
text-align: left;
|
||||
font-size: .8rem;
|
||||
flex-grow: 1;
|
||||
|
||||
@media #{$medium-and-small} {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.validation-message {
|
||||
$arrow-border-width: 1rem;
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
$arrow-border-width: 1rem;
|
||||
margin-top: $arrow-border-width;
|
||||
border: 1px solid $active-gray;
|
||||
border-radius: 5px;
|
||||
|
@ -166,7 +172,23 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
|||
}
|
||||
|
||||
.comments-container {
|
||||
width: 65%;
|
||||
padding-right: 1.5rem;
|
||||
min-width: 65%;
|
||||
max-width: 100%;
|
||||
flex: 1;
|
||||
|
||||
@media #{$medium-and-small} {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.comment, .comment-top-row, .comment-bottom-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.comment-bubble {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.remix-button,
|
||||
|
@ -208,11 +230,31 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
|||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
&.force-row {
|
||||
@media #{$medium-and-small} {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.guiPlayer {
|
||||
display: inline-block;
|
||||
width: $player-width;
|
||||
|
||||
@media #{$small} {
|
||||
width: 100%;
|
||||
|
||||
.stage-wrapper {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.force-center {
|
||||
@media #{$medium-and-small} {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.project-notes {
|
||||
|
@ -222,26 +264,17 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
|||
flex: 1;
|
||||
flex-flow: column;
|
||||
|
||||
@media #{$medium-and-small} {
|
||||
margin-top: 1rem;
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
> .description-block:first-child {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.share-date {
|
||||
margin-right: .75rem;
|
||||
vertical-align: middle;
|
||||
line-height: 2rem;
|
||||
color: $type-gray;
|
||||
font-size: .875rem;
|
||||
}
|
||||
|
||||
.subactions {
|
||||
margin-left: 1.5rem;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.remix-credit {
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid $ui-blue-10percent;
|
||||
|
@ -250,7 +283,11 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
|||
padding: .5rem;
|
||||
width: calc(100% - 1rem);
|
||||
flex-wrap: nowrap;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
|
||||
@media #{$medium-and-small} {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.credit-text {
|
||||
|
@ -325,146 +362,6 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
|||
}
|
||||
}
|
||||
|
||||
.copyleft {
|
||||
display: inline-block;
|
||||
transform: scale(-1, 1);
|
||||
margin: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.stats {
|
||||
line-height: 2rem;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.project-loves,
|
||||
.project-favorites,
|
||||
.project-remixes,
|
||||
.project-views {
|
||||
|
||||
display: inline;
|
||||
padding-right: 2rem;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
|
||||
&:before {
|
||||
display: inline-block;
|
||||
margin-right: .5rem;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: contain;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
vertical-align: -.25rem;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
.project-loves {
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
&:before {
|
||||
opacity: .5;
|
||||
background-image: url("/svgs/project/love-gray.svg");
|
||||
}
|
||||
}
|
||||
|
||||
.project-loves.loved {
|
||||
|
||||
&:before {
|
||||
opacity: 1;
|
||||
background-image: url("/svgs/project/love-red.svg");
|
||||
}
|
||||
}
|
||||
|
||||
.project-favorites {
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
&:before {
|
||||
opacity: .5;
|
||||
background-image: url("/svgs/project/fav-gray.svg");
|
||||
}
|
||||
}
|
||||
|
||||
.project-favorites.favorited {
|
||||
|
||||
&:before {
|
||||
opacity: 1;
|
||||
background-image: url("/svgs/project/fav-yellow.svg");
|
||||
}
|
||||
}
|
||||
|
||||
.project-remixes {
|
||||
|
||||
&:before {
|
||||
opacity: .5;
|
||||
background-image: url("/svgs/project/remix-gray.svg");
|
||||
}
|
||||
}
|
||||
|
||||
.project-views {
|
||||
|
||||
&:before {
|
||||
opacity: .5;
|
||||
background-image: url("/svgs/project/views-gray.svg");
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
margin-top: 0;
|
||||
color: $type-white;
|
||||
font-size: .8rem;
|
||||
font-weight: 500;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
margin: 0 0 0 .5rem;
|
||||
border-radius: 19px;
|
||||
background-color: $ui-blue;
|
||||
padding: 0 .75rem;
|
||||
height: 2rem;
|
||||
text-decoration: none;
|
||||
line-height: .875rem;
|
||||
font-size: .75rem;
|
||||
font-weight: normal;
|
||||
|
||||
// &:hover {
|
||||
// transition: background-color .25s ease;
|
||||
// border-color: transparent;
|
||||
// background-color: $active-gray;
|
||||
// }
|
||||
//
|
||||
// &:active {
|
||||
// border: 0 solid transparent;
|
||||
// box-shadow: inset 0 0 5px $box-shadow-gray;
|
||||
// background-color: $active-dark-gray;
|
||||
// padding: calc(.75em + 1px) calc(1.5em + 1px);
|
||||
// }
|
||||
//
|
||||
// &.report {
|
||||
// border: 1px solid $ui-coral;
|
||||
// background-color: $ui-coral;
|
||||
//
|
||||
// &:hover {
|
||||
// transition: background-color .25s ease;
|
||||
// border-color: transparent;
|
||||
// background-color: $active-gray;
|
||||
// }
|
||||
//
|
||||
// &:active {
|
||||
// border: 0 solid transparent;
|
||||
// box-shadow: inset 0 0 5px $box-shadow-gray;
|
||||
// background-color: $active-dark-gray;
|
||||
// padding: calc(.75em + 1px) calc(1.5em + 1px);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
.comments-header {
|
||||
padding: 0 0 1rem 0;
|
||||
justify-content: space-between;
|
||||
|
@ -474,42 +371,6 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
|||
}
|
||||
}
|
||||
|
||||
.studio-button,
|
||||
.copy-link-button,
|
||||
.report-button {
|
||||
&:before {
|
||||
display: inline-block;
|
||||
margin-right: .25rem;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: contain;
|
||||
width: .875rem;
|
||||
height: .875rem;
|
||||
vertical-align: bottom;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
.studio-button {
|
||||
&:before {
|
||||
background-image: url("/svgs/project/studio-add-white.svg");
|
||||
}
|
||||
}
|
||||
|
||||
.copy-link-button {
|
||||
&:before {
|
||||
background-image: url("/svgs/project/copy-link-white.svg");
|
||||
}
|
||||
}
|
||||
|
||||
.report-button {
|
||||
background-color: $ui-coral;
|
||||
|
||||
&:before {
|
||||
background-image: url("/svgs/project/report-white.svg");
|
||||
}
|
||||
}
|
||||
|
||||
.project-lower-container {
|
||||
margin-top: 1rem;
|
||||
background-color: $ui-blue-10percent;
|
||||
|
@ -573,8 +434,3 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.report-text textarea {
|
||||
// override min-height from default settings (for teacher registration)
|
||||
min-height: 8rem;
|
||||
}
|
||||
|
|
53
src/views/preview/stats.jsx
Normal file
53
src/views/preview/stats.jsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||
const classNames = require('classnames');
|
||||
const approx = require('approximate-number');
|
||||
|
||||
const CappedNumber = require('../../components/cappednumber/cappednumber.jsx');
|
||||
const projectShape = require('./projectshape.jsx').projectShape;
|
||||
|
||||
require('./stats.scss');
|
||||
|
||||
const Stats = props => (
|
||||
<FlexRow className="stats">
|
||||
<div
|
||||
className={classNames('project-loves', {loved: props.loved})}
|
||||
key="loves"
|
||||
onClick={props.onLoveClicked}
|
||||
>
|
||||
{approx(props.loveCount, {decimal: false})}
|
||||
</div>
|
||||
<div
|
||||
className={classNames('project-favorites', {favorited: props.faved})}
|
||||
key="favorites"
|
||||
onClick={props.onFavoriteClicked}
|
||||
>
|
||||
{approx(props.favoriteCount, {decimal: false})}
|
||||
</div>
|
||||
<div
|
||||
className="project-remixes"
|
||||
key="remixes"
|
||||
>
|
||||
{approx(props.projectInfo.stats.remixes, {decimal: false})}
|
||||
</div>
|
||||
<div
|
||||
className="project-views"
|
||||
key="views"
|
||||
>
|
||||
<CappedNumber value={props.projectInfo.stats.views} />
|
||||
</div>
|
||||
</FlexRow>
|
||||
);
|
||||
|
||||
Stats.propTypes = {
|
||||
faved: PropTypes.bool,
|
||||
favoriteCount: PropTypes.number,
|
||||
loveCount: PropTypes.number,
|
||||
loved: PropTypes.bool,
|
||||
onFavoriteClicked: PropTypes.func,
|
||||
onLoveClicked: PropTypes.func,
|
||||
projectInfo: projectShape
|
||||
};
|
||||
|
||||
module.exports = Stats;
|
96
src/views/preview/stats.scss
Normal file
96
src/views/preview/stats.scss
Normal file
|
@ -0,0 +1,96 @@
|
|||
@import "../../frameless";
|
||||
|
||||
$medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||
|
||||
.stats {
|
||||
line-height: 2rem;
|
||||
justify-content: flex-start;
|
||||
|
||||
@media #{$medium-and-small} {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
& > div {
|
||||
@media #{$medium-and-small} {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.project-loves,
|
||||
.project-favorites,
|
||||
.project-remixes,
|
||||
.project-views {
|
||||
|
||||
display: inline;
|
||||
padding-right: 2rem;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
|
||||
&:before {
|
||||
display: inline-block;
|
||||
margin-right: .5rem;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: contain;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
vertical-align: -.25rem;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
.project-loves {
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
&:before {
|
||||
opacity: .5;
|
||||
background-image: url("/svgs/project/love-gray.svg");
|
||||
}
|
||||
}
|
||||
|
||||
.project-loves.loved {
|
||||
|
||||
&:before {
|
||||
opacity: 1;
|
||||
background-image: url("/svgs/project/love-red.svg");
|
||||
}
|
||||
}
|
||||
|
||||
.project-favorites {
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
&:before {
|
||||
opacity: .5;
|
||||
background-image: url("/svgs/project/fav-gray.svg");
|
||||
}
|
||||
}
|
||||
|
||||
.project-favorites.favorited {
|
||||
|
||||
&:before {
|
||||
opacity: 1;
|
||||
background-image: url("/svgs/project/fav-yellow.svg");
|
||||
}
|
||||
}
|
||||
|
||||
.project-remixes {
|
||||
|
||||
&:before {
|
||||
opacity: .5;
|
||||
background-image: url("/svgs/project/remix-gray.svg");
|
||||
}
|
||||
}
|
||||
|
||||
.project-views {
|
||||
|
||||
&:before {
|
||||
opacity: .5;
|
||||
background-image: url("/svgs/project/views-gray.svg");
|
||||
}
|
||||
}
|
95
src/views/preview/subactions.jsx
Normal file
95
src/views/preview/subactions.jsx
Normal file
|
@ -0,0 +1,95 @@
|
|||
const FormattedDate = require('react-intl').FormattedDate;
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||
|
||||
const Button = require('../../components/forms/button.jsx');
|
||||
const AddToStudioModal = require('../../components/modal/addtostudio/container.jsx');
|
||||
const ReportModal = require('../../components/modal/report/modal.jsx');
|
||||
|
||||
const projectShape = require('./projectshape.jsx').projectShape;
|
||||
|
||||
require('./subactions.scss');
|
||||
|
||||
const Subactions = props => {
|
||||
const shareDate = ((props.projectInfo.history && props.projectInfo.history.shared)) ?
|
||||
props.projectInfo.history.shared : '';
|
||||
return (
|
||||
<FlexRow className="subactions">
|
||||
<div className="share-date">
|
||||
<div className="copyleft">©</div>
|
||||
{' '}
|
||||
{/* eslint-disable react/jsx-sort-props */}
|
||||
{shareDate === null ?
|
||||
'Unshared' :
|
||||
<FormattedDate
|
||||
value={Date.parse(shareDate)}
|
||||
day="2-digit"
|
||||
month="short"
|
||||
year="numeric"
|
||||
/>
|
||||
}
|
||||
{/* eslint-enable react/jsx-sort-props */}
|
||||
</div>
|
||||
<FlexRow className="action-buttons">
|
||||
{(props.isLoggedIn && props.userOwnsProject) &&
|
||||
<React.Fragment>
|
||||
<Button
|
||||
className="action-button studio-button"
|
||||
key="add-to-studio-button"
|
||||
onClick={props.onAddToStudioClicked}
|
||||
>
|
||||
Add to Studio
|
||||
</Button>,
|
||||
<AddToStudioModal
|
||||
isOpen={props.addToStudioOpen}
|
||||
key="add-to-studio-modal"
|
||||
studios={props.studios}
|
||||
onRequestClose={props.onAddToStudioClosed}
|
||||
onToggleStudio={props.onToggleStudio}
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
<Button className="action-button copy-link-button">
|
||||
Copy Link
|
||||
</Button>
|
||||
{(props.isLoggedIn && !props.userOwnsProject) &&
|
||||
<React.Fragment>
|
||||
<Button
|
||||
className="action-button report-button"
|
||||
key="report-button"
|
||||
onClick={props.onReportClicked}
|
||||
>
|
||||
Report
|
||||
</Button>,
|
||||
<ReportModal
|
||||
isOpen={props.reportOpen}
|
||||
key="report-modal"
|
||||
type="project"
|
||||
onReport={props.onReportSubmit}
|
||||
onRequestClose={props.onReportClose}
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
</FlexRow>
|
||||
</FlexRow>
|
||||
);
|
||||
};
|
||||
|
||||
Subactions.propTypes = {
|
||||
addToStudioOpen: PropTypes.bool,
|
||||
isLoggedIn: PropTypes.bool,
|
||||
onAddToStudioClicked: PropTypes.func,
|
||||
onAddToStudioClosed: PropTypes.func,
|
||||
onReportClicked: PropTypes.func.isRequired,
|
||||
onReportClose: PropTypes.func.isRequired,
|
||||
onReportSubmit: PropTypes.func.isRequired,
|
||||
onToggleStudio: PropTypes.func,
|
||||
projectInfo: projectShape,
|
||||
reportOpen: PropTypes.bool,
|
||||
studios: PropTypes.arrayOf(PropTypes.object),
|
||||
userOwnsProject: PropTypes.bool
|
||||
|
||||
};
|
||||
|
||||
module.exports = Subactions;
|
115
src/views/preview/subactions.scss
Normal file
115
src/views/preview/subactions.scss
Normal file
|
@ -0,0 +1,115 @@
|
|||
@import "../../colors";
|
||||
@import "../../frameless";
|
||||
|
||||
$small: "screen and (max-width : #{$mobile}-1)";
|
||||
$medium: "screen and (min-width : #{$mobile}) and (max-width : #{$tablet}-1)";
|
||||
$intermediate: "screen and (min-width : #{$tablet}) and (max-width : 941px)"; /* 941 because currently breakpoint of .inner in www is 941 */
|
||||
$medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||
|
||||
.subactions {
|
||||
margin-left: 1.5rem;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
flex: 1;
|
||||
|
||||
@media #{$medium-and-small} {
|
||||
margin-top: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.share-date {
|
||||
margin-right: .75rem;
|
||||
vertical-align: middle;
|
||||
line-height: 2rem;
|
||||
color: $type-gray;
|
||||
font-size: .875rem;
|
||||
|
||||
@media #{$small} {
|
||||
margin-top: 1rem;
|
||||
width: 100%;
|
||||
order: 100;
|
||||
}
|
||||
|
||||
.copyleft {
|
||||
display: inline-block;
|
||||
transform: scale(-1, 1);
|
||||
margin: 0;
|
||||
text-align: right;
|
||||
|
||||
@media #{$medium-and-small} {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
margin-top: 0;
|
||||
color: $type-white;
|
||||
font-size: .8rem;
|
||||
font-weight: 500;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.action-button {
|
||||
margin: 0 0 0 .5rem;
|
||||
border-radius: 19px;
|
||||
background-color: $ui-blue;
|
||||
padding: 0 .75rem;
|
||||
height: 2rem;
|
||||
text-decoration: none;
|
||||
line-height: .875rem;
|
||||
font-size: .75rem;
|
||||
font-weight: normal;
|
||||
|
||||
&.studio-button,
|
||||
&.copy-link-button,
|
||||
&.report-button {
|
||||
&:before {
|
||||
display: inline-block;
|
||||
margin-right: .25rem;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: contain;
|
||||
width: .875rem;
|
||||
height: .875rem;
|
||||
vertical-align: bottom;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
&.studio-button {
|
||||
&:before {
|
||||
background-image: url("/svgs/project/studio-add-white.svg");
|
||||
}
|
||||
}
|
||||
|
||||
&.copy-link-button {
|
||||
&:before {
|
||||
background-image: url("/svgs/project/copy-link-white.svg");
|
||||
}
|
||||
}
|
||||
|
||||
&.report-button {
|
||||
background-color: $ui-coral;
|
||||
|
||||
&:before {
|
||||
background-image: url("/svgs/project/report-white.svg");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.subactions, .subactions .action-buttons {
|
||||
@media #{$medium-and-small} {
|
||||
margin: 0;
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
& > div, .action-button {
|
||||
@media #{$medium-and-small} {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue