mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2025-02-17 08:31:23 -05:00
Merge branch 'develop' of https://github.com/LLK/scratch-www into develop
This commit is contained in:
commit
a224e82c41
20 changed files with 1022 additions and 537 deletions
|
@ -38,6 +38,29 @@ $desktop: 942px;
|
||||||
$tablet: 640px;
|
$tablet: 640px;
|
||||||
$mobile: 480px;
|
$mobile: 480px;
|
||||||
|
|
||||||
|
/* Media Queries */
|
||||||
|
|
||||||
|
/* Width */
|
||||||
|
/*
|
||||||
|
* ... small | medium | intermediate | big ...
|
||||||
|
* ... medium-and-smaller |
|
||||||
|
* ... intermediate-and-smaller |
|
||||||
|
*/
|
||||||
|
|
||||||
|
$small: "only screen and (max-width : #{$mobile}-1)";
|
||||||
|
$medium: "only screen and (min-width : #{$mobile}) and (max-width : #{$tablet}-1)";
|
||||||
|
$intermediate: "only screen and (min-width : #{$tablet}) and (max-width : #{$desktop}-1)";
|
||||||
|
$big: "only screen and (min-width : #{$desktop})";
|
||||||
|
|
||||||
|
$medium-and-smaller: "only screen and (max-width : #{$tablet}-1)";
|
||||||
|
$intermediate-and-smaller: "only screen and (max-width : #{$desktop}-1)";
|
||||||
|
|
||||||
|
/* Height */
|
||||||
|
|
||||||
|
$small-height: "only screen and (max-height : #{$mobile} - 1)";
|
||||||
|
$medium-height: "only screen and (min-height : #{$mobile}) and (max-height : #{$tablet} - 1)";
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Column-widths in a function, in ems
|
// Column-widths in a function, in ems
|
||||||
//
|
//
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.inplace-textarea {
|
.inplace-textarea {
|
||||||
transition: all 1s ease;
|
transition: all .2s ease;
|
||||||
border: 2px dashed $ui-blue-25percent;
|
border: 2px dashed $ui-blue-25percent;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-color: $ui-light-gray;
|
background-color: $ui-light-gray;
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
resize: none;
|
resize: none;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
transition: all 1s ease;
|
transition: all .2s ease;
|
||||||
outline: none;
|
outline: none;
|
||||||
border: 2px solid $ui-blue;
|
border: 2px solid $ui-blue;
|
||||||
box-shadow: 0 0 0 4px $ui-blue-25percent;
|
box-shadow: 0 0 0 4px $ui-blue-25percent;
|
||||||
|
|
|
@ -1,52 +1,41 @@
|
||||||
@import "../../../colors";
|
@import "../../../colors";
|
||||||
@import "../../../frameless";
|
@import "../../../frameless";
|
||||||
|
|
||||||
.mod-addToStudio * {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mod-addToStudio {
|
.mod-addToStudio {
|
||||||
margin: 100px auto;
|
|
||||||
outline: none;
|
|
||||||
padding: 0;
|
|
||||||
width: 36.25rem; /* 580px; */
|
|
||||||
height: 388px; /* 24.25rem; */
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
user-select: none;
|
|
||||||
|
@media #{$small}, #{$small-height} {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.addToStudio-modal-header {
|
.addToStudio-modal-header {
|
||||||
box-shadow: inset 0 -1px 0 0 $ui-blue-dark;
|
box-shadow: inset 0 -1px 0 0 $ui-blue-dark;
|
||||||
background-color: $ui-blue;
|
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 {
|
.addToStudio-modal-content {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
box-shadow: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: .875rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.studio-list-outer-scrollbox {
|
.studio-list-outer-scrollbox {
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: $ui-blue-10percent;
|
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 {
|
.studio-list-inner-scrollbox {
|
||||||
margin-right: .5rem;
|
margin-right: .5rem;
|
||||||
padding-right: .5rem;
|
padding-right: .5rem;
|
||||||
height: 16.9375rem;
|
height: 100%;
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
@ -93,35 +82,32 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: all .5s;
|
transition: all .5s;
|
||||||
margin: .21875rem .21875rem;
|
margin: .21875rem;
|
||||||
border-radius: .5rem;
|
border-radius: .5rem;
|
||||||
background-color: $ui-white;
|
background-color: $ui-white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: 16.1875rem; /* 259px */
|
width: 48%;
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
box-sizing: border-box;
|
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
@media #{$small} {
|
||||||
|
min-width: 98%;
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.studio-selector-button-text {
|
.studio-selector-button-text {
|
||||||
position: absolute;
|
margin: auto 2.18375rem auto .6875rem;
|
||||||
/* per spec, should be:
|
min-width: 0;
|
||||||
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 */
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
line-height: 1.25rem;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-family: "Helvetica Neue";
|
font-family: "Helvetica Neue";
|
||||||
font-size: .875rem;
|
font-size: .875rem;
|
||||||
font-weight: regular;
|
font-weight: regular;
|
||||||
|
flex-shrink: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.studio-selector-button-selected {
|
.studio-selector-button-selected {
|
||||||
|
@ -144,7 +130,7 @@
|
||||||
|
|
||||||
.studio-status-icon {
|
.studio-status-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin: .5rem .625rem .5rem 14.0625rem;
|
right: .625rem;
|
||||||
border-radius: .75rem;
|
border-radius: .75rem;
|
||||||
padding: .0625rem .075rem;
|
padding: .0625rem .075rem;
|
||||||
width: 1.5rem;
|
width: 1.5rem;
|
||||||
|
|
|
@ -37,70 +37,69 @@ const AddToStudioModalPresentation = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
useStandardSizes
|
||||||
className="mod-addToStudio"
|
className="mod-addToStudio"
|
||||||
contentLabel={contentLabel}
|
contentLabel={contentLabel}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onRequestClose={onRequestClose}
|
onRequestClose={onRequestClose}
|
||||||
>
|
>
|
||||||
<div>
|
<div className="addToStudio-modal-header modal-header">
|
||||||
<div className="addToStudio-modal-header">
|
<div className="addToStudio-content-label content-label">
|
||||||
<div className="addToStudio-content-label">
|
{contentLabel}
|
||||||
{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>
|
</div>
|
||||||
<div className="addToStudio-modal-content">
|
|
||||||
<div className="studio-list-outer-scrollbox">
|
|
||||||
<div className="studio-list-inner-scrollbox">
|
<Form
|
||||||
<div className="studio-list-container">
|
className="add-to-studio"
|
||||||
{studioButtons}
|
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>
|
||||||
<div className="studio-list-bottom-gradient" />
|
</Button>
|
||||||
</div>
|
{waitingToClose ? [
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<Form
|
|
||||||
className="add-to-studio"
|
|
||||||
onSubmit={onSubmit}
|
|
||||||
>
|
|
||||||
<FlexRow className="action-buttons">
|
|
||||||
<Button
|
<Button
|
||||||
className="action-button close-button white"
|
className="action-button submit-button submit-button-waiting"
|
||||||
key="closeButton"
|
disabled="disabled"
|
||||||
name="closeButton"
|
key="submitButton"
|
||||||
type="button"
|
type="submit"
|
||||||
onClick={onRequestClose}
|
|
||||||
>
|
>
|
||||||
<div className="action-button-text">
|
<div className="action-button-text">
|
||||||
<FormattedMessage id="general.close" />
|
<Spinner />
|
||||||
|
<FormattedMessage id="addToStudio.finishing" />
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
{waitingToClose ? [
|
] : [
|
||||||
<Button
|
<Button
|
||||||
className="action-button submit-button submit-button-waiting"
|
className="action-button submit-button"
|
||||||
disabled="disabled"
|
key="submitButton"
|
||||||
key="submitButton"
|
type="submit"
|
||||||
type="submit"
|
>
|
||||||
>
|
<div className="action-button-text">
|
||||||
<div className="action-button-text">
|
<FormattedMessage id="general.okay" />
|
||||||
<Spinner />
|
</div>
|
||||||
<FormattedMessage id="addToStudio.finishing" />
|
</Button>
|
||||||
</div>
|
]}
|
||||||
</Button>
|
</FlexRow>
|
||||||
] : [
|
</Form>
|
||||||
<Button
|
|
||||||
className="action-button submit-button"
|
|
||||||
key="submitButton"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
<div className="action-button-text">
|
|
||||||
<FormattedMessage id="general.okay" />
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
]}
|
|
||||||
</FlexRow>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
|
@ -26,8 +26,11 @@ class Modal extends React.Component {
|
||||||
return (
|
return (
|
||||||
<ReactModal
|
<ReactModal
|
||||||
appElement={document.getElementById('app')}
|
appElement={document.getElementById('app')}
|
||||||
|
bodyOpenClassName={this.props.useStandardSizes ? classNames('overflow-hidden') : null}
|
||||||
className={{
|
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),
|
afterOpen: classNames('modal-content', this.props.className),
|
||||||
beforeClose: classNames('modal-content', this.props.className)
|
beforeClose: classNames('modal-content', this.props.className)
|
||||||
}}
|
}}
|
||||||
|
@ -60,7 +63,8 @@ class Modal extends React.Component {
|
||||||
Modal.propTypes = {
|
Modal.propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
overlayClassName: PropTypes.string
|
overlayClassName: PropTypes.string,
|
||||||
|
useStandardSizes: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Modal;
|
module.exports = Modal;
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
@import "../../../colors";
|
@import "../../../colors";
|
||||||
@import "../../../frameless";
|
@import "../../../frameless";
|
||||||
|
|
||||||
|
.overflow-hidden {
|
||||||
|
/* to avoid double scroll bars this
|
||||||
|
gets added to body while modal is open */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 3.75rem auto;
|
margin: 3.75rem auto;
|
||||||
|
@ -10,9 +16,27 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: 48.75rem;
|
width: 48.75rem;
|
||||||
|
|
||||||
|
.modal-content { /* content inside of content */
|
||||||
|
display: flex;
|
||||||
|
border-radius: 0;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media #{$intermediate-and-smaller} {
|
||||||
|
margin-top: 0;
|
||||||
|
width: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media #{$small}, #{$small-height} {
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-overlay {
|
.modal-overlay {
|
||||||
|
@ -43,23 +67,6 @@ $modal-close-size: 1rem;
|
||||||
padding-top: $modal-close-size / 2;
|
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. */
|
/* Close button, Submit button, etc. */
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -68,6 +75,11 @@ $modal-close-size: 1rem;
|
||||||
justify-content: flex-end !important;
|
justify-content: flex-end !important;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
|
|
||||||
|
@media #{$intermediate-and-smaller} {
|
||||||
|
justify-content: center !important; //overwriting flex row properties
|
||||||
|
flex-direction: row !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* setting overall modal to contain overflow looks good, but isn't
|
/* setting overall modal to contain overflow looks good, but isn't
|
||||||
|
@ -108,3 +120,46 @@ row to appear to contain overflow. */
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
color: $type-white;
|
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}`});
|
const contentLabel = intl.formatMessage({id: `report.${type}`});
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
useStandardSizes
|
||||||
className="mod-report"
|
className="mod-report"
|
||||||
contentLabel={contentLabel}
|
contentLabel={contentLabel}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
|
@ -118,8 +119,8 @@ class ReportModal extends React.Component {
|
||||||
{...modalProps}
|
{...modalProps}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div className="report-modal-header">
|
<div className="report-modal-header modal-header">
|
||||||
<div className="report-content-label">
|
<div className="report-content-label content-label">
|
||||||
{contentLabel}
|
{contentLabel}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -130,7 +131,7 @@ class ReportModal extends React.Component {
|
||||||
onValid={this.handleValid}
|
onValid={this.handleValid}
|
||||||
onValidSubmit={onReport}
|
onValidSubmit={onReport}
|
||||||
>
|
>
|
||||||
<div className="report-modal-content">
|
<div className="report-modal-content modal-content">
|
||||||
{isConfirmed ? (
|
{isConfirmed ? (
|
||||||
<div className="received">
|
<div className="received">
|
||||||
<div className="received-header">
|
<div className="received-header">
|
||||||
|
|
|
@ -1,42 +1,15 @@
|
||||||
@import "../../../colors";
|
@import "../../../colors";
|
||||||
@import "../../../frameless";
|
@import "../../../frameless";
|
||||||
|
|
||||||
$medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
|
||||||
|
|
||||||
.mod-report * {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mod-report {
|
|
||||||
margin: 100px auto;
|
|
||||||
outline: none;
|
|
||||||
padding: 0;
|
|
||||||
width: 36.25rem; /* 580px; */
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.report-modal-header {
|
.report-modal-header {
|
||||||
border-radius: 1rem 1rem 0 0;
|
|
||||||
box-shadow: inset 0 -1px 0 0 $ui-coral-dark;
|
box-shadow: inset 0 -1px 0 0 $ui-coral-dark;
|
||||||
background-color: $ui-coral;
|
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 {
|
.report-modal-content {
|
||||||
margin: 1rem auto;
|
margin: 1rem auto;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
font-size: .875rem;
|
|
||||||
|
|
||||||
.instructions {
|
.instructions {
|
||||||
line-height: 1.5rem;
|
line-height: 1.5rem;
|
||||||
|
@ -73,7 +46,7 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
color: $type-white;
|
color: $type-white;
|
||||||
|
|
||||||
@media #{$medium-and-small} {
|
@media #{$medium-and-smaller} {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-top: calc($arrow-border-width / 2);
|
margin-top: calc($arrow-border-width / 2);
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
@ -98,7 +71,7 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||||
|
|
||||||
content: "";
|
content: "";
|
||||||
|
|
||||||
@media #{$medium-and-small} {
|
@media #{$medium-and-smaller} {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import ScratchStorage from 'scratch-storage';
|
import ScratchStorage from 'scratch-storage';
|
||||||
|
|
||||||
const PROJECT_SERVER = 'https://projects.scratch.mit.edu';
|
const PROJECT_HOST = process.env.PROJECT_HOST || 'https://projects.scratch.mit.edu';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper for ScratchStorage which adds default web sources.
|
* Wrapper for ScratchStorage which adds default web sources.
|
||||||
|
@ -14,8 +14,8 @@ class Storage extends ScratchStorage {
|
||||||
projectAsset => {
|
projectAsset => {
|
||||||
const [projectId, revision] = projectAsset.assetId.split('.');
|
const [projectId, revision] = projectAsset.assetId.split('.');
|
||||||
return revision ?
|
return revision ?
|
||||||
`${PROJECT_SERVER}/internalapi/project/${projectId}/get/${revision}` :
|
`${PROJECT_HOST}/internalapi/project/${projectId}/get/${revision}` :
|
||||||
`${PROJECT_SERVER}/internalapi/project/${projectId}/get/`;
|
`${PROJECT_HOST}/internalapi/project/${projectId}/get/`;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,6 +86,43 @@ module.exports.previewReducer = (state, action) => {
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
comments: [...state.comments, ...action.items] // TODO: consider a different way of doing this?
|
comments: [...state.comments, ...action.items] // TODO: consider a different way of doing this?
|
||||||
});
|
});
|
||||||
|
case 'SET_COMMENT_DELETED':
|
||||||
|
if (action.topLevelCommentId) {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
replies: Object.assign({}, state.replies, {
|
||||||
|
[action.topLevelCommentId]: state.replies[action.topLevelCommentId].map(comment => {
|
||||||
|
if (comment.id === action.commentId) {
|
||||||
|
return Object.assign({}, comment, {deleted: true});
|
||||||
|
}
|
||||||
|
return comment;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
comments: state.comments.map(comment => {
|
||||||
|
if (comment.id === action.commentId) {
|
||||||
|
return Object.assign({}, comment, {deleted: true});
|
||||||
|
}
|
||||||
|
return comment;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
case 'ADD_NEW_COMMENT':
|
||||||
|
if (action.topLevelCommentId) {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
replies: Object.assign({}, state.replies, {
|
||||||
|
// Replies to comments go at the end of the thread
|
||||||
|
[action.topLevelCommentId]: state.replies[action.topLevelCommentId].concat(action.comment)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reply to the top level project, put the reply at the beginning
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
comments: [action.comment, ...state.comments],
|
||||||
|
replies: Object.assign({}, state.replies, {[action.comment.id]: []})
|
||||||
|
});
|
||||||
case 'SET_REPLIES':
|
case 'SET_REPLIES':
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
replies: merge({}, state.replies, action.replies)
|
replies: merge({}, state.replies, action.replies)
|
||||||
|
@ -191,6 +228,18 @@ module.exports.setStudioFetchStatus = (studioId, status) => ({
|
||||||
status: status
|
status: status
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module.exports.setCommentDeleted = (commentId, topLevelCommentId) => ({
|
||||||
|
type: 'SET_COMMENT_DELETED',
|
||||||
|
commentId: commentId,
|
||||||
|
topLevelCommentId: topLevelCommentId
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.addNewComment = (comment, topLevelCommentId) => ({
|
||||||
|
type: 'ADD_NEW_COMMENT',
|
||||||
|
comment: comment,
|
||||||
|
topLevelCommentId: topLevelCommentId
|
||||||
|
});
|
||||||
|
|
||||||
module.exports.getProjectInfo = (id, token) => (dispatch => {
|
module.exports.getProjectInfo = (id, token) => (dispatch => {
|
||||||
const opts = {
|
const opts = {
|
||||||
uri: `/projects/${id}`
|
uri: `/projects/${id}`
|
||||||
|
@ -562,6 +611,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 => {
|
module.exports.reportProject = (id, jsonData) => (dispatch => {
|
||||||
dispatch(module.exports.setFetchStatus('report', module.exports.Status.FETCHING));
|
dispatch(module.exports.setFetchStatus('report', module.exports.Status.FETCHING));
|
||||||
// scratchr2 will fail if no thumbnail base64 string provided. We don't yet have
|
// scratchr2 will fail if no thumbnail base64 string provided. We don't yet have
|
||||||
|
|
|
@ -1,59 +1,122 @@
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const PropTypes = require('prop-types');
|
const PropTypes = require('prop-types');
|
||||||
|
const bindAll = require('lodash.bindall');
|
||||||
|
const classNames = require('classnames');
|
||||||
|
|
||||||
const FlexRow = require('../../../components/flex-row/flex-row.jsx');
|
const FlexRow = require('../../../components/flex-row/flex-row.jsx');
|
||||||
const Avatar = require('../../../components/avatar/avatar.jsx');
|
const Avatar = require('../../../components/avatar/avatar.jsx');
|
||||||
const FormattedRelative = require('react-intl').FormattedRelative;
|
const FormattedRelative = require('react-intl').FormattedRelative;
|
||||||
|
const ComposeComment = require('./compose-comment.jsx');
|
||||||
|
|
||||||
require('./comment.scss');
|
require('./comment.scss');
|
||||||
|
|
||||||
const Comment = ({
|
class Comment extends React.Component {
|
||||||
author,
|
constructor (props) {
|
||||||
content,
|
super(props);
|
||||||
datetimeCreated,
|
bindAll(this, [
|
||||||
id
|
'handleDelete',
|
||||||
}) => (
|
'handlePostReply',
|
||||||
<div
|
'handleToggleReplying'
|
||||||
className="flex-row comment"
|
]);
|
||||||
id={`comments-${id}`}
|
this.state = {
|
||||||
>
|
replying: false
|
||||||
<a href={`/users/${author.username}`}>
|
};
|
||||||
<Avatar src={author.image} />
|
}
|
||||||
</a>
|
|
||||||
<FlexRow className="comment-body column">
|
handlePostReply (comment) {
|
||||||
<FlexRow className="comment-top-row">
|
this.setState({replying: false});
|
||||||
<a
|
this.props.onAddComment(comment);
|
||||||
className="username"
|
}
|
||||||
href={`/users/${author.username}`}
|
|
||||||
>{author.username}</a>
|
handleToggleReplying () {
|
||||||
<div className="action-list">
|
this.setState({replying: !this.state.replying});
|
||||||
{/* TODO: Hook these up to API calls/logic */}
|
}
|
||||||
<span className="comment-delete">Delete</span>
|
|
||||||
<span className="comment-report">Report</span>
|
handleDelete () {
|
||||||
</div>
|
this.props.onDelete(this.props.id);
|
||||||
</FlexRow>
|
}
|
||||||
<div className="comment-bubble">
|
|
||||||
{/* TODO: at the moment, comment content does not properly display
|
render () {
|
||||||
* emojis/easter eggs
|
const {
|
||||||
* @user links in replies
|
author,
|
||||||
* links to scratch.mit.edu pages
|
deletable,
|
||||||
*/}
|
deleted,
|
||||||
<span className="comment-content">{content}</span>
|
canReply,
|
||||||
<FlexRow className="comment-bottom-row">
|
content,
|
||||||
<span className="comment-time">
|
datetimeCreated,
|
||||||
<FormattedRelative value={new Date(datetimeCreated)} />
|
id,
|
||||||
</span>
|
projectId
|
||||||
<a
|
} = this.props;
|
||||||
className="comment-reply"
|
|
||||||
href={`#comments-${id}`}
|
return (
|
||||||
|
<div
|
||||||
|
className="flex-row comment"
|
||||||
|
id={`comments-${id}`}
|
||||||
|
>
|
||||||
|
<a href={`/users/${author.username}`}>
|
||||||
|
<Avatar src={author.image} />
|
||||||
|
</a>
|
||||||
|
<FlexRow className="comment-body column">
|
||||||
|
<FlexRow className="comment-top-row">
|
||||||
|
<a
|
||||||
|
className="username"
|
||||||
|
href={`/users/${author.username}`}
|
||||||
|
>{author.username}</a>
|
||||||
|
<div className="action-list">
|
||||||
|
{deletable ? (
|
||||||
|
<span
|
||||||
|
className="comment-delete"
|
||||||
|
onClick={this.handleDelete}
|
||||||
|
>
|
||||||
|
Delete {/* TODO internationalize */}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
<span className="comment-report">
|
||||||
|
Report {/* TODO internationalize */}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</FlexRow>
|
||||||
|
<div
|
||||||
|
className={classNames({
|
||||||
|
'comment-bubble': true,
|
||||||
|
'comment-bubble-deleted': deleted
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
reply
|
{/* TODO: at the moment, comment content does not properly display
|
||||||
</a>
|
* emojis/easter eggs
|
||||||
|
* @user links in replies
|
||||||
|
* links to scratch.mit.edu pages
|
||||||
|
*/}
|
||||||
|
<span className="comment-content">{content}</span>
|
||||||
|
<FlexRow className="comment-bottom-row">
|
||||||
|
<span className="comment-time">
|
||||||
|
<FormattedRelative value={new Date(datetimeCreated)} />
|
||||||
|
</span>
|
||||||
|
{canReply ? (
|
||||||
|
<span
|
||||||
|
className="comment-reply"
|
||||||
|
onClick={this.handleToggleReplying}
|
||||||
|
>
|
||||||
|
reply
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</FlexRow>
|
||||||
|
</div>
|
||||||
|
{this.state.replying ? (
|
||||||
|
<FlexRow className="comment-reply-row">
|
||||||
|
<ComposeComment
|
||||||
|
parentId={id}
|
||||||
|
projectId={projectId}
|
||||||
|
onAddComment={this.handlePostReply}
|
||||||
|
onCancel={this.handleToggleReplying}
|
||||||
|
/>
|
||||||
|
</FlexRow>
|
||||||
|
) : null}
|
||||||
</FlexRow>
|
</FlexRow>
|
||||||
</div>
|
</div>
|
||||||
</FlexRow>
|
);
|
||||||
</div>
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
Comment.propTypes = {
|
Comment.propTypes = {
|
||||||
author: PropTypes.shape({
|
author: PropTypes.shape({
|
||||||
|
@ -61,9 +124,15 @@ Comment.propTypes = {
|
||||||
image: PropTypes.string,
|
image: PropTypes.string,
|
||||||
username: PropTypes.string
|
username: PropTypes.string
|
||||||
}),
|
}),
|
||||||
|
canReply: PropTypes.bool,
|
||||||
content: PropTypes.string,
|
content: PropTypes.string,
|
||||||
datetimeCreated: PropTypes.string,
|
datetimeCreated: PropTypes.string,
|
||||||
id: PropTypes.number
|
deletable: PropTypes.bool,
|
||||||
|
deleted: PropTypes.bool,
|
||||||
|
id: PropTypes.number,
|
||||||
|
onAddComment: PropTypes.func,
|
||||||
|
onDelete: PropTypes.func,
|
||||||
|
projectId: PropTypes.number
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Comment;
|
module.exports = Comment;
|
||||||
|
|
|
@ -1,8 +1,26 @@
|
||||||
@import "../../../colors";
|
@import "../../../colors";
|
||||||
|
|
||||||
.compose-comment {
|
.compose-comment {
|
||||||
|
margin-left: .5rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
.compose-error-row {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
.compose-error-tip {
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
border: 1px solid $active-gray;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: $ui-orange;
|
||||||
|
padding: .25rem;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
color: $type-white;
|
||||||
|
font-size: .85rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.textarea-row {
|
.textarea-row {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
@ -131,6 +149,19 @@
|
||||||
height: 9px;
|
height: 9px;
|
||||||
content: "";
|
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 {
|
.comment-content {
|
||||||
|
@ -148,6 +179,9 @@
|
||||||
|
|
||||||
.comment-reply {
|
.comment-reply {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
cursor: pointer;
|
||||||
|
color: $ui-blue;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
&:after {
|
&:after {
|
||||||
margin-left: .25rem;
|
margin-left: .25rem;
|
||||||
|
@ -184,6 +218,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comments-root-reply {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-reply-row {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-left: .5rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.expand-thread {
|
.expand-thread {
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -1,26 +1,203 @@
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
const PropTypes = require('prop-types');
|
||||||
|
const bindAll = require('lodash.bindall');
|
||||||
|
const classNames = require('classnames');
|
||||||
|
const keyMirror = require('keymirror');
|
||||||
|
|
||||||
const FlexRow = require('../../../components/flex-row/flex-row.jsx');
|
const FlexRow = require('../../../components/flex-row/flex-row.jsx');
|
||||||
|
const Avatar = require('../../../components/avatar/avatar.jsx');
|
||||||
const InplaceInput = require('../../../components/forms/inplace-input.jsx');
|
const InplaceInput = require('../../../components/forms/inplace-input.jsx');
|
||||||
const Button = require('../../../components/forms/button.jsx');
|
const Button = require('../../../components/forms/button.jsx');
|
||||||
|
|
||||||
|
const connect = require('react-redux').connect;
|
||||||
|
|
||||||
|
const api = require('../../../lib/api');
|
||||||
|
|
||||||
require('./comment.scss');
|
require('./comment.scss');
|
||||||
|
|
||||||
const onUpdate = update => update;
|
const onUpdate = update => update;
|
||||||
|
|
||||||
const ComposeComment = () => (
|
const MAX_COMMENT_LENGTH = 500;
|
||||||
<FlexRow className="compose-comment column">
|
|
||||||
<InplaceInput
|
|
||||||
handleUpdate={onUpdate}
|
|
||||||
name="compose-comment"
|
|
||||||
type="textarea"
|
|
||||||
/>
|
|
||||||
<FlexRow className="compose-bottom-row">
|
|
||||||
<Button className="compose-post">Post</Button>
|
|
||||||
<Button className="compose-cancel">Cancel</Button>
|
|
||||||
<span className="compose-limit">500 characters left</span>
|
|
||||||
</FlexRow>
|
|
||||||
</FlexRow>
|
|
||||||
);
|
|
||||||
|
|
||||||
module.exports = ComposeComment;
|
const ComposeStatus = keyMirror({
|
||||||
|
EDITING: null,
|
||||||
|
SUBMITTING: null,
|
||||||
|
REJECTED: null
|
||||||
|
});
|
||||||
|
|
||||||
|
/* TODO translations */
|
||||||
|
const CommentErrorMessages = {
|
||||||
|
isEmpty: "You can't post an empty comment",
|
||||||
|
isFlood: "Woah, seems like you're commenting really quickly. Please wait longer between posts.",
|
||||||
|
isBad: 'Hmm...the bad word detector thinks there is a problem with your comment. ' +
|
||||||
|
'Please change it and remember to be respectful.',
|
||||||
|
hasChatSite: 'Uh oh! This comment contains a link to a website with unmoderated chat.' +
|
||||||
|
'For safety reasons, please do not link to these sites!',
|
||||||
|
isSpam: "Hmm, seems like you've posted the same comment a bunch of times. Please don't spam.",
|
||||||
|
isMuted: "Hmm, the filterbot is pretty sure your recent comments weren't ok for Scratch, " +
|
||||||
|
'so your account has been muted for the rest of the day. :/',
|
||||||
|
isUnconstructive: 'Hmm, the filterbot thinks your comment may be mean or disrespectful. ' +
|
||||||
|
'Remember, most projects on Scratch are made by people who are just learning how to program.',
|
||||||
|
isDisallowed: 'Hmm, it looks like comments have been turned off for this page. :/',
|
||||||
|
// TODO implement the special modal for ip mute bans that includes links to appeals
|
||||||
|
// this is just a stub of the actual message
|
||||||
|
isIPMuted: 'Sorry, the Scratch Team had to prevent your network from sharing comments or ' +
|
||||||
|
'projects because it was used to break our community guidelines too many times.' +
|
||||||
|
'You can still share comments and projects from another network.',
|
||||||
|
isTooLong: "That's too long! Please find a way to shorten your text.",
|
||||||
|
error: 'Oops! Something went wrong'
|
||||||
|
};
|
||||||
|
|
||||||
|
class ComposeComment extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
bindAll(this, [
|
||||||
|
'handlePost',
|
||||||
|
'handleCancel',
|
||||||
|
'handleInput'
|
||||||
|
]);
|
||||||
|
this.state = {
|
||||||
|
message: '',
|
||||||
|
status: ComposeStatus.EDITING,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
handleInput (event) {
|
||||||
|
this.setState({
|
||||||
|
message: event.target.value,
|
||||||
|
status: ComposeStatus.EDITING,
|
||||||
|
error: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
handlePost () {
|
||||||
|
this.setState({status: ComposeStatus.SUBMITTING});
|
||||||
|
api({
|
||||||
|
uri: `/proxy/comments/project/${this.props.projectId}`,
|
||||||
|
authentication: this.props.user.token,
|
||||||
|
withCredentials: true,
|
||||||
|
method: 'POST',
|
||||||
|
useCsrf: true,
|
||||||
|
json: {
|
||||||
|
content: this.state.message,
|
||||||
|
parent_id: this.props.parentId || '',
|
||||||
|
comentee_id: this.props.comenteeId || ''
|
||||||
|
}
|
||||||
|
}, (err, body, res) => {
|
||||||
|
if (err || res.statusCode !== 200) {
|
||||||
|
body = {rejected: 'error'};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body.rejected && this.state.status === ComposeStatus.SUBMITTING) {
|
||||||
|
// Note: does not reset the message state
|
||||||
|
this.setState({
|
||||||
|
status: ComposeStatus.REJECTED,
|
||||||
|
// If there is a special error message for the rejected reason,
|
||||||
|
// use it. Otherwise, use the generic 'error' ("Oops!...")
|
||||||
|
error: CommentErrorMessages[body.rejected] ?
|
||||||
|
body.rejected : 'error'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the text field and reset status on successful submission
|
||||||
|
this.setState({
|
||||||
|
message: '',
|
||||||
|
status: ComposeStatus.EDITING,
|
||||||
|
error: null
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add the username, which isn't included right now from scratch-api
|
||||||
|
if (body.author) body.author.username = this.props.user.username;
|
||||||
|
|
||||||
|
this.props.onAddComment(body);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
handleCancel () {
|
||||||
|
this.setState({
|
||||||
|
message: '',
|
||||||
|
status: ComposeStatus.EDITING,
|
||||||
|
error: null
|
||||||
|
});
|
||||||
|
if (this.props.onCancel) this.props.onCancel();
|
||||||
|
}
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex-row comment"
|
||||||
|
>
|
||||||
|
<a href={`/users/${this.props.user.username}`}>
|
||||||
|
<Avatar src={this.props.user.thumbnailUrl} />
|
||||||
|
</a>
|
||||||
|
<FlexRow className="compose-comment column">
|
||||||
|
{this.state.error ? (
|
||||||
|
<FlexRow className="compose-error-row">
|
||||||
|
<div className="compose-error-tip">
|
||||||
|
{CommentErrorMessages[this.state.error] || 'Unknown error'}
|
||||||
|
</div>
|
||||||
|
</FlexRow>
|
||||||
|
) : null}
|
||||||
|
<InplaceInput
|
||||||
|
className={classNames('compose-input',
|
||||||
|
MAX_COMMENT_LENGTH - this.state.message.length >= 0 ? 'compose-valid' : 'compose-invalid')}
|
||||||
|
handleUpdate={onUpdate}
|
||||||
|
name="compose-comment"
|
||||||
|
type="textarea"
|
||||||
|
value={this.state.message}
|
||||||
|
onInput={this.handleInput}
|
||||||
|
/>
|
||||||
|
<FlexRow className="compose-bottom-row">
|
||||||
|
<Button
|
||||||
|
className="compose-post"
|
||||||
|
disabled={this.state.status === ComposeStatus.SUBMITTING}
|
||||||
|
onClick={this.handlePost}
|
||||||
|
>
|
||||||
|
{this.state.status === ComposeStatus.SUBMITTING ? (
|
||||||
|
'Posting...' /* TODO internationalize */
|
||||||
|
) : (
|
||||||
|
'Post' /* TODO internationalize */
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="compose-cancel"
|
||||||
|
onClick={this.handleCancel}
|
||||||
|
>
|
||||||
|
Cancel {/* TODO internationalize */}
|
||||||
|
</Button>
|
||||||
|
<span
|
||||||
|
className={classNames('compose-limit',
|
||||||
|
MAX_COMMENT_LENGTH - this.state.message.length >= 0 ?
|
||||||
|
'compose-valid' : 'compose-invalid')}
|
||||||
|
>
|
||||||
|
{/* TODO internationalize */}
|
||||||
|
{MAX_COMMENT_LENGTH - this.state.message.length} characters left
|
||||||
|
</span>
|
||||||
|
</FlexRow>
|
||||||
|
</FlexRow>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ComposeComment.propTypes = {
|
||||||
|
comenteeId: PropTypes.number,
|
||||||
|
onAddComment: PropTypes.func,
|
||||||
|
onCancel: PropTypes.func,
|
||||||
|
parentId: PropTypes.number,
|
||||||
|
projectId: PropTypes.number,
|
||||||
|
user: PropTypes.shape({
|
||||||
|
id: PropTypes.number,
|
||||||
|
username: PropTypes.string,
|
||||||
|
token: PropTypes.string,
|
||||||
|
thumbnailUrl: PropTypes.string
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
user: state.session.session.user
|
||||||
|
});
|
||||||
|
|
||||||
|
const ConnectedComposeComment = connect(
|
||||||
|
mapStateToProps
|
||||||
|
)(ComposeComment);
|
||||||
|
|
||||||
|
module.exports = ConnectedComposeComment;
|
||||||
|
|
|
@ -12,7 +12,9 @@ class TopLevelComment extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props);
|
super(props);
|
||||||
bindAll(this, [
|
bindAll(this, [
|
||||||
'handleExpandThread'
|
'handleExpandThread',
|
||||||
|
'handleAddComment',
|
||||||
|
'handleDeleteReply'
|
||||||
]);
|
]);
|
||||||
this.state = {
|
this.state = {
|
||||||
expanded: false
|
expanded: false
|
||||||
|
@ -25,18 +27,37 @@ class TopLevelComment extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleDeleteReply (commentId) {
|
||||||
|
// Only apply topLevelCommentId for deleting replies
|
||||||
|
// The top level comment itself just gets passed onDelete directly
|
||||||
|
this.props.onDelete(commentId, this.props.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAddComment (comment) {
|
||||||
|
this.props.onAddComment(comment, this.props.id);
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
author,
|
author,
|
||||||
|
canReply,
|
||||||
content,
|
content,
|
||||||
datetimeCreated,
|
datetimeCreated,
|
||||||
|
deletable,
|
||||||
|
deleted,
|
||||||
id,
|
id,
|
||||||
replies
|
onDelete,
|
||||||
|
replies,
|
||||||
|
projectId
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FlexRow className="comment-container">
|
<FlexRow className="comment-container">
|
||||||
<Comment {...{author, content, datetimeCreated, id}} />
|
<Comment
|
||||||
|
projectId={projectId}
|
||||||
|
onAddComment={this.handleAddComment}
|
||||||
|
{...{author, content, datetimeCreated, deletable, deleted, canReply, id, onDelete}}
|
||||||
|
/>
|
||||||
{replies.length > 0 &&
|
{replies.length > 0 &&
|
||||||
<FlexRow
|
<FlexRow
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
@ -49,10 +70,16 @@ class TopLevelComment extends React.Component {
|
||||||
{(this.state.expanded ? replies : replies.slice(0, 3)).map(reply => (
|
{(this.state.expanded ? replies : replies.slice(0, 3)).map(reply => (
|
||||||
<Comment
|
<Comment
|
||||||
author={reply.author}
|
author={reply.author}
|
||||||
|
canReply={canReply}
|
||||||
content={reply.content}
|
content={reply.content}
|
||||||
datetimeCreated={reply.datetime_created}
|
datetimeCreated={reply.datetime_created}
|
||||||
|
deletable={deletable}
|
||||||
|
deleted={reply.deleted}
|
||||||
id={reply.id}
|
id={reply.id}
|
||||||
key={reply.id}
|
key={reply.id}
|
||||||
|
projectId={projectId}
|
||||||
|
onAddComment={this.handleAddComment}
|
||||||
|
onDelete={this.handleDeleteReply}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</FlexRow>
|
</FlexRow>
|
||||||
|
@ -74,9 +101,14 @@ TopLevelComment.propTypes = {
|
||||||
image: PropTypes.string,
|
image: PropTypes.string,
|
||||||
username: PropTypes.string
|
username: PropTypes.string
|
||||||
}),
|
}),
|
||||||
|
canReply: PropTypes.bool,
|
||||||
content: PropTypes.string,
|
content: PropTypes.string,
|
||||||
datetimeCreated: PropTypes.string,
|
datetimeCreated: PropTypes.string,
|
||||||
|
deletable: PropTypes.bool,
|
||||||
|
deleted: PropTypes.bool,
|
||||||
id: PropTypes.number,
|
id: PropTypes.number,
|
||||||
|
onAddComment: PropTypes.func,
|
||||||
|
onDelete: PropTypes.func,
|
||||||
parentId: PropTypes.number,
|
parentId: PropTypes.number,
|
||||||
projectId: PropTypes.string,
|
projectId: PropTypes.string,
|
||||||
replies: PropTypes.arrayOf(PropTypes.object)
|
replies: PropTypes.arrayOf(PropTypes.object)
|
||||||
|
|
|
@ -21,6 +21,7 @@ const StudioList = require('./studio-list.jsx');
|
||||||
const Subactions = require('./subactions.jsx');
|
const Subactions = require('./subactions.jsx');
|
||||||
const InplaceInput = require('../../components/forms/inplace-input.jsx');
|
const InplaceInput = require('../../components/forms/inplace-input.jsx');
|
||||||
const TopLevelComment = require('./comment/top-level-comment.jsx');
|
const TopLevelComment = require('./comment/top-level-comment.jsx');
|
||||||
|
const ComposeComment = require('./comment/compose-comment.jsx');
|
||||||
const ExtensionChip = require('./extension-chip.jsx');
|
const ExtensionChip = require('./extension-chip.jsx');
|
||||||
|
|
||||||
const projectShape = require('./projectshape.jsx').projectShape;
|
const projectShape = require('./projectshape.jsx').projectShape;
|
||||||
|
@ -64,6 +65,8 @@ const PreviewPresentation = ({
|
||||||
projectStudios,
|
projectStudios,
|
||||||
studios,
|
studios,
|
||||||
userOwnsProject,
|
userOwnsProject,
|
||||||
|
onAddComment,
|
||||||
|
onDeleteComment,
|
||||||
onFavoriteClicked,
|
onFavoriteClicked,
|
||||||
onLoadMore,
|
onLoadMore,
|
||||||
onLoveClicked,
|
onLoveClicked,
|
||||||
|
@ -75,80 +78,197 @@ const PreviewPresentation = ({
|
||||||
onToggleStudio,
|
onToggleStudio,
|
||||||
onSeeInside,
|
onSeeInside,
|
||||||
onUpdate
|
onUpdate
|
||||||
}) => (
|
}) => {
|
||||||
<div className="preview">
|
const shareDate = ((projectInfo.history && projectInfo.history.shared)) ? projectInfo.history.shared : '';
|
||||||
<ShareBanner shared={isShared} />
|
return (
|
||||||
{ projectInfo && projectInfo.author && projectInfo.author.id && (
|
<div className="preview">
|
||||||
<Formsy onKeyPress={onKeyPress}>
|
<ShareBanner shared={isShared} />
|
||||||
<div className="inner">
|
{ projectInfo && projectInfo.author && projectInfo.author.id && (
|
||||||
<FlexRow className="preview-row wrap-to-col">
|
<Formsy onKeyPress={onKeyPress}>
|
||||||
<FlexRow className="project-header">
|
<div className="inner">
|
||||||
<a href={`/users/${projectInfo.author.username}`}>
|
<FlexRow className="preview-row force-row">
|
||||||
<Avatar
|
<FlexRow className="project-header">
|
||||||
alt={projectInfo.author.username}
|
<a href={`/users/${projectInfo.author.username}`}>
|
||||||
src={`https://cdn2.scratch.mit.edu/get_image/user/${projectInfo.author.id}_48x48.png`}
|
<Avatar
|
||||||
/>
|
alt={projectInfo.author.username}
|
||||||
</a>
|
src={`https://cdn2.scratch.mit.edu/get_image/user/${projectInfo.author.id}_48x48.png`}
|
||||||
<div className="title">
|
/>
|
||||||
{editable ?
|
</a>
|
||||||
<InplaceInput
|
<div className="title">
|
||||||
className="project-title"
|
{editable ?
|
||||||
handleUpdate={onUpdate}
|
<InplaceInput
|
||||||
name="title"
|
className="project-title"
|
||||||
validationErrors={{
|
handleUpdate={onUpdate}
|
||||||
maxLength: intl.formatMessage({
|
name="title"
|
||||||
id: 'preview.titleMaxLength'
|
validationErrors={{
|
||||||
})
|
maxLength: intl.formatMessage({
|
||||||
}}
|
id: 'preview.titleMaxLength'
|
||||||
validations={{
|
})
|
||||||
maxLength: 100
|
}}
|
||||||
}}
|
validations={{
|
||||||
value={projectInfo.title}
|
maxLength: 100
|
||||||
/> :
|
}}
|
||||||
<React.Fragment>
|
value={projectInfo.title}
|
||||||
<div
|
/> :
|
||||||
className="project-title no-edit"
|
<React.Fragment>
|
||||||
title={projectInfo.title}
|
<div
|
||||||
>{projectInfo.title}</div>
|
className="project-title no-edit"
|
||||||
{'by '}
|
title={projectInfo.title}
|
||||||
<a href={`/users/${projectInfo.author.username}`}>
|
>{projectInfo.title}</div>
|
||||||
{projectInfo.author.username}
|
{'by '}
|
||||||
</a>
|
<a href={`/users/${projectInfo.author.username}`}>
|
||||||
</React.Fragment>
|
{projectInfo.author.username}
|
||||||
}
|
</a>
|
||||||
</div>
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</FlexRow>
|
||||||
|
<MediaQuery minWidth={frameless.mobile}>
|
||||||
|
<div className="project-buttons">
|
||||||
|
{/* TODO: Hide Remix button for now until implemented */}
|
||||||
|
{(!userOwnsProject && false) &&
|
||||||
|
<Button className="button remix-button">
|
||||||
|
Remix
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
<Button
|
||||||
|
className="button see-inside-button"
|
||||||
|
onClick={onSeeInside}
|
||||||
|
>
|
||||||
|
See Inside
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</MediaQuery>
|
||||||
</FlexRow>
|
</FlexRow>
|
||||||
<div className="project-buttons">
|
<FlexRow className="preview-row">
|
||||||
{/* TODO: Hide Remix button for now until implemented */}
|
<div className="guiPlayer">
|
||||||
{(!userOwnsProject && false) &&
|
<IntlGUI
|
||||||
<Button className="button remix-button">
|
isPlayerOnly
|
||||||
Remix
|
assetHost={assetHost}
|
||||||
</Button>
|
backpackOptions={backpackOptions}
|
||||||
}
|
basePath="/"
|
||||||
<Button
|
className="guiPlayer"
|
||||||
className="button see-inside-button"
|
isFullScreen={isFullScreen}
|
||||||
onClick={onSeeInside}
|
previewInfoVisible="false"
|
||||||
>
|
projectHost={projectHost}
|
||||||
See Inside
|
projectId={projectId}
|
||||||
</Button>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</FlexRow>
|
<MediaQuery maxWidth={frameless.tablet - 1}>
|
||||||
<FlexRow className="preview-row">
|
<FlexRow className="preview-row force-center">
|
||||||
<div className="guiPlayer">
|
<Stats
|
||||||
<IntlGUI
|
faved={faved}
|
||||||
isPlayerOnly
|
favoriteCount={favoriteCount}
|
||||||
assetHost={assetHost}
|
loveCount={loveCount}
|
||||||
backpackOptions={backpackOptions}
|
loved={loved}
|
||||||
basePath="/"
|
projectInfo={projectInfo}
|
||||||
className="guiPlayer"
|
onFavoriteClicked={onFavoriteClicked}
|
||||||
isFullScreen={isFullScreen}
|
onLoveClicked={onLoveClicked}
|
||||||
previewInfoVisible="false"
|
/>
|
||||||
projectHost={projectHost}
|
<Subactions
|
||||||
projectId={projectId}
|
addToStudioOpen={addToStudioOpen}
|
||||||
/>
|
isLoggedIn={isLoggedIn}
|
||||||
</div>
|
projectInfo={projectInfo}
|
||||||
<MediaQuery maxWidth={frameless.tablet - 1}>
|
reportOpen={reportOpen}
|
||||||
<FlexRow className="preview-row force-center">
|
shareDate={shareDate}
|
||||||
|
studios={studios}
|
||||||
|
userOwnsProject={userOwnsProject}
|
||||||
|
onAddToStudioClicked={onAddToStudioClicked}
|
||||||
|
onAddToStudioClosed={onAddToStudioClosed}
|
||||||
|
onReportClicked={onReportClicked}
|
||||||
|
onReportClose={onReportClose}
|
||||||
|
onReportSubmit={onReportSubmit}
|
||||||
|
onToggleStudio={onToggleStudio}
|
||||||
|
/>
|
||||||
|
</FlexRow>
|
||||||
|
</MediaQuery>
|
||||||
|
<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>
|
||||||
|
<MediaQuery minWidth={frameless.tablet}>
|
||||||
|
<FlexRow className="preview-row">
|
||||||
<Stats
|
<Stats
|
||||||
faved={faved}
|
faved={faved}
|
||||||
favoriteCount={favoriteCount}
|
favoriteCount={favoriteCount}
|
||||||
|
@ -163,6 +283,7 @@ const PreviewPresentation = ({
|
||||||
isLoggedIn={isLoggedIn}
|
isLoggedIn={isLoggedIn}
|
||||||
projectInfo={projectInfo}
|
projectInfo={projectInfo}
|
||||||
reportOpen={reportOpen}
|
reportOpen={reportOpen}
|
||||||
|
shareDate={shareDate}
|
||||||
studios={studios}
|
studios={studios}
|
||||||
userOwnsProject={userOwnsProject}
|
userOwnsProject={userOwnsProject}
|
||||||
onAddToStudioClicked={onAddToStudioClicked}
|
onAddToStudioClicked={onAddToStudioClicked}
|
||||||
|
@ -174,176 +295,80 @@ const PreviewPresentation = ({
|
||||||
/>
|
/>
|
||||||
</FlexRow>
|
</FlexRow>
|
||||||
</MediaQuery>
|
</MediaQuery>
|
||||||
<FlexRow className="project-notes">
|
<MediaQuery minWidth={frameless.tablet}>
|
||||||
<RemixCredit projectInfo={parentInfo} />
|
<FlexRow className="preview-row">
|
||||||
<RemixCredit projectInfo={originalInfo} />
|
<FlexRow className="extension-list">
|
||||||
{/* eslint-disable max-len */}
|
{extensions && extensions.map(extension => (
|
||||||
<MediaQuery maxWidth={frameless.tablet - 1}>
|
<ExtensionChip
|
||||||
<FlexRow className="preview-row">
|
extensionL10n={extension.l10nId}
|
||||||
<FlexRow className="extension-list">
|
extensionName={extension.name}
|
||||||
{extensions && extensions.map(extension => (
|
hasStatus={extension.hasStatus}
|
||||||
<ExtensionChip
|
iconURI={extension.icon && `/svgs/project/${extension.icon}`}
|
||||||
extensionL10n={extension.l10nId}
|
key={extension.name || 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>
|
|
||||||
<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}
|
|
||||||
id={comment.id}
|
|
||||||
key={comment.id}
|
|
||||||
parentId={comment.parent_id}
|
|
||||||
projectId={projectId}
|
|
||||||
replies={replies && replies[comment.id] ? replies[comment.id] : []}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{comments.length < projectInfo.stats.comments &&
|
</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-root-reply">
|
||||||
|
{isLoggedIn &&
|
||||||
|
<ComposeComment
|
||||||
|
projectId={projectId}
|
||||||
|
onAddComment={onAddComment}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</FlexRow>
|
||||||
|
|
||||||
|
<FlexRow className="comments-list">
|
||||||
|
{comments.map(comment => (
|
||||||
|
<TopLevelComment
|
||||||
|
author={comment.author}
|
||||||
|
canReply={isLoggedIn}
|
||||||
|
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] : []}
|
||||||
|
onAddComment={onAddComment}
|
||||||
|
onDelete={onDeleteComment}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{comments.length < projectInfo.stats.comments &&
|
||||||
<Button
|
<Button
|
||||||
className="button load-more-button"
|
className="button load-more-button"
|
||||||
onClick={onLoadMore}
|
onClick={onLoadMore}
|
||||||
>
|
>
|
||||||
Load More
|
Load More
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
</FlexRow>
|
||||||
|
</div>
|
||||||
|
<FlexRow className="column">
|
||||||
|
<RemixList remixes={remixes} />
|
||||||
|
<StudioList studios={projectStudios} />
|
||||||
</FlexRow>
|
</FlexRow>
|
||||||
</div>
|
|
||||||
<FlexRow className="column">
|
|
||||||
<RemixList remixes={remixes} />
|
|
||||||
<StudioList studios={projectStudios} />
|
|
||||||
</FlexRow>
|
</FlexRow>
|
||||||
</FlexRow>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Formsy>
|
||||||
</Formsy>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
};
|
||||||
|
|
||||||
PreviewPresentation.propTypes = {
|
PreviewPresentation.propTypes = {
|
||||||
addToStudioOpen: PropTypes.bool,
|
addToStudioOpen: PropTypes.bool,
|
||||||
|
@ -363,8 +388,10 @@ PreviewPresentation.propTypes = {
|
||||||
isShared: PropTypes.bool,
|
isShared: PropTypes.bool,
|
||||||
loveCount: PropTypes.number,
|
loveCount: PropTypes.number,
|
||||||
loved: PropTypes.bool,
|
loved: PropTypes.bool,
|
||||||
|
onAddComment: PropTypes.func,
|
||||||
onAddToStudioClicked: PropTypes.func,
|
onAddToStudioClicked: PropTypes.func,
|
||||||
onAddToStudioClosed: PropTypes.func,
|
onAddToStudioClosed: PropTypes.func,
|
||||||
|
onDeleteComment: PropTypes.func,
|
||||||
onFavoriteClicked: PropTypes.func,
|
onFavoriteClicked: PropTypes.func,
|
||||||
onLoadMore: PropTypes.func,
|
onLoadMore: PropTypes.func,
|
||||||
onLoveClicked: PropTypes.func,
|
onLoveClicked: PropTypes.func,
|
||||||
|
|
|
@ -33,6 +33,8 @@ class Preview extends React.Component {
|
||||||
super(props);
|
super(props);
|
||||||
bindAll(this, [
|
bindAll(this, [
|
||||||
'addEventListeners',
|
'addEventListeners',
|
||||||
|
'handleAddComment',
|
||||||
|
'handleDeleteComment',
|
||||||
'handleToggleStudio',
|
'handleToggleStudio',
|
||||||
'handleFavoriteToggle',
|
'handleFavoriteToggle',
|
||||||
'handleLoadMore',
|
'handleLoadMore',
|
||||||
|
@ -123,7 +125,8 @@ class Preview extends React.Component {
|
||||||
* landscape format should make the fullscreen mode active
|
* landscape format should make the fullscreen mode active
|
||||||
*/
|
*/
|
||||||
const isMobileDevice = screen.height <= frameless.mobile || screen.width <= frameless.mobile;
|
const isMobileDevice = screen.height <= frameless.mobile || screen.width <= frameless.mobile;
|
||||||
if (this.props.playerMode && isMobileDevice) {
|
const isAModalOpen = this.state.addToStudioOpen || this.state.reportOpen;
|
||||||
|
if (this.props.playerMode && isMobileDevice && !isAModalOpen) {
|
||||||
const isLandscape = screen.height < screen.width;
|
const isLandscape = screen.height < screen.width;
|
||||||
if (isLandscape) {
|
if (isLandscape) {
|
||||||
this.props.setFullScreen(true);
|
this.props.setFullScreen(true);
|
||||||
|
@ -164,6 +167,12 @@ class Preview extends React.Component {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
handleAddComment (comment, topLevelCommentId) {
|
||||||
|
this.props.handleAddComment(comment, topLevelCommentId);
|
||||||
|
}
|
||||||
|
handleDeleteComment (id, topLevelCommentId) {
|
||||||
|
this.props.handleDeleteComment(this.state.projectId, id, topLevelCommentId, this.props.user.token);
|
||||||
|
}
|
||||||
handleReportClick () {
|
handleReportClick () {
|
||||||
this.setState({reportOpen: true});
|
this.setState({reportOpen: true});
|
||||||
}
|
}
|
||||||
|
@ -336,8 +345,10 @@ class Preview extends React.Component {
|
||||||
reportOpen={this.state.reportOpen}
|
reportOpen={this.state.reportOpen}
|
||||||
studios={this.props.studios}
|
studios={this.props.studios}
|
||||||
userOwnsProject={this.props.userOwnsProject}
|
userOwnsProject={this.props.userOwnsProject}
|
||||||
|
onAddComment={this.handleAddComment}
|
||||||
onAddToStudioClicked={this.handleAddToStudioClick}
|
onAddToStudioClicked={this.handleAddToStudioClick}
|
||||||
onAddToStudioClosed={this.handleAddToStudioClose}
|
onAddToStudioClosed={this.handleAddToStudioClose}
|
||||||
|
onDeleteComment={this.handleDeleteComment}
|
||||||
onFavoriteClicked={this.handleFavoriteToggle}
|
onFavoriteClicked={this.handleFavoriteToggle}
|
||||||
onLoadMore={this.handleLoadMore}
|
onLoadMore={this.handleLoadMore}
|
||||||
onLoveClicked={this.handleLoveToggle}
|
onLoveClicked={this.handleLoveToggle}
|
||||||
|
@ -393,6 +404,8 @@ Preview.propTypes = {
|
||||||
getProjectStudios: PropTypes.func.isRequired,
|
getProjectStudios: PropTypes.func.isRequired,
|
||||||
getRemixes: PropTypes.func.isRequired,
|
getRemixes: PropTypes.func.isRequired,
|
||||||
getTopLevelComments: PropTypes.func.isRequired,
|
getTopLevelComments: PropTypes.func.isRequired,
|
||||||
|
handleAddComment: PropTypes.func,
|
||||||
|
handleDeleteComment: PropTypes.func,
|
||||||
handleLogIn: PropTypes.func,
|
handleLogIn: PropTypes.func,
|
||||||
handleLogOut: PropTypes.func,
|
handleLogOut: PropTypes.func,
|
||||||
handleOpenRegistration: PropTypes.func,
|
handleOpenRegistration: PropTypes.func,
|
||||||
|
@ -519,6 +532,12 @@ const mapStateToProps = state => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
handleAddComment: (comment, topLevelCommentId) => {
|
||||||
|
dispatch(previewActions.addNewComment(comment, topLevelCommentId));
|
||||||
|
},
|
||||||
|
handleDeleteComment: (projectId, commentId, topLevelCommentId, token) => {
|
||||||
|
dispatch(previewActions.deleteComment(projectId, commentId, topLevelCommentId, token));
|
||||||
|
},
|
||||||
handleOpenRegistration: event => {
|
handleOpenRegistration: event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
dispatch(navigationActions.setRegistrationOpen(true));
|
dispatch(navigationActions.setRegistrationOpen(true));
|
||||||
|
|
|
@ -6,12 +6,10 @@ $player-width: 482px;
|
||||||
$player-height: 406px;
|
$player-height: 406px;
|
||||||
$stage-width: 480px;
|
$stage-width: 480px;
|
||||||
|
|
||||||
$small: "screen and (max-width : #{$mobile}-1)";
|
|
||||||
$medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
|
||||||
|
|
||||||
/* override view padding for share banner */
|
/* override view padding for share banner */
|
||||||
#view {
|
#view {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gui {
|
.gui {
|
||||||
|
@ -25,6 +23,19 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||||
|
|
||||||
.preview {
|
.preview {
|
||||||
|
|
||||||
|
.inner {
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
@media #{$medium-and-smaller} {
|
||||||
|
max-width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media #{$intermediate} {
|
||||||
|
width: 90%;
|
||||||
|
min-width: 640px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.project-title {
|
.project-title {
|
||||||
font-size: 1.75rem;
|
font-size: 1.75rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
@ -55,6 +66,14 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||||
.inplace-input {
|
.inplace-input {
|
||||||
height: calc(3rem - 4px);
|
height: calc(3rem - 4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media #{$medium-and-smaller} {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media #{$small} {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
@ -79,6 +98,10 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-size: .8rem;
|
font-size: .8rem;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
||||||
|
@media #{$medium-and-smaller} {
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.validation-message {
|
.validation-message {
|
||||||
|
@ -97,7 +120,7 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||||
color: $type-white;
|
color: $type-white;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
|
||||||
@media #{$medium-and-small} {
|
@media #{$medium-and-smaller} {
|
||||||
margin-top: calc($arrow-border-width / 2);
|
margin-top: calc($arrow-border-width / 2);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -120,7 +143,7 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||||
|
|
||||||
content: "";
|
content: "";
|
||||||
|
|
||||||
@media #{$medium-and-small} {
|
@media #{$medium-and-smaller} {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,7 +177,7 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
@media #{$medium-and-small} {
|
@media #{$medium-and-smaller} {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -208,12 +231,9 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
|
|
||||||
&.wrap-to-col {
|
&.force-row {
|
||||||
@media #{$medium-and-small} {
|
@media #{$medium-and-smaller} {
|
||||||
width: 100%;
|
flex-direction: row;
|
||||||
flex-wrap: wrap-reverse;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,15 +244,11 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||||
|
|
||||||
@media #{$small} {
|
@media #{$small} {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.stage-wrapper {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.force-center {
|
.force-center {
|
||||||
@media #{$medium-and-small} {
|
@media #{$medium-and-smaller} {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -244,7 +260,7 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||||
flex: 1;
|
flex: 1;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
|
|
||||||
@media #{$medium-and-small} {
|
@media #{$medium-and-smaller} {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -263,7 +279,11 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
width: calc(100% - 1rem);
|
width: calc(100% - 1rem);
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
align-items: flex-start;
|
align-items: center;
|
||||||
|
|
||||||
|
@media #{$medium-and-smaller} {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.credit-text {
|
.credit-text {
|
||||||
|
@ -367,7 +387,7 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
@media #{$medium-and-small} {
|
@media #{$medium-and-smaller} {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -396,7 +416,7 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||||
/* TODO: the following can be transferred to
|
/* TODO: the following can be transferred to
|
||||||
src/components/thumbnailcolumn/thumbnailcolumn.scss
|
src/components/thumbnailcolumn/thumbnailcolumn.scss
|
||||||
after testing */
|
after testing */
|
||||||
@media #{$medium-and-small} {
|
@media #{$medium-and-smaller} {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
.thumbnail {
|
.thumbnail {
|
||||||
|
@ -405,7 +425,7 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media #{$medium-and-small} {
|
@media #{$medium-and-smaller} {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
@import "../../frameless";
|
@import "../../frameless";
|
||||||
|
|
||||||
$medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
|
||||||
|
|
||||||
.stats {
|
.stats {
|
||||||
line-height: 2rem;
|
line-height: 2rem;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|
||||||
@media #{$medium-and-small} {
|
@media #{$medium-and-smaller} {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -14,7 +12,7 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||||
}
|
}
|
||||||
|
|
||||||
& > div {
|
& > div {
|
||||||
@media #{$medium-and-small} {
|
@media #{$medium-and-smaller} {
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,74 +7,68 @@ const Button = require('../../components/forms/button.jsx');
|
||||||
const AddToStudioModal = require('../../components/modal/addtostudio/container.jsx');
|
const AddToStudioModal = require('../../components/modal/addtostudio/container.jsx');
|
||||||
const ReportModal = require('../../components/modal/report/modal.jsx');
|
const ReportModal = require('../../components/modal/report/modal.jsx');
|
||||||
|
|
||||||
const projectShape = require('./projectshape.jsx').projectShape;
|
|
||||||
|
|
||||||
require('./subactions.scss');
|
require('./subactions.scss');
|
||||||
|
|
||||||
const Subactions = props => {
|
const Subactions = props => (
|
||||||
const shareDate = ((props.projectInfo.history && props.projectInfo.history.shared)) ?
|
<FlexRow className="subactions">
|
||||||
props.projectInfo.history.shared : '';
|
<div className="share-date">
|
||||||
return (
|
<div className="copyleft">©</div>
|
||||||
<FlexRow className="subactions">
|
{' '}
|
||||||
<div className="share-date">
|
{/* eslint-disable react/jsx-sort-props */}
|
||||||
<div className="copyleft">©</div>
|
{props.shareDate === null ?
|
||||||
{' '}
|
'Unshared' :
|
||||||
{/* eslint-disable react/jsx-sort-props */}
|
<FormattedDate
|
||||||
{shareDate === null ?
|
value={Date.parse(props.shareDate)}
|
||||||
'Unshared' :
|
day="2-digit"
|
||||||
<FormattedDate
|
month="short"
|
||||||
value={Date.parse(shareDate)}
|
year="numeric"
|
||||||
day="2-digit"
|
/>
|
||||||
month="short"
|
}
|
||||||
year="numeric"
|
{/* eslint-enable react/jsx-sort-props */}
|
||||||
/>
|
</div>
|
||||||
}
|
<FlexRow className="action-buttons">
|
||||||
{/* eslint-enable react/jsx-sort-props */}
|
{(props.isLoggedIn && props.userOwnsProject) &&
|
||||||
</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>
|
<React.Fragment>
|
||||||
<Button
|
<Button
|
||||||
className="action-button report-button"
|
className="action-button studio-button"
|
||||||
key="report-button"
|
key="add-to-studio-button"
|
||||||
onClick={props.onReportClicked}
|
onClick={props.onAddToStudioClicked}
|
||||||
>
|
>
|
||||||
Report
|
Add to Studio
|
||||||
</Button>,
|
</Button>,
|
||||||
<ReportModal
|
<AddToStudioModal
|
||||||
isOpen={props.reportOpen}
|
isOpen={props.addToStudioOpen}
|
||||||
key="report-modal"
|
key="add-to-studio-modal"
|
||||||
type="project"
|
studios={props.studios}
|
||||||
onReport={props.onReportSubmit}
|
onRequestClose={props.onAddToStudioClosed}
|
||||||
onRequestClose={props.onReportClose}
|
onToggleStudio={props.onToggleStudio}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
</FlexRow>
|
<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>
|
||||||
);
|
</FlexRow>
|
||||||
};
|
);
|
||||||
|
|
||||||
Subactions.propTypes = {
|
Subactions.propTypes = {
|
||||||
addToStudioOpen: PropTypes.bool,
|
addToStudioOpen: PropTypes.bool,
|
||||||
|
@ -85,8 +79,8 @@ Subactions.propTypes = {
|
||||||
onReportClose: PropTypes.func.isRequired,
|
onReportClose: PropTypes.func.isRequired,
|
||||||
onReportSubmit: PropTypes.func.isRequired,
|
onReportSubmit: PropTypes.func.isRequired,
|
||||||
onToggleStudio: PropTypes.func,
|
onToggleStudio: PropTypes.func,
|
||||||
projectInfo: projectShape,
|
|
||||||
reportOpen: PropTypes.bool,
|
reportOpen: PropTypes.bool,
|
||||||
|
shareDate: PropTypes.string,
|
||||||
studios: PropTypes.arrayOf(PropTypes.object),
|
studios: PropTypes.arrayOf(PropTypes.object),
|
||||||
userOwnsProject: PropTypes.bool
|
userOwnsProject: PropTypes.bool
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,13 @@
|
||||||
@import "../../colors";
|
@import "../../colors";
|
||||||
@import "../../frameless";
|
@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 {
|
.subactions {
|
||||||
margin-left: 1.5rem;
|
margin-left: 1.5rem;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
@media #{$medium-and-small} {
|
@media #{$medium-and-smaller} {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -36,7 +31,7 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||||
margin: 0;
|
margin: 0;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
|
||||||
@media #{$medium-and-small} {
|
@media #{$medium-and-smaller} {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,7 +55,7 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||||
line-height: .875rem;
|
line-height: .875rem;
|
||||||
font-size: .75rem;
|
font-size: .75rem;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
|
||||||
&.studio-button,
|
&.studio-button,
|
||||||
&.copy-link-button,
|
&.copy-link-button,
|
||||||
&.report-button {
|
&.report-button {
|
||||||
|
@ -101,14 +96,14 @@ $medium-and-small: "screen and (max-width : #{$tablet}-1)";
|
||||||
}
|
}
|
||||||
|
|
||||||
.subactions, .subactions .action-buttons {
|
.subactions, .subactions .action-buttons {
|
||||||
@media #{$medium-and-small} {
|
@media #{$medium-and-smaller} {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > div, .action-button {
|
& > div, .action-button {
|
||||||
@media #{$medium-and-small} {
|
@media #{$medium-and-smaller} {
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue