mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2025-02-17 00:21:20 -05:00
Merge pull request #1912 from chrisgarrity/feature/preview-restructure
Feature/preview restructure
This commit is contained in:
commit
5740fe8bf8
15 changed files with 651 additions and 541 deletions
|
@ -29,8 +29,7 @@
|
|||
"express-http-proxy": "1.1.0",
|
||||
"lodash.defaults": "4.0.1",
|
||||
"newrelic": "1.25.4",
|
||||
"raven": "0.10.0",
|
||||
"scratch-gui": "0.1.0-prerelease.20180529181946"
|
||||
"raven": "0.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ajv": "6.4.0",
|
||||
|
@ -97,6 +96,7 @@
|
|||
"redux-thunk": "2.0.1",
|
||||
"sass-lint": "1.5.1",
|
||||
"sass-loader": "6.0.6",
|
||||
"scratch-gui": "0.1.0-prerelease.20180605163331",
|
||||
"scratchr2_translations": "git://github.com/LLK/scratchr2_translations.git#master",
|
||||
"slick-carousel": "1.6.0",
|
||||
"source-map-support": "0.3.2",
|
||||
|
|
|
@ -38,6 +38,8 @@ class InplaceInput extends React.Component {
|
|||
<FRCTextarea
|
||||
className="inplace-textarea"
|
||||
componentRef={this.setRef}
|
||||
elementWrapperClassName="grow"
|
||||
label={null}
|
||||
rowClassName={classNames('textarea-row no-label', className)}
|
||||
onBlur={this.handleBlur}
|
||||
{...props}
|
||||
|
|
|
@ -5,7 +5,6 @@ const FormattedMessage = require('react-intl').FormattedMessage;
|
|||
const injectIntl = require('react-intl').injectIntl;
|
||||
const intlShape = require('react-intl').intlShape;
|
||||
const Modal = require('../base/modal.jsx');
|
||||
const log = require('../../../lib/log.js');
|
||||
|
||||
const Form = require('../../forms/form.jsx');
|
||||
const Button = require('../../forms/button.jsx');
|
||||
|
@ -16,48 +15,74 @@ const TextArea = require('../../forms/textarea.jsx');
|
|||
require('../../forms/button.scss');
|
||||
require('./modal.scss');
|
||||
|
||||
const REPORT_OPTIONS = [
|
||||
{
|
||||
value: '',
|
||||
label: {id: 'report.reasonPlaceHolder'},
|
||||
prompt: {id: 'report.promptPlaceholder'}
|
||||
},
|
||||
{
|
||||
value: '0',
|
||||
label: {id: 'report.reasonCopy'},
|
||||
prompt: {id: 'report.promptCopy'}
|
||||
},
|
||||
{
|
||||
value: '1',
|
||||
label: {id: 'report.reasonUncredited'},
|
||||
prompt: {id: 'report.promptUncredited'}
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: {id: 'report.reasonScary'},
|
||||
prompt: {id: 'report.promptScary'}
|
||||
},
|
||||
{
|
||||
value: '3',
|
||||
label: {id: 'report.reasonLanguage'},
|
||||
prompt: {id: 'report.promptLanguage'}
|
||||
},
|
||||
{
|
||||
value: '4',
|
||||
label: {id: 'report.reasonMusic'},
|
||||
prompt: {id: 'report.promptMusic'}
|
||||
},
|
||||
{
|
||||
value: '8',
|
||||
label: {id: 'report.reasonImage'},
|
||||
prompt: {id: 'report.promptImage'}
|
||||
},
|
||||
{
|
||||
value: '5',
|
||||
label: {id: 'report.reasonPersonal'},
|
||||
prompt: {id: 'report.promptPersonal'}
|
||||
},
|
||||
{
|
||||
value: '6',
|
||||
label: {id: 'general.other'},
|
||||
prompt: {id: 'report.promptGuidelines'}
|
||||
}
|
||||
];
|
||||
|
||||
class ReportModal extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleReasonSelect',
|
||||
'handleSubmit'
|
||||
'handleReportCategorySelect'
|
||||
]);
|
||||
this.state = {
|
||||
prompt: props.intl.formatMessage({id: 'report.promptPlaceholder'}),
|
||||
reason: '',
|
||||
waiting: false
|
||||
};
|
||||
this.state = {reportCategory: this.props.report.category};
|
||||
}
|
||||
handleReasonSelect (name, value) {
|
||||
const prompts = [
|
||||
this.props.intl.formatMessage({id: 'report.promptCopy'}),
|
||||
this.props.intl.formatMessage({id: 'report.promptUncredited'}),
|
||||
this.props.intl.formatMessage({id: 'report.promptScary'}),
|
||||
this.props.intl.formatMessage({id: 'report.promptLanguage'}),
|
||||
this.props.intl.formatMessage({id: 'report.promptMusic'}),
|
||||
this.props.intl.formatMessage({id: 'report.promptPersonal'}),
|
||||
this.props.intl.formatMessage({id: 'report.promptGuidelines'}),
|
||||
'not used',
|
||||
this.props.intl.formatMessage({id: 'report.promptImage'})
|
||||
];
|
||||
this.setState({prompt: prompts[value], reason: value});
|
||||
handleReportCategorySelect (name, value) {
|
||||
this.setState({reportCategory: value});
|
||||
}
|
||||
handleSubmit (formData) {
|
||||
this.setState({waiting: true});
|
||||
this.props.onReport(formData, err => {
|
||||
if (err) log.error(err);
|
||||
this.setState({
|
||||
prompt: this.props.intl.formatMessage({id: 'report.promptPlaceholder'}),
|
||||
reason: '',
|
||||
waiting: false
|
||||
});
|
||||
});
|
||||
lookupPrompt (value) {
|
||||
const prompt = REPORT_OPTIONS.find(item => item.value === value).prompt;
|
||||
return this.props.intl.formatMessage(prompt);
|
||||
}
|
||||
render () {
|
||||
const {
|
||||
intl,
|
||||
onReport, // eslint-disable-line no-unused-vars
|
||||
report,
|
||||
type,
|
||||
...modalProps
|
||||
} = this.props;
|
||||
|
@ -66,6 +91,7 @@ class ReportModal extends React.Component {
|
|||
<Modal
|
||||
className="mod-report"
|
||||
contentLabel={contentLabel}
|
||||
isOpen={report.open}
|
||||
{...modalProps}
|
||||
>
|
||||
<div>
|
||||
|
@ -88,68 +114,38 @@ class ReportModal extends React.Component {
|
|||
/>
|
||||
<Form
|
||||
className="report"
|
||||
onSubmit={this.handleSubmit}
|
||||
onSubmit={onReport}
|
||||
>
|
||||
<Select
|
||||
required
|
||||
name="reason"
|
||||
options={[
|
||||
{
|
||||
value: '',
|
||||
label: this.props.intl.formatMessage({id: 'report.reasonPlaceHolder'})
|
||||
},
|
||||
{
|
||||
value: '0',
|
||||
label: this.props.intl.formatMessage({id: 'report.reasonCopy'})
|
||||
},
|
||||
{
|
||||
value: '1',
|
||||
label: this.props.intl.formatMessage({id: 'report.reasonUncredited'})
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: this.props.intl.formatMessage({id: 'report.reasonScary'})
|
||||
},
|
||||
{
|
||||
value: '3',
|
||||
label: this.props.intl.formatMessage({id: 'report.reasonLanguage'})
|
||||
},
|
||||
{
|
||||
value: '4',
|
||||
label: this.props.intl.formatMessage({id: 'report.reasonMusic'})
|
||||
},
|
||||
{
|
||||
value: '8',
|
||||
label: this.props.intl.formatMessage({id: 'report.reasonImage'})
|
||||
},
|
||||
{
|
||||
value: '5',
|
||||
label: this.props.intl.formatMessage({id: 'report.reasonPersonal'})
|
||||
},
|
||||
{
|
||||
value: '6',
|
||||
label: this.props.intl.formatMessage({id: 'general.other'})
|
||||
}
|
||||
]}
|
||||
value={this.state.reason}
|
||||
onChange={this.handleReasonSelect}
|
||||
elementWrapperClassName="report-modal-field"
|
||||
label={null}
|
||||
name="report_category"
|
||||
options={REPORT_OPTIONS.map(option => ({
|
||||
value: option.value,
|
||||
label: this.props.intl.formatMessage(option.label)
|
||||
}))}
|
||||
value={this.state.reportCategory}
|
||||
onChange={this.handleReportCategorySelect}
|
||||
/>
|
||||
<TextArea
|
||||
required
|
||||
className="report-text"
|
||||
name="reportText"
|
||||
placeholder={this.state.prompt}
|
||||
elementWrapperClassName="report-modal-field"
|
||||
label={null}
|
||||
name="notes"
|
||||
placeholder={this.lookupPrompt(this.state.reportCategory)}
|
||||
validationErrors={{
|
||||
maxLength: this.props.intl.formatMessage({id: 'report.tooLongError'}),
|
||||
minLength: this.props.intl.formatMessage({id: 'report.tooShortError'})
|
||||
}}
|
||||
validations={{
|
||||
// TODO find out max and min characters
|
||||
maxLength: 500,
|
||||
minLength: 30
|
||||
minLength: 20
|
||||
}}
|
||||
value={report.notes}
|
||||
/>
|
||||
{this.state.reportWaiting ? [
|
||||
{report.waiting ? [
|
||||
<Button
|
||||
className="submit-button white"
|
||||
disabled="disabled"
|
||||
|
@ -180,6 +176,12 @@ ReportModal.propTypes = {
|
|||
intl: intlShape,
|
||||
onReport: PropTypes.func,
|
||||
onRequestClose: PropTypes.func,
|
||||
report: PropTypes.shape({
|
||||
category: PropTypes.string,
|
||||
notes: PropTypes.string,
|
||||
open: PropTypes.bool,
|
||||
waiting: PropTypes.bool
|
||||
}),
|
||||
type: PropTypes.string
|
||||
};
|
||||
|
||||
|
|
|
@ -10,11 +10,11 @@
|
|||
outline: none;
|
||||
padding: 0;
|
||||
width: 30rem;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.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;
|
||||
|
@ -36,4 +36,45 @@
|
|||
width: 80%;
|
||||
line-height: 1.5rem;
|
||||
font-size: .875rem;
|
||||
|
||||
.validation-message {
|
||||
$arrow-border-width: 1rem;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform: translate(23.5rem, 0);
|
||||
margin-left: $arrow-border-width;
|
||||
border: 1px solid $active-gray;
|
||||
border-radius: 5px;
|
||||
background-color: $ui-orange;
|
||||
padding: 1rem;
|
||||
max-width: 18.75rem;
|
||||
min-height: 1rem;
|
||||
overflow: visible;
|
||||
color: $type-white;
|
||||
|
||||
&:before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
left: -$arrow-border-width / 2;
|
||||
|
||||
transform: rotate(45deg);
|
||||
|
||||
border-bottom: 1px solid $active-gray;
|
||||
border-left: 1px solid $active-gray;
|
||||
border-radius: 5px;
|
||||
|
||||
background-color: $ui-orange;
|
||||
width: $arrow-border-width;
|
||||
height: $arrow-border-width;
|
||||
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.report-modal-field {
|
||||
position: relative;
|
||||
}
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./share-banner.scss');
|
||||
|
||||
const ShareBanner = props => (
|
||||
<div className={classNames('shareBanner', props.className)}>
|
||||
<div className="inner">
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
ShareBanner.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = ShareBanner;
|
|
@ -1,10 +0,0 @@
|
|||
@import "../../colors";
|
||||
|
||||
$navigation-height: 50px;
|
||||
|
||||
.shareBanner {
|
||||
background-color: $ui-orange-25percent;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
color: $ui-orange;
|
||||
}
|
|
@ -14,7 +14,7 @@ const ThumbnailColumn = props => (
|
|||
if (props.itemType === 'preview') {
|
||||
return (
|
||||
<Thumbnail
|
||||
avatar={`https://cdn2.scratch.mit.edu/get_image/user/${item.author.i}_32x32.png`}
|
||||
avatar={`https://cdn2.scratch.mit.edu/get_image/user/${item.author.id}_32x32.png`}
|
||||
creator={item.author.username}
|
||||
favorites={item.stats.favorites}
|
||||
href={href}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
const bindAll = require('lodash.bindall');
|
||||
const FormattedDate = require('react-intl').FormattedDate;
|
||||
const injectIntl = require('react-intl').injectIntl;
|
||||
const PropTypes = require('prop-types');
|
||||
|
@ -10,401 +9,289 @@ const approx = require('approximate-number');
|
|||
const GUI = require('scratch-gui').default;
|
||||
const IntlGUI = injectIntl(GUI);
|
||||
|
||||
const sessionActions = require('../../redux/session.js');
|
||||
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('../../components/share-banner/share-banner.jsx');
|
||||
const ThumbnailColumn = require('../../components/thumbnailcolumn/thumbnailcolumn.jsx');
|
||||
const ShareBanner = require('./share-banner.jsx');
|
||||
const RemixCredit = require('./remix-credit.jsx');
|
||||
const RemixList = require('./remix-list.jsx');
|
||||
const StudioList = require('./studio-list.jsx');
|
||||
const InplaceInput = require('../../components/forms/inplace-input.jsx');
|
||||
const ReportModal = require('../../components/modal/report/modal.jsx');
|
||||
|
||||
const projectShape = require('./projectshape.jsx').projectShape;
|
||||
require('./preview.scss');
|
||||
|
||||
class PreviewPresentation extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleReportClick',
|
||||
'handleReportClose',
|
||||
'handleReportSubmit'
|
||||
]);
|
||||
this.state = {
|
||||
reportOpen: false
|
||||
};
|
||||
}
|
||||
handleReportClick (e) {
|
||||
e.preventDefault();
|
||||
this.setState({reportOpen: true});
|
||||
}
|
||||
handleReportClose () {
|
||||
this.setState({reportOpen: false});
|
||||
}
|
||||
|
||||
handleReportSubmit (formData, callback) {
|
||||
const data = {
|
||||
...formData,
|
||||
id: this.props.projectId,
|
||||
username: this.props.user.username
|
||||
};
|
||||
console.log('submit report data', data); // eslint-disable-line no-console
|
||||
// TODO: pass error to modal via callback.
|
||||
callback();
|
||||
this.setState({reportOpen: false});
|
||||
}
|
||||
render () {
|
||||
const {
|
||||
editable,
|
||||
faved,
|
||||
favoriteCount,
|
||||
isFullScreen,
|
||||
loved,
|
||||
loveCount,
|
||||
originalInfo,
|
||||
parentInfo,
|
||||
projectId,
|
||||
projectInfo,
|
||||
remixes,
|
||||
sessionStatus,
|
||||
studios,
|
||||
user,
|
||||
onFavoriteClicked,
|
||||
onLoveClicked,
|
||||
onSeeInside,
|
||||
onUpdate
|
||||
// ...otherProps TBD
|
||||
} = this.props;
|
||||
const shareDate = (projectInfo.history && projectInfo.history.shared) ? projectInfo.history.shared : '';
|
||||
return (
|
||||
<div className="preview">
|
||||
{projectInfo.history && shareDate === '' &&
|
||||
<ShareBanner>
|
||||
<FlexRow className="preview-row">
|
||||
<span className="share-text">
|
||||
This project is not shared — so only you can see it. Click share to let everyone see it!
|
||||
</span>
|
||||
<Button className="button share-button">
|
||||
Share
|
||||
</Button>
|
||||
</FlexRow>
|
||||
</ShareBanner>
|
||||
}
|
||||
|
||||
{ projectInfo && projectInfo.author && projectInfo.author.id && (
|
||||
<div className="inner">
|
||||
<Formsy>
|
||||
<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: 'Sorry title is too long'
|
||||
// maxLength: props.intl.formatMessage({
|
||||
// id: 'project.titleMaxLength'
|
||||
// })
|
||||
}}
|
||||
validations={{
|
||||
maxLength: 100
|
||||
}}
|
||||
value={projectInfo.title}
|
||||
/> :
|
||||
<div className="project-title">{projectInfo.title}</div>
|
||||
}
|
||||
</div>
|
||||
</FlexRow>
|
||||
<div className="project-buttons">
|
||||
{sessionStatus === sessionActions.Status.FETCHED &&
|
||||
Object.keys(user).length > 0 &&
|
||||
user.id !== projectInfo.author.id &&
|
||||
<Button className="button remix-button">
|
||||
Remix
|
||||
</Button>
|
||||
}
|
||||
<Button
|
||||
className="button see-inside-button"
|
||||
onClick={onSeeInside}
|
||||
>
|
||||
See Inside
|
||||
</Button>
|
||||
</div>
|
||||
</FlexRow>
|
||||
<FlexRow className="preview-row">
|
||||
<div className="guiPlayer">
|
||||
<IntlGUI
|
||||
isPlayerOnly
|
||||
basePath="/"
|
||||
className="guiPlayer"
|
||||
isFullScreen={isFullScreen}
|
||||
previewInfoVisible="false"
|
||||
projectId={projectId}
|
||||
/>
|
||||
</div>
|
||||
<FlexRow className="project-notes">
|
||||
{parentInfo && parentInfo.author && parentInfo.id && (
|
||||
<FlexRow className="remix-credit">
|
||||
<Avatar
|
||||
className="remix"
|
||||
src={`https://cdn2.scratch.mit.edu/get_image/user/${parentInfo.author.id}_48x48.png`}
|
||||
/>
|
||||
<div className="credit-text">
|
||||
Thanks to <a
|
||||
href={`/users/${parentInfo.author.username}`}
|
||||
>
|
||||
{parentInfo.author.username}
|
||||
</a> for the original project <a
|
||||
href={`/preview/${parentInfo.id}`}
|
||||
>
|
||||
{parentInfo.title}
|
||||
</a>.
|
||||
</div>
|
||||
</FlexRow>
|
||||
)}
|
||||
{originalInfo && originalInfo.author && originalInfo.id && (
|
||||
<FlexRow className="remix-credit">
|
||||
<Avatar
|
||||
className="remix"
|
||||
src={`https://cdn2.scratch.mit.edu/get_image/user/${originalInfo.author.id}_48x48.png`}
|
||||
/>
|
||||
<div className="credit-text">
|
||||
Thanks to <a
|
||||
href={`/users/${originalInfo.author.username}`}
|
||||
>
|
||||
{originalInfo.author.username}
|
||||
</a> for the original project <a
|
||||
href={`/preview/${originalInfo.id}`}
|
||||
>
|
||||
{originalInfo.title}
|
||||
</a>.
|
||||
</div>
|
||||
</FlexRow>
|
||||
)}
|
||||
{/* eslint-disable max-len */}
|
||||
<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">
|
||||
<Button className="action-button studio-button">
|
||||
Add to Studio
|
||||
</Button>
|
||||
<Button className="action-button copy-link-button">
|
||||
Copy Link
|
||||
</Button>
|
||||
{
|
||||
sessionStatus === sessionActions.Status.FETCHED &&
|
||||
Object.keys(user).length > 0 &&
|
||||
user.id !== projectInfo.author.id && [
|
||||
<Button
|
||||
className="action-button report-button"
|
||||
key="report-button"
|
||||
onClick={this.handleReportClick}
|
||||
>
|
||||
Report
|
||||
</Button>,
|
||||
<ReportModal
|
||||
isOpen={this.state.reportOpen}
|
||||
key="report-modal"
|
||||
type="project"
|
||||
onReport={this.handleReportSubmit}
|
||||
onRequestClose={this.handleReportClose}
|
||||
/>
|
||||
]
|
||||
}
|
||||
</FlexRow>
|
||||
</FlexRow>
|
||||
</FlexRow>
|
||||
<FlexRow className="preview-row">
|
||||
<div className="comments-container">
|
||||
<div className="project-title">
|
||||
Comments go here
|
||||
</div>
|
||||
</div>
|
||||
<FlexRow className="column">
|
||||
{/* hide remixes if there aren't any */}
|
||||
{remixes && remixes.length !== 0 && (
|
||||
<FlexRow className="remix-list">
|
||||
<div className="project-title">
|
||||
Remixes
|
||||
</div>
|
||||
{remixes && remixes.length === 0 ? (
|
||||
// TODO: style remix invitation
|
||||
<span>Invite user to remix</span>
|
||||
) : (
|
||||
<ThumbnailColumn
|
||||
cards
|
||||
showAvatar
|
||||
itemType="preview"
|
||||
items={remixes.slice(0, 5)}
|
||||
showFavorites={false}
|
||||
showLoves={false}
|
||||
showViews={false}
|
||||
/>
|
||||
)}
|
||||
</FlexRow>
|
||||
)}
|
||||
{/* hide studios if there aren't any */}
|
||||
{studios && studios.length !== 0 && (
|
||||
<FlexRow className="studio-list">
|
||||
<div className="project-title">
|
||||
Studios
|
||||
</div>
|
||||
{studios && studios.length === 0 ? (
|
||||
// TODO: invite user to add to studio?
|
||||
<span>None </span>
|
||||
) : (
|
||||
<ThumbnailColumn
|
||||
cards
|
||||
showAvatar
|
||||
itemType="gallery"
|
||||
items={studios.slice(0, 5)}
|
||||
showFavorites={false}
|
||||
showLoves={false}
|
||||
showViews={false}
|
||||
/>
|
||||
)}
|
||||
</FlexRow>
|
||||
)}
|
||||
</FlexRow>
|
||||
</FlexRow>
|
||||
</Formsy>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
const PreviewPresentation = ({
|
||||
editable,
|
||||
faved,
|
||||
favoriteCount,
|
||||
isFullScreen,
|
||||
isLoggedIn,
|
||||
isShared,
|
||||
loved,
|
||||
loveCount,
|
||||
originalInfo,
|
||||
parentInfo,
|
||||
projectId,
|
||||
projectInfo,
|
||||
remixes,
|
||||
report,
|
||||
studios,
|
||||
userOwnsProject,
|
||||
onFavoriteClicked,
|
||||
onLoveClicked,
|
||||
onReportClicked,
|
||||
onReportClose,
|
||||
onReportSubmit,
|
||||
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 && (
|
||||
<div className="inner">
|
||||
<Formsy>
|
||||
<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: 'Sorry title is too long'
|
||||
// maxLength: props.intl.formatMessage({
|
||||
// id: 'project.titleMaxLength'
|
||||
// })
|
||||
}}
|
||||
validations={{
|
||||
maxLength: 100
|
||||
}}
|
||||
value={projectInfo.title}
|
||||
/> :
|
||||
<React.Fragment>
|
||||
<div className="project-title">{projectInfo.title}</div>
|
||||
{'by '}
|
||||
<a href={`/users/${projectInfo.author.username}`}>
|
||||
{projectInfo.author.username}
|
||||
</a>
|
||||
</React.Fragment>
|
||||
}
|
||||
</div>
|
||||
</FlexRow>
|
||||
<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>
|
||||
</FlexRow>
|
||||
<FlexRow className="preview-row">
|
||||
<div className="guiPlayer">
|
||||
<IntlGUI
|
||||
isPlayerOnly
|
||||
basePath="/"
|
||||
className="guiPlayer"
|
||||
isFullScreen={isFullScreen}
|
||||
previewInfoVisible="false"
|
||||
projectId={projectId}
|
||||
/>
|
||||
</div>
|
||||
<FlexRow className="project-notes">
|
||||
<RemixCredit projectInfo={parentInfo} />
|
||||
<RemixCredit projectInfo={originalInfo} />
|
||||
{/* eslint-disable max-len */}
|
||||
<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">
|
||||
<Button className="action-button studio-button">
|
||||
Add to Studio
|
||||
</Button>
|
||||
<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
|
||||
key="report-modal"
|
||||
report={report}
|
||||
type="project"
|
||||
onReport={onReportSubmit}
|
||||
onRequestClose={onReportClose}
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
</FlexRow>
|
||||
</FlexRow>
|
||||
</FlexRow>
|
||||
<FlexRow className="preview-row">
|
||||
<div className="comments-container">
|
||||
<div className="project-title" />
|
||||
</div>
|
||||
<FlexRow className="column">
|
||||
<RemixList remixes={remixes} />
|
||||
<StudioList studios={studios} />
|
||||
</FlexRow>
|
||||
</FlexRow>
|
||||
</Formsy>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
PreviewPresentation.propTypes = {
|
||||
editable: PropTypes.bool,
|
||||
faved: PropTypes.bool,
|
||||
favoriteCount: PropTypes.number,
|
||||
isFullScreen: PropTypes.bool,
|
||||
isLoggedIn: PropTypes.bool,
|
||||
isShared: PropTypes.bool,
|
||||
loveCount: PropTypes.number,
|
||||
loved: PropTypes.bool,
|
||||
onFavoriteClicked: PropTypes.func,
|
||||
onLoveClicked: PropTypes.func,
|
||||
onReportClicked: PropTypes.func.isRequired,
|
||||
onReportClose: PropTypes.func.isRequired,
|
||||
onReportSubmit: PropTypes.func.isRequired,
|
||||
onSeeInside: PropTypes.func,
|
||||
onUpdate: PropTypes.func,
|
||||
originalInfo: projectShape,
|
||||
|
@ -412,18 +299,14 @@ PreviewPresentation.propTypes = {
|
|||
projectId: PropTypes.string,
|
||||
projectInfo: projectShape,
|
||||
remixes: PropTypes.arrayOf(PropTypes.object),
|
||||
sessionStatus: PropTypes.string.isRequired,
|
||||
report: PropTypes.shape({
|
||||
category: PropTypes.string,
|
||||
notes: PropTypes.string,
|
||||
open: PropTypes.bool,
|
||||
waiting: PropTypes.bool
|
||||
}),
|
||||
studios: PropTypes.arrayOf(PropTypes.object),
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.number,
|
||||
banned: PropTypes.bool,
|
||||
username: PropTypes.string,
|
||||
token: PropTypes.string,
|
||||
thumbnailUrl: PropTypes.string,
|
||||
dateJoined: PropTypes.string,
|
||||
email: PropTypes.string,
|
||||
classroomId: PropTypes.string
|
||||
}).isRequired
|
||||
userOwnsProject: PropTypes.bool
|
||||
};
|
||||
|
||||
module.exports = injectIntl(PreviewPresentation);
|
||||
|
|
|
@ -24,12 +24,33 @@ class Preview extends React.Component {
|
|||
'handleLoveToggle',
|
||||
'handlePermissions',
|
||||
'handlePopState',
|
||||
'handleReportClick',
|
||||
'handleReportClose',
|
||||
'handleReportSubmit',
|
||||
'handleSeeInside',
|
||||
'handleUpdate',
|
||||
'initCounts',
|
||||
'pushHistory'
|
||||
'isShared',
|
||||
'pushHistory',
|
||||
'userOwnsProject'
|
||||
]);
|
||||
this.state = this.initState();
|
||||
const pathname = window.location.pathname.toLowerCase();
|
||||
const parts = pathname.split('/').filter(Boolean);
|
||||
// parts[0]: 'preview'
|
||||
// parts[1]: either :id or 'editor'
|
||||
// parts[2]: undefined if no :id, otherwise either 'editor' or 'fullscreen'
|
||||
this.state = {
|
||||
editable: false,
|
||||
favoriteCount: 0,
|
||||
loveCount: 0,
|
||||
projectId: parts[1] === 'editor' ? 0 : parts[1],
|
||||
report: {
|
||||
category: '',
|
||||
notes: '',
|
||||
open: false,
|
||||
waiting: false
|
||||
}
|
||||
};
|
||||
this.addEventListeners();
|
||||
}
|
||||
componentDidUpdate (prevProps) {
|
||||
|
@ -70,25 +91,39 @@ class Preview extends React.Component {
|
|||
componentWillUnmount () {
|
||||
this.removeEventListeners();
|
||||
}
|
||||
initState () {
|
||||
const pathname = window.location.pathname.toLowerCase();
|
||||
const parts = pathname.split('/').filter(Boolean);
|
||||
// parts[0]: 'preview'
|
||||
// parts[1]: either :id or 'editor'
|
||||
// parts[2]: undefined if no :id, otherwise either 'editor' or 'fullscreen'
|
||||
return {
|
||||
editable: false,
|
||||
favoriteCount: 0,
|
||||
loveCount: 0,
|
||||
projectId: parts[1] === 'editor' ? 0 : parts[1]
|
||||
};
|
||||
}
|
||||
addEventListeners () {
|
||||
window.addEventListener('popstate', this.handlePopState);
|
||||
}
|
||||
removeEventListeners () {
|
||||
window.removeEventListener('popstate', this.handlePopState);
|
||||
}
|
||||
handleReportClick () {
|
||||
this.setState({report: {...this.state.report, open: true}});
|
||||
}
|
||||
handleReportClose () {
|
||||
this.setState({report: {...this.state.report, open: false}});
|
||||
}
|
||||
handleReportSubmit (formData) {
|
||||
this.setState({report: {
|
||||
category: formData.report_category,
|
||||
notes: formData.notes,
|
||||
open: this.state.report.open,
|
||||
waiting: true}
|
||||
});
|
||||
|
||||
const data = {
|
||||
...formData,
|
||||
id: this.state.projectId,
|
||||
user: this.props.user.username
|
||||
};
|
||||
console.log('submit report data', data); // eslint-disable-line no-console
|
||||
this.setState({report: {
|
||||
category: '',
|
||||
notes: '',
|
||||
open: false,
|
||||
waiting: false}
|
||||
});
|
||||
}
|
||||
handlePopState () {
|
||||
const path = window.location.pathname.toLowerCase();
|
||||
const playerMode = path.indexOf('editor') === -1;
|
||||
|
@ -179,6 +214,28 @@ class Preview extends React.Component {
|
|||
loveCount: loves
|
||||
});
|
||||
}
|
||||
isShared () {
|
||||
return (
|
||||
// if we don't have projectInfo assume shared until we know otherwise
|
||||
Object.keys(this.props.projectInfo).length === 0 || (
|
||||
this.props.projectInfo.history &&
|
||||
this.props.projectInfo.history.shared.length > 0
|
||||
)
|
||||
);
|
||||
}
|
||||
isLoggedIn () {
|
||||
return (
|
||||
this.props.sessionStatus === sessionActions.Status.FETCHED &&
|
||||
Object.keys(this.props.user).length > 0
|
||||
);
|
||||
}
|
||||
userOwnsProject () {
|
||||
return (
|
||||
this.isLoggedIn() &&
|
||||
Object.keys(this.props.projectInfo).length > 0 &&
|
||||
this.props.user.id === this.props.projectInfo.author.id
|
||||
);
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
this.props.playerMode ?
|
||||
|
@ -189,6 +246,8 @@ class Preview extends React.Component {
|
|||
faved={this.props.faved}
|
||||
favoriteCount={this.state.favoriteCount}
|
||||
isFullScreen={this.state.isFullScreen}
|
||||
isLoggedIn={this.isLoggedIn()}
|
||||
isShared={this.isShared()}
|
||||
loveCount={this.state.loveCount}
|
||||
loved={this.props.loved}
|
||||
originalInfo={this.props.original}
|
||||
|
@ -196,11 +255,15 @@ class Preview extends React.Component {
|
|||
projectId={this.state.projectId}
|
||||
projectInfo={this.props.projectInfo}
|
||||
remixes={this.props.remixes}
|
||||
sessionStatus={this.props.sessionStatus}
|
||||
report={this.state.report}
|
||||
studios={this.props.studios}
|
||||
user={this.props.user}
|
||||
userOwnsProject={this.userOwnsProject()}
|
||||
onFavoriteClicked={this.handleFavoriteToggle}
|
||||
onLoveClicked={this.handleLoveToggle}
|
||||
onReportClicked={this.handleReportClick}
|
||||
onReportClose={this.handleReportClose}
|
||||
onReportSubmit={this.handleReportSubmit}
|
||||
onSeeInside={this.handleSeeInside}
|
||||
onUpdate={this.handleUpdate}
|
||||
/>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
@import "../../colors";
|
||||
@import "../../frameless";
|
||||
|
||||
/* stage size contants
|
||||
/* stage size constants
|
||||
* this is a hack right now - stage includes padding of .5rem (8px) for alignment in gui
|
||||
* in www the player is placed with margin -.5rem to align the edge.
|
||||
* the height is calculated from the actual height on the page (404)
|
||||
|
@ -10,13 +10,6 @@ $gui-width: 496px;
|
|||
$stage-width: 480px;
|
||||
$stage-height: 404px;
|
||||
|
||||
|
||||
// remix credit height: 52px
|
||||
// project text label line-height + margin-bottom .5rem: 19px + 8px = 27px
|
||||
// Formsy wrapper adds 3px to the input height for
|
||||
$description-input: 166px; // $stage-height / 2 - $project-label - $wrapper - margin
|
||||
$description-input-small: 120px; // normal $description-input - $remix-credit
|
||||
|
||||
/* override view padding for share banner */
|
||||
#view {
|
||||
padding: 0 0 20px 0;
|
||||
|
@ -138,7 +131,6 @@ $description-input-small: 120px; // normal $description-input - $remix-credit
|
|||
width: 60%;
|
||||
}
|
||||
|
||||
.share-button,
|
||||
.remix-button,
|
||||
.see-inside-button {
|
||||
margin-top: 0;
|
||||
|
@ -158,18 +150,6 @@ $description-input-small: 120px; // normal $description-input - $remix-credit
|
|||
}
|
||||
}
|
||||
|
||||
.shareText {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.share-button {
|
||||
background-color: $ui-orange;
|
||||
|
||||
&:before {
|
||||
background-image: url("/svgs/project/share-white.svg");
|
||||
}
|
||||
}
|
||||
|
||||
.remix-button {
|
||||
background-color: $ui-green;
|
||||
|
||||
|
@ -189,6 +169,7 @@ $description-input-small: 120px; // normal $description-input - $remix-credit
|
|||
margin-top: 1rem;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.guiPlayer {
|
||||
|
@ -237,10 +218,11 @@ $description-input-small: 120px; // normal $description-input - $remix-credit
|
|||
}
|
||||
|
||||
.description-block {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
flex-grow: 1;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.project-textlabel {
|
||||
|
@ -256,6 +238,7 @@ $description-input-small: 120px; // normal $description-input - $remix-credit
|
|||
background-color: $ui-blue-10percent;
|
||||
padding: .5rem;
|
||||
width: calc(100% - (1rem + 2px));
|
||||
overflow: auto;
|
||||
white-space: pre-line;
|
||||
font-size: 1rem;
|
||||
// flex-grow
|
||||
|
@ -267,6 +250,7 @@ $description-input-small: 120px; // normal $description-input - $remix-credit
|
|||
}
|
||||
|
||||
.project-description-edit {
|
||||
display: flex;
|
||||
margin-bottom: .75rem;
|
||||
border: 1px solid $ui-blue-10percent;
|
||||
border-radius: 8px;
|
||||
|
@ -293,15 +277,12 @@ $description-input-small: 120px; // normal $description-input - $remix-credit
|
|||
}
|
||||
}
|
||||
|
||||
.inplace-textarea {
|
||||
height: $description-input;
|
||||
& > .grow {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.project-description-edit.remixes .inplace-textarea {
|
||||
height: $description-input-small;
|
||||
}
|
||||
|
||||
.copyleft {
|
||||
display: inline-block;
|
||||
transform: scale(-1, 1);
|
||||
|
|
34
src/views/preview/remix-credit.jsx
Normal file
34
src/views/preview/remix-credit.jsx
Normal file
|
@ -0,0 +1,34 @@
|
|||
const React = require('react');
|
||||
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||
const Avatar = require('../../components/avatar/avatar.jsx');
|
||||
const projectShape = require('./projectshape.jsx').projectShape;
|
||||
|
||||
const RemixCredit = props => {
|
||||
const projectInfo = props.projectInfo;
|
||||
if (Object.keys(projectInfo).length === 0) return null;
|
||||
return (
|
||||
<FlexRow className="remix-credit">
|
||||
<Avatar
|
||||
className="remix"
|
||||
src={`https://cdn2.scratch.mit.edu/get_image/user/${projectInfo.author.id}_48x48.png`}
|
||||
/>
|
||||
<div className="credit-text">
|
||||
Thanks to <a
|
||||
href={`/users/${projectInfo.author.username}`}
|
||||
>
|
||||
{projectInfo.author.username}
|
||||
</a> for the original project <a
|
||||
href={`/preview/${projectInfo.id}`}
|
||||
>
|
||||
{projectInfo.title}
|
||||
</a>.
|
||||
</div>
|
||||
</FlexRow>
|
||||
);
|
||||
};
|
||||
|
||||
RemixCredit.propTypes = {
|
||||
projectInfo: projectShape
|
||||
};
|
||||
|
||||
module.exports = RemixCredit;
|
37
src/views/preview/remix-list.jsx
Normal file
37
src/views/preview/remix-list.jsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||
const ThumbnailColumn = require('../../components/thumbnailcolumn/thumbnailcolumn.jsx');
|
||||
const projectShape = require('./projectshape.jsx').projectShape;
|
||||
|
||||
const RemixList = props => {
|
||||
const remixes = props.remixes;
|
||||
if (remixes.length === 0) return null;
|
||||
return (
|
||||
<FlexRow className="remix-list">
|
||||
<div className="project-title">
|
||||
Remixes
|
||||
</div>
|
||||
{remixes.length === 0 ? (
|
||||
// TODO: style remix invitation
|
||||
<span>Invite user to remix</span>
|
||||
) : (
|
||||
<ThumbnailColumn
|
||||
cards
|
||||
showAvatar
|
||||
itemType="preview"
|
||||
items={remixes.slice(0, 5)}
|
||||
showFavorites={false}
|
||||
showLoves={false}
|
||||
showViews={false}
|
||||
/>
|
||||
)}
|
||||
</FlexRow>
|
||||
);
|
||||
};
|
||||
|
||||
RemixList.propTypes = {
|
||||
remixes: PropTypes.arrayOf(projectShape)
|
||||
};
|
||||
|
||||
module.exports = RemixList;
|
30
src/views/preview/share-banner.jsx
Normal file
30
src/views/preview/share-banner.jsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
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');
|
||||
|
||||
require('./share-banner.scss');
|
||||
|
||||
const ShareBanner = props => {
|
||||
if (props.shared) return null;
|
||||
return (
|
||||
<div className="shareBanner">
|
||||
<div className="inner">
|
||||
<FlexRow className="preview-row">
|
||||
<span className="share-text">
|
||||
This project is not shared — so only you can see it. Click share to let everyone see it!
|
||||
</span>
|
||||
<Button className="button share-button">
|
||||
Share
|
||||
</Button>
|
||||
</FlexRow>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ShareBanner.propTypes = {
|
||||
shared: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
module.exports = ShareBanner;
|
30
src/views/preview/share-banner.scss
Normal file
30
src/views/preview/share-banner.scss
Normal file
|
@ -0,0 +1,30 @@
|
|||
@import "../../colors";
|
||||
|
||||
$navigation-height: 50px;
|
||||
|
||||
.shareBanner {
|
||||
background-color: $ui-orange-25percent;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
color: $ui-orange;
|
||||
}
|
||||
|
||||
.share-button {
|
||||
margin-top: 0;
|
||||
background-color: $ui-orange;
|
||||
font-size: .875rem;
|
||||
font-weight: normal;
|
||||
|
||||
&:before {
|
||||
display: inline-block;
|
||||
margin-right: .5rem;
|
||||
background-image: url("/svgs/project/share-white.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: contain;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
vertical-align: middle;
|
||||
content: "";
|
||||
}
|
||||
}
|
37
src/views/preview/studio-list.jsx
Normal file
37
src/views/preview/studio-list.jsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||
const ThumbnailColumn = require('../../components/thumbnailcolumn/thumbnailcolumn.jsx');
|
||||
const projectShape = require('./projectshape.jsx').projectShape;
|
||||
|
||||
const StudioList = props => {
|
||||
const studios = props.studios;
|
||||
if (studios.length === 0) return null;
|
||||
return (
|
||||
<FlexRow className="remix-list">
|
||||
<div className="project-title">
|
||||
Studios
|
||||
</div>
|
||||
{studios.length === 0 ? (
|
||||
// TODO: style remix invitation
|
||||
<span>Invite user to add to studio</span>
|
||||
) : (
|
||||
<ThumbnailColumn
|
||||
cards
|
||||
showAvatar
|
||||
itemType="studio"
|
||||
items={studios.slice(0, 5)}
|
||||
showFavorites={false}
|
||||
showLoves={false}
|
||||
showViews={false}
|
||||
/>
|
||||
)}
|
||||
</FlexRow>
|
||||
);
|
||||
};
|
||||
|
||||
StudioList.propTypes = {
|
||||
studios: PropTypes.arrayOf(projectShape)
|
||||
};
|
||||
|
||||
module.exports = StudioList;
|
Loading…
Reference in a new issue