diff --git a/package.json b/package.json
index 143b05f6e..951a6e528 100644
--- a/package.json
+++ b/package.json
@@ -29,10 +29,12 @@
"express-http-proxy": "1.1.0",
"lodash.defaults": "4.0.1",
"newrelic": "1.25.4",
- "raven": "0.10.0"
+ "raven": "0.10.0",
+ "scratch-gui": "0.1.0-prerelease.20180529181946"
},
"devDependencies": {
"ajv": "6.4.0",
+ "approximate-number": "2.0.0",
"async": "1.5.2",
"autoprefixer": "6.3.6",
"babel-cli": "6.26.0",
@@ -95,7 +97,6 @@
"redux-thunk": "2.0.1",
"sass-lint": "1.5.1",
"sass-loader": "6.0.6",
- "scratch-gui": "0.1.0-prerelease.20180522203439",
"scratchr2_translations": "git://github.com/LLK/scratchr2_translations.git#master",
"slick-carousel": "1.6.0",
"source-map-support": "0.3.2",
diff --git a/src/_colors.scss b/src/_colors.scss
index f1fcf7fa5..2e7bd838b 100644
--- a/src/_colors.scss
+++ b/src/_colors.scss
@@ -18,9 +18,11 @@ $ui-border: hsla(0, 0, 85, 1); //#D9D9D9
/* 3.0 colors */
/* Using www naming convention for now, should be consistent with gui */
-$ui-green: hsla(163, 83, 40, 1); //#0fbd8c Pen Primary
-$ui-coral: hsla(350, 100, 70, 1); //#FF6680 More Priamry
+$ui-green: hsla(163, 83, 40, 1); // #0fbd8c Pen Primary
+$ui-coral: hsla(350, 100, 70, 1); // #FF6680 More Primary
+$ui-coral-dark: hsla(350, 100, 60, 1); // #FF3355 More tertiary
$ui-blue-10percent: hsla(215, 100, 65, .1);
+$ui-blue-25percent: hsla(215, 100, 65, .25);
$ui-orange-25percent: hsla(35, 90, 55, .25);
/* Overlay UI Gray Colors */
diff --git a/src/components/forms/inplace-input.scss b/src/components/forms/inplace-input.scss
index 46deab4b9..33bc254b3 100644
--- a/src/components/forms/inplace-input.scss
+++ b/src/components/forms/inplace-input.scss
@@ -3,16 +3,18 @@
.inplace-input {
transition: all .5s ease;
- border: 2px dashed $ui-dark-gray;
- border-radius: 5px;
+ border: 2px dashed $ui-blue-25percent;
+ border-radius: 8px;
background-color: transparent;
padding: 0 1rem;
+ width: calc(100% - 2.25rem);
color: $type-gray;
&:focus {
transition: all .5s ease;
outline: none;
- border: 1px solid $ui-blue;
+ border: 2px solid $ui-blue;
+ box-shadow: 0 0 0 4px $ui-blue-25percent;
}
&.fail {
@@ -27,31 +29,41 @@
&::-ms-reveal, &::-ms-clear {
display: none;
}
+
+ &::placeholder {
+ font-style: italic;
+ }
}
.inplace-textarea {
transition: all 1s ease;
- margin-bottom: .75rem;
- border: 2px dashed $ui-dark-gray;
- border-radius: 5px;
+ border: 2px dashed $ui-blue-25percent;
+ border-radius: 8px;
background-color: $ui-light-gray;
padding: .75rem 1rem;
- width: calc(100% - 2.25rem);
- min-height: 20rem;
+ width: 100%;
line-height: 1.75em;
color: $type-gray;
- font-size: .875rem;
+ font-size: 1rem;
+ box-sizing: border-box;
resize: none;
&:focus {
transition: all 1s ease;
outline: none;
- border: 1px solid $ui-blue;
+ border: 2px solid $ui-blue;
+ box-shadow: 0 0 0 4px $ui-blue-25percent;
}
&.fail {
border: 1px solid $ui-orange;
}
+
+ &::placeholder {
+ padding-top: 1rem;
+ text-align: center;
+ font-style: italic;
+ }
}
diff --git a/src/components/forms/textarea.scss b/src/components/forms/textarea.scss
index d1782d37f..69556f361 100644
--- a/src/components/forms/textarea.scss
+++ b/src/components/forms/textarea.scss
@@ -22,4 +22,8 @@
&.fail {
border: 1px solid $ui-orange;
}
+
+ &::placeholder {
+ font-style: italic;
+ }
}
diff --git a/src/components/modal/report/modal.jsx b/src/components/modal/report/modal.jsx
new file mode 100644
index 000000000..c6419d4f5
--- /dev/null
+++ b/src/components/modal/report/modal.jsx
@@ -0,0 +1,186 @@
+const bindAll = require('lodash.bindall');
+const PropTypes = require('prop-types');
+const React = require('react');
+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');
+const Select = require('../../forms/select.jsx');
+const Spinner = require('../../spinner/spinner.jsx');
+const TextArea = require('../../forms/textarea.jsx');
+
+require('../../forms/button.scss');
+require('./modal.scss');
+
+class ReportModal extends React.Component {
+ constructor (props) {
+ super(props);
+ bindAll(this, [
+ 'handleReasonSelect',
+ 'handleSubmit'
+ ]);
+ this.state = {
+ prompt: props.intl.formatMessage({id: 'report.promptPlaceholder'}),
+ reason: '',
+ waiting: false
+ };
+ }
+ 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});
+ }
+ 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
+ });
+ });
+ }
+ render () {
+ const {
+ intl,
+ onReport, // eslint-disable-line no-unused-vars
+ type,
+ ...modalProps
+ } = this.props;
+ const contentLabel = intl.formatMessage({id: `report.${type}`});
+ return (
+
+
+
+
+
+
+
+
+ )
+ }}
+ />
+
+
+
+
+
+ );
+ }
+}
+
+ReportModal.propTypes = {
+ intl: intlShape,
+ onReport: PropTypes.func,
+ onRequestClose: PropTypes.func,
+ type: PropTypes.string
+};
+
+module.exports = injectIntl(ReportModal);
diff --git a/src/components/modal/report/modal.scss b/src/components/modal/report/modal.scss
new file mode 100644
index 000000000..80f838d7d
--- /dev/null
+++ b/src/components/modal/report/modal.scss
@@ -0,0 +1,39 @@
+@import "../../../colors";
+@import "../../../frameless";
+
+.mod-report * {
+ box-sizing: border-box;
+}
+
+.mod-report {
+ margin: 100px auto;
+ outline: none;
+ padding: 0;
+ width: 30rem;
+ overflow: hidden;
+ user-select: none;
+}
+
+.report-modal-header {
+ box-shadow: inset 0 -1px 0 0 $ui-coral-dark;
+ background-color: $ui-coral;
+ padding-top: .75rem;
+ width: 100%;
+ height: 3rem;
+ box-sizing: border-box;
+}
+
+.report-content-label {
+ text-align: center;
+ color: $type-white;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 1rem;
+ font-weight: bold;
+}
+
+.report-modal-content {
+ margin: 1rem auto;
+ width: 80%;
+ line-height: 1.5rem;
+ font-size: .875rem;
+}
diff --git a/src/l10n.json b/src/l10n.json
index fe4b152ce..dee03efb3 100644
--- a/src/l10n.json
+++ b/src/l10n.json
@@ -159,5 +159,28 @@
"registration.welcomeStepPrompt": "To get started, click on the button below.",
"registration.welcomeStepTitle": "Hurray! Welcome to Scratch!",
- "thumbnail.by": "by"
+ "thumbnail.by": "by",
+ "report.project": "Report Project",
+ "report.projectInstructions": "From the dropdown below, please select the reason why you feel this project is disrespectful or inappropriate or otherwise breaks the {CommunityGuidelinesLink}.",
+ "report.CommunityGuidelinesLinkText": "Scratch Community Guidelines",
+ "report.reasonPlaceHolder": "Select a reason",
+ "report.reasonCopy": "Exact Copy of Project",
+ "report.reasonUncredited": "Uses Image/Music Without Credit",
+ "report.reasonScary": "Too Violent or Scary",
+ "report.reasonLanguage": "Inappropriate Language",
+ "report.reasonMusic": "Inappropriate Music",
+ "report.reasonImage": "Inappropriate Images",
+ "report.reasonPersonal": "Sharing Personal Contact Information",
+ "report.promptPlaceholder": "Select a reason why above.",
+ "report.promptCopy": "Please provide a link to the original project",
+ "report.promptUncredited": "Please provide links to the uncredited content",
+ "report.promptScary": "Please say why the project is too violent or scary",
+ "report.promptLanguage": "Please say where the inappropriate language occurs in the project (For example: Notes & Credits, sprite name, project text, etc.)",
+ "report.promptMusic": "Please say the name of the audio file with the inappropriate music",
+ "report.promptPersonal": "Please say where the personal contact information is shared (For example: Notes & Credits, sprite name, project text, etc.)",
+ "report.promptGuidelines": "Please be specific about why this project does not follow our Community Guidelines",
+ "report.promptImage": "Please say the name of the sprite or the backdrop with the inappropriate image",
+ "report.tooLongError": "That's too long! Please find a way to shorten your text.",
+ "report.tooShortError": "That's too short. Please describe in detail what's inappropriate or disrespectful about the project.",
+ "report.send": "Send"
}
\ No newline at end of file
diff --git a/src/redux/preview.js b/src/redux/preview.js
index 92c5a6c20..25732af24 100644
--- a/src/redux/preview.js
+++ b/src/redux/preview.js
@@ -13,19 +13,21 @@ module.exports.Status = keyMirror({
module.exports.getInitialState = () => ({
status: {
project: module.exports.Status.NOT_FETCHED,
- credit: module.exports.Status.NOT_FETCHED,
comments: module.exports.Status.NOT_FETCHED,
faved: module.exports.Status.NOT_FETCHED,
loved: module.exports.Status.NOT_FETCHED,
+ original: module.exports.Status.NOT_FETCHED,
+ parent: module.exports.Status.NOT_FETCHED,
remixes: module.exports.Status.NOT_FETCHED,
studios: module.exports.Status.NOT_FETCHED
},
projectInfo: {},
remixes: [],
- credit: {},
comments: [],
faved: false,
loved: false,
+ original: {},
+ parent: {},
studios: []
});
@@ -43,14 +45,18 @@ module.exports.previewReducer = (state, action) => {
return Object.assign({}, state, {
remixes: action.items
});
+ case 'SET_ORIGINAL':
+ return Object.assign({}, state, {
+ original: action.info
+ });
+ case 'SET_PARENT':
+ return Object.assign({}, state, {
+ parent: action.info
+ });
case 'SET_STUDIOS':
return Object.assign({}, state, {
studios: action.items
});
- case 'SET_CREDIT':
- return Object.assign({}, state, {
- credit: action.info
- });
case 'SET_COMMENTS':
return Object.assign({}, state, {
comments: action.items
@@ -85,8 +91,13 @@ module.exports.setProjectInfo = info => ({
info: info
});
-module.exports.setCreditInfo = info => ({
- type: 'SET_CREDIT',
+module.exports.setOriginalInfo = info => ({
+ type: 'SET_ORIGINAL',
+ info: info
+});
+
+module.exports.setParentInfo = info => ({
+ type: 'SET_PARENT',
info: info
});
@@ -140,23 +151,43 @@ module.exports.getProjectInfo = (id, token) => (dispatch => {
});
});
-module.exports.getCreditInfo = id => (dispatch => {
- dispatch(module.exports.setFetchStatus('credit', module.exports.Status.FETCHING));
+module.exports.getOriginalInfo = id => (dispatch => {
+ dispatch(module.exports.setFetchStatus('original', module.exports.Status.FETCHING));
api({
uri: `/projects/${id}`
}, (err, body) => {
if (err) {
- dispatch(module.exports.setFetchStatus('credit', module.exports.Status.ERROR));
+ dispatch(module.exports.setFetchStatus('original', module.exports.Status.ERROR));
dispatch(module.exports.setError(err));
return;
}
if (typeof body === 'undefined') {
- dispatch(module.exports.setFetchStatus('credit', module.exports.Status.ERROR));
- dispatch(module.exports.setError('No credit info'));
+ dispatch(module.exports.setFetchStatus('original', module.exports.Status.ERROR));
+ dispatch(module.exports.setError('No original info'));
return;
}
- dispatch(module.exports.setFetchStatus('credit', module.exports.Status.FETCHED));
- dispatch(module.exports.setCreditInfo(body));
+ dispatch(module.exports.setFetchStatus('original', module.exports.Status.FETCHED));
+ dispatch(module.exports.setOriginalInfo(body));
+ });
+});
+
+module.exports.getParentInfo = id => (dispatch => {
+ dispatch(module.exports.setFetchStatus('parent', module.exports.Status.FETCHING));
+ api({
+ uri: `/projects/${id}`
+ }, (err, body) => {
+ if (err) {
+ dispatch(module.exports.setFetchStatus('parent', module.exports.Status.ERROR));
+ dispatch(module.exports.setError(err));
+ return;
+ }
+ if (typeof body === 'undefined') {
+ dispatch(module.exports.setFetchStatus('parent', module.exports.Status.ERROR));
+ dispatch(module.exports.setError('No parent info'));
+ return;
+ }
+ dispatch(module.exports.setFetchStatus('parent', module.exports.Status.FETCHED));
+ dispatch(module.exports.setParentInfo(body));
});
});
diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx
index b4f5f0165..994dd23da 100644
--- a/src/views/preview/presentation.jsx
+++ b/src/views/preview/presentation.jsx
@@ -1,10 +1,11 @@
+const bindAll = require('lodash.bindall');
const FormattedDate = require('react-intl').FormattedDate;
const injectIntl = require('react-intl').injectIntl;
-const intlShape = require('react-intl').intlShape;
const PropTypes = require('prop-types');
const React = require('react');
const Formsy = require('formsy-react').default;
const classNames = require('classnames');
+const approx = require('approximate-number');
const GUI = require('scratch-gui').default;
const IntlGUI = injectIntl(GUI);
@@ -12,293 +13,393 @@ 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 InplaceInput = require('../../components/forms/inplace-input.jsx');
+const ReportModal = require('../../components/modal/report/modal.jsx');
+const projectShape = require('./projectshape.jsx').projectShape;
require('./preview.scss');
-const PreviewPresentation = props => {
- const {
- creditInfo,
- editable,
- faved,
- favoriteCount,
- intl,
- isFullScreen,
- loved,
- loveCount,
- projectId,
- projectInfo,
- remixes,
- sessionStatus,
- studios,
- user,
- onFavoriteClicked,
- onLoveClicked,
- onSeeInside,
- onUpdate
- // ...otherProps TBD
- } = props;
- const shareDate = (projectInfo.history && projectInfo.history.shared) ? projectInfo.history.shared : '';
- return (
-
-
-
-
- This project is not shared — so only you can see it. Click share to let everyone see it!
-
-
- Share
-
-
-
- { projectInfo && projectInfo.author && projectInfo.author.id && (
-
-
+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 (
+
+ {projectInfo.history && shareDate === '' &&
+
-
-
-
- {editable ?
-
-
:
-
{projectInfo.title}
- }
- {`${intl.formatMessage({id: 'thumbnail.by'})} `}
+
+ This project is not shared — so only you can see it. Click share to let everyone see it!
+
+
+ Share
+
+
+
+ }
+
+ { projectInfo && projectInfo.author && projectInfo.author.id && (
+
+
+
+
- {projectInfo.author.username}
+
+
+ {editable ?
+
+
:
+
{projectInfo.title}
+ }
+
+
+
+ {sessionStatus === sessionActions.Status.FETCHED &&
+ Object.keys(user).length > 0 &&
+ user.id !== projectInfo.author.id &&
+
+ Remix
+
+ }
+
+ See Inside
+
-
- {sessionStatus === sessionActions.Status.FETCHED &&
- Object.keys(user).length > 0 &&
- user.id !== projectInfo.author.id &&
-
- Remix
-
- }
-
- See Inside
-
-
-
-
-
-
- {shareDate && (
+
+
+
+
+
+ {parentInfo && parentInfo.author && parentInfo.id && (
+
+
+
+
+ )}
+ {originalInfo && originalInfo.author && originalInfo.id && (
+
+
+
+
+ )}
+ {/* eslint-disable max-len */}
+
+
+ Instructions
+
+ {editable ?
+ :
+
+ {decorateText(projectInfo.instructions)}
+
+ }
+
+
+
+ Notes and Credits
+
+ {editable ?
+ :
+
+ {decorateText(projectInfo.description)}
+
+ }
+
+ {/* eslint-enable max-len */}
+
+
+
+
+
+ {approx(loveCount, {decimal: false})}
+
+
+ {approx(favoriteCount, {decimal: false})}
+
+
+ {approx(projectInfo.stats.remixes, {decimal: false})}
+
+
+
+
+
+
©
{' '}
{/* eslint-disable react/jsx-sort-props */}
-
+ {shareDate === null ?
+ 'Unshared' :
+
+ }
{/* eslint-enable react/jsx-sort-props */}
- )}
- {creditInfo && creditInfo.author && creditInfo.id && (
-
-
-
+
+
+ Add to Studio
+
+
+ Copy Link
+
+ {
+ sessionStatus === sessionActions.Status.FETCHED &&
+ Object.keys(user).length > 0 &&
+ user.id !== projectInfo.author.id && [
+
+ Report
+ ,
+
+ ]
+ }
- )
- }
- {editable ?
- :
-
- {decorateText(projectInfo.description)}
-
- }
-
-
-
-
-
- {loveCount}
-
-
- {favoriteCount}
-
-
- {projectInfo.remix.count}
-
-
-
-
-
-
-
-
- Add to Studio
-
-
-
-
- Social
-
-
-
-
- Report
-
-
-
-
-
-
-
-
-
- Remixes
-
- {remixes && remixes.length === 0 ? (
- No remixes
- ) : (
-
- )}
-
+
+
+
- Studios
+ Comments go here
- {studios && studios.length === 0 ? (
-
No studios
- ) : (
-
+
+
+ {/* hide remixes if there aren't any */}
+ {remixes && remixes.length !== 0 && (
+
+
+ Remixes
+
+ {remixes && remixes.length === 0 ? (
+ // TODO: style remix invitation
+ Invite user to remix
+ ) : (
+
+ )}
+
+ )}
+ {/* hide studios if there aren't any */}
+ {studios && studios.length !== 0 && (
+
+
+ Studios
+
+ {studios && studios.length === 0 ? (
+ // TODO: invite user to add to studio?
+ None
+ ) : (
+
+ )}
+
)}
-
-
-
- )}
-
-
- );
-};
+
+
+ )}
+
+
+ );
+ }
+}
PreviewPresentation.propTypes = {
- creditInfo: PropTypes.shape({
- id: PropTypes.number,
- title: PropTypes.string,
- description: PropTypes.string,
- author: PropTypes.shape({id: PropTypes.number}),
- history: PropTypes.shape({
- created: PropTypes.string,
- modified: PropTypes.string,
- shared: PropTypes.string
- }),
- stats: PropTypes.shape({
- views: PropTypes.number,
- loves: PropTypes.number,
- favorites: PropTypes.number
- }),
- remix: PropTypes.shape({
- parent: PropTypes.number,
- root: PropTypes.number
- })
- }),
editable: PropTypes.bool,
faved: PropTypes.bool,
favoriteCount: PropTypes.number,
- intl: intlShape,
isFullScreen: PropTypes.bool,
loveCount: PropTypes.number,
loved: PropTypes.bool,
@@ -306,27 +407,10 @@ PreviewPresentation.propTypes = {
onLoveClicked: PropTypes.func,
onSeeInside: PropTypes.func,
onUpdate: PropTypes.func,
+ originalInfo: projectShape,
+ parentInfo: projectShape,
projectId: PropTypes.string,
- projectInfo: PropTypes.shape({
- id: PropTypes.number,
- title: PropTypes.string,
- description: PropTypes.string,
- author: PropTypes.shape({id: PropTypes.number}),
- history: PropTypes.shape({
- created: PropTypes.string,
- modified: PropTypes.string,
- shared: PropTypes.string
- }),
- stats: PropTypes.shape({
- views: PropTypes.number,
- loves: PropTypes.number,
- favorites: PropTypes.number
- }),
- remix: PropTypes.shape({
- parent: PropTypes.number,
- root: PropTypes.number
- })
- }),
+ projectInfo: projectShape,
remixes: PropTypes.arrayOf(PropTypes.object),
sessionStatus: PropTypes.string.isRequired,
studios: PropTypes.arrayOf(PropTypes.object),
diff --git a/src/views/preview/preview.jsx b/src/views/preview/preview.jsx
index 1c68caf84..c45f92646 100644
--- a/src/views/preview/preview.jsx
+++ b/src/views/preview/preview.jsx
@@ -7,6 +7,7 @@ const Page = require('../../components/page/www/page.jsx');
const render = require('../../lib/render.jsx');
const PreviewPresentation = require('./presentation.jsx');
+const projectShape = require('./projectshape.jsx').projectShape;
const sessionActions = require('../../redux/session.js');
const previewActions = require('../../redux/preview.js');
@@ -53,8 +54,13 @@ class Preview extends React.Component {
if (this.props.projectInfo.id !== prevProps.projectInfo.id) {
this.initCounts(this.props.projectInfo.stats.favorites, this.props.projectInfo.stats.loves);
this.handlePermissions();
- if (this.props.projectInfo.remix.root !== null) {
- this.props.getCreditInfo(this.props.projectInfo.remix.root);
+ if (this.props.projectInfo.remix.parent !== null) {
+ this.props.getParentInfo(this.props.projectInfo.remix.parent);
+ }
+ if (this.props.projectInfo.remix.root !== null &&
+ this.props.projectInfo.remix.root !== this.props.projectInfo.remix.parent
+ ) {
+ this.props.getOriginalInfo(this.props.projectInfo.remix.root);
}
}
if (this.props.playerMode !== prevProps.playerMode || this.props.fullScreen !== prevProps.fullScreen) {
@@ -64,6 +70,19 @@ 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);
}
@@ -103,19 +122,6 @@ class Preview extends React.Component {
);
}
}
- 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]
- };
- }
handleFavoriteToggle () {
this.props.setFavedStatus(
!this.props.faved,
@@ -179,13 +185,14 @@ class Preview extends React.Component {
({
projectInfo: state.preview.projectInfo,
- credit: state.preview.credit,
comments: state.preview.comments,
faved: state.preview.faved,
loved: state.preview.loved,
+ original: state.preview.original,
+ parent: state.preview.parent,
remixes: state.preview.remixes,
sessionStatus: state.session.status,
studios: state.preview.studios,
@@ -306,8 +273,11 @@ const mapStateToProps = state => ({
const mapDispatchToProps = dispatch => ({
- getCreditInfo: id => {
- dispatch(previewActions.getCreditInfo(id));
+ getOriginalInfo: id => {
+ dispatch(previewActions.getOriginalInfo(id));
+ },
+ getParentInfo: id => {
+ dispatch(previewActions.getParentInfo(id));
},
getProjectInfo: (id, token) => {
dispatch(previewActions.getProjectInfo(id, token));
@@ -333,8 +303,11 @@ const mapDispatchToProps = dispatch => ({
refreshSession: () => {
dispatch(sessionActions.refreshSession());
},
- setCreditInfo: info => {
- dispatch(previewActions.setCreditInfo(info));
+ setOriginalInfo: info => {
+ dispatch(previewActions.setOriginalInfo(info));
+ },
+ setParentInfo: info => {
+ dispatch(previewActions.setParentInfo(info));
},
updateProject: (id, formData, username, token) => {
dispatch(previewActions.updateProject(id, formData, username, token));
diff --git a/src/views/preview/preview.scss b/src/views/preview/preview.scss
index 921749f8d..c276448e3 100644
--- a/src/views/preview/preview.scss
+++ b/src/views/preview/preview.scss
@@ -1,6 +1,22 @@
@import "../../colors";
@import "../../frameless";
+/* stage size contants
+ * 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)
+*/
+$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;
@@ -15,8 +31,6 @@
height: 100%;
}
-// .gui * { box-sizing: border-box; }
-
.preview {
.project-title {
@@ -31,16 +45,29 @@
}
}
+ .project-header {
+ margin-right: 2rem;
+ flex-grow: 1;
+ justify-content: flex-start;
+ align-items: flex-start;
+
+ .inplace-input {
+ height: calc(3rem - 4px);
+ }
+ }
+
img {
&.avatar {
border: 0;
border-radius: 5px;
width: 3rem;
+ height: 3rem;
&.remix {
margin-right: .5em;
- width: 1.5rem;
+ width: 2rem;
+ height: 2rem;
}
}
}
@@ -49,6 +76,7 @@
margin-left: 1rem;
text-align: left;
font-size: .8rem;
+ flex-grow: 1;
}
.validation-message {
@@ -113,6 +141,7 @@
.share-button,
.remix-button,
.see-inside-button {
+ margin-top: 0;
font-size: .875rem;
font-weight: normal;
@@ -122,8 +151,8 @@
background-repeat: no-repeat;
background-position: center center;
background-size: contain;
- width: 1.5rem;
- height: 1.5rem;
+ width: 1.25rem;
+ height: 1.25rem;
vertical-align: middle;
content: "";
}
@@ -137,7 +166,7 @@
background-color: $ui-orange;
&:before {
- background-image: url("/svgs/favorite/favorite_type-gray.svg");
+ background-image: url("/svgs/project/share-white.svg");
}
}
@@ -145,14 +174,14 @@
background-color: $ui-green;
&:before {
- background-image: url("/svgs/remix/remix_type-gray.svg");
+ background-image: url("/svgs/project/remix-white.svg");
}
}
.see-inside-button {
&:before {
- background-image: url("/images/emoji/cool-cat.png");
+ background-image: url("/svgs/project/see-inside-white.svg");
}
}
@@ -164,19 +193,31 @@
.guiPlayer {
display: inline-block;
- width: 480px;
+ margin-left: -.5rem;
+ width: $gui-width;
}
.project-notes {
- width: 45%;
- height: 404px;
+ // not 1.5rem because of stage padding
+ margin-left: 1rem;
+ height: $stage-height;
+ align-items: flex-start;
+ flex: 1;
flex-flow: column;
- align-items: left;
}
.share-date {
- height: 2.5rem;
+ margin-right: .75rem;
vertical-align: middle;
+ line-height: 2rem;
+ color: $type-gray;
+ font-size: .875rem;
+ }
+
+ .subactions {
+ margin-left: 1.5rem;
+ justify-content: flex-end;
+ flex: 1;
}
.remix-credit {
@@ -185,7 +226,7 @@
border-radius: 8px;
background-color: $ui-blue-10percent;
padding: .5rem;
- width: 100%;
+ width: calc(100% - 1rem);
flex-wrap: nowrap;
align-items: flex-start;
}
@@ -194,22 +235,56 @@
font-size: .875rem;
flex-shrink: 1;
}
+
+ .description-block {
+ width: 100%;
+ flex-direction: column;
+ align-items: flex-start;
+ flex-grow: 1;
+ }
+
+ .project-textlabel {
+ margin: 0 0 .5rem 0;
+ font-size: 1rem;
+ font-weight: bold;
+ }
.project-description {
+ margin-bottom: .75rem;
+ border: 1px solid $ui-blue-10percent;
+ border-radius: 8px;
+ background-color: $ui-blue-10percent;
+ padding: .5rem;
+ width: calc(100% - (1rem + 2px));
+ white-space: pre-line;
+ font-size: 1rem;
+ // flex-grow
+ flex: 1;
+ }
+
+ .project-description.last {
+ margin-bottom: 0;
+ }
+
+ .project-description-edit {
+ margin-bottom: .75rem;
border: 1px solid $ui-blue-10percent;
border-radius: 8px;
background-color: $ui-blue-10percent;
padding: .5rem;
width: 100%;
white-space: pre-line;
- overflow-y: scroll;
+ // flex-grow
flex: 1;
+ &.last {
+ margin-bottom: 0;
+ }
+
&.textarea-row {
border: 0;
background-color: inherit;
padding: 0;
- overflow: visible;
}
&.has-error {
@@ -217,6 +292,14 @@
transform: translate(26rem, 0);
}
}
+
+ .inplace-textarea {
+ height: $description-input;
+ }
+ }
+
+ .project-description-edit.remixes .inplace-textarea {
+ height: $description-input-small;
}
.copyleft {
@@ -227,7 +310,7 @@
}
.stats {
- width: 480px;
+ line-height: 2rem;
justify-content: flex-start;
}
@@ -238,17 +321,18 @@
display: inline;
padding-right: 2rem;
- font-size: 1.25rem;
+ font-size: 1rem;
+ font-weight: bold;
&:before {
display: inline-block;
- margin-right: .1rem;
+ margin-right: .5rem;
background-repeat: no-repeat;
background-position: center center;
background-size: contain;
width: 1.5rem;
height: 1.5rem;
- vertical-align: text-bottom;
+ vertical-align: -.25rem;
content: "";
}
}
@@ -258,14 +342,16 @@
cursor: pointer;
&:before {
- background-image: url("/svgs/love/love_type-gray.svg");
+ opacity: .5;
+ background-image: url("/svgs/project/love-gray.svg");
}
}
.project-loves.loved {
&:before {
- background-image: url("/svgs/messages/love.svg");
+ opacity: 1;
+ background-image: url("/svgs/project/love-red.svg");
}
}
@@ -274,85 +360,122 @@
cursor: pointer;
&:before {
- background-image: url("/svgs/favorite/favorite_type-gray.svg");
+ opacity: .5;
+ background-image: url("/svgs/project/fav-gray.svg");
}
}
.project-favorites.favorited {
&:before {
- background-image: url("/svgs/messages/favorite.svg");
+ opacity: 1;
+ background-image: url("/svgs/project/fav-yellow.svg");
}
}
.project-remixes {
&:before {
- background-image: url("/svgs/remix/remix_type-gray.svg");
+ opacity: .5;
+ background-image: url("/svgs/project/remix-gray.svg");
}
}
.project-views {
&:before {
- background-image: url("/svgs/view/view_type-gray.svg");
+ opacity: .5;
+ background-image: url("/svgs/project/views-gray.svg");
}
}
.action-buttons {
display: flex;
- width: 40%;
color: $type-white;
font-size: .8rem;
font-weight: 500;
justify-content: flex-end;
flex-wrap: wrap;
-
+ }
- li {
+ .action-button {
+ margin: 0 0 0 .5rem;
+ border-radius: 19px;
+ background-color: $ui-blue;
+ padding: 0 .75rem;
+ height: 2rem;
+ text-decoration: none;
+ line-height: .875rem;
+ font-size: .75rem;
+ font-weight: normal;
+
+ // &:hover {
+ // transition: background-color .25s ease;
+ // border-color: transparent;
+ // background-color: $active-gray;
+ // }
+ //
+ // &:active {
+ // border: 0 solid transparent;
+ // box-shadow: inset 0 0 5px $box-shadow-gray;
+ // background-color: $active-dark-gray;
+ // padding: calc(.75em + 1px) calc(1.5em + 1px);
+ // }
+ //
+ // &.report {
+ // border: 1px solid $ui-coral;
+ // background-color: $ui-coral;
+ //
+ // &:hover {
+ // transition: background-color .25s ease;
+ // border-color: transparent;
+ // background-color: $active-gray;
+ // }
+ //
+ // &:active {
+ // border: 0 solid transparent;
+ // box-shadow: inset 0 0 5px $box-shadow-gray;
+ // background-color: $active-dark-gray;
+ // padding: calc(.75em + 1px) calc(1.5em + 1px);
+ // }
+ // }
+ }
+
+ .studio-button,
+ .copy-link-button,
+ .report-button {
+ &:before {
display: inline-block;
- margin: 0 5px;
- border: 1px solid $ui-blue;
- border-radius: 50px;
- background-color: $ui-blue;
- padding: .5em .75em .5em 1.5em;
- text-decoration: none;
- color: $type-white;
- list-style-type: none;
-
- &:hover {
- transition: background-color .25s ease;
- border-color: transparent;
- background-color: $active-gray;
- }
-
- &:active {
- border: 0 solid transparent;
- box-shadow: inset 0 0 5px $box-shadow-gray;
- background-color: $active-dark-gray;
- padding: calc(.75em + 1px) calc(1.5em + 1px);
- }
-
- &.report {
- border: 1px solid $ui-coral;
- background-color: $ui-coral;
-
- &:hover {
- transition: background-color .25s ease;
- border-color: transparent;
- background-color: $active-gray;
- }
-
- &:active {
- border: 0 solid transparent;
- box-shadow: inset 0 0 5px $box-shadow-gray;
- background-color: $active-dark-gray;
- padding: calc(.75em + 1px) calc(1.5em + 1px);
- }
- }
+ margin-right: .25rem;
+ background-repeat: no-repeat;
+ background-position: center center;
+ background-size: contain;
+ width: .875rem;
+ height: .875rem;
+ vertical-align: bottom;
+ content: "";
+ }
+ }
+ .studio-button {
+ &:before {
+ background-image: url("/svgs/project/studio-add-white.svg");
}
}
+ .copy-link-button {
+ &:before {
+ background-image: url("/svgs/project/copy-link-white.svg");
+ }
+ }
+
+ .report-button {
+ background-color: $ui-coral;
+
+ &:before {
+ background-image: url("/svgs/project/report-white.svg");
+ }
+ }
+
.remix-list,
.studio-list {
flex-direction: column;
@@ -372,3 +495,8 @@
}
}
}
+
+.report-text textarea {
+ // override min-height from default settings (for teacher registration)
+ min-height: 8rem;
+}
diff --git a/src/views/preview/projectshape.jsx b/src/views/preview/projectshape.jsx
new file mode 100644
index 000000000..d2dfeda4a
--- /dev/null
+++ b/src/views/preview/projectshape.jsx
@@ -0,0 +1,35 @@
+const PropTypes = require('prop-types');
+
+const {
+ number,
+ string,
+ shape
+} = PropTypes;
+
+export const projectShape = shape({
+ id: number,
+ instructions: string,
+ title: string,
+ description: string,
+ author: shape({
+ id: number,
+ username: string
+ }),
+ image: string,
+ history: shape({
+ created: string,
+ modified: string,
+ shared: string
+ }),
+ stats: shape({
+ views: number,
+ loves: number,
+ favorites: number,
+ comments: number,
+ remixes: number
+ }),
+ remix: shape({
+ parent: number,
+ root: number
+ })
+});
diff --git a/static/svgs/project/copy-link-gray.svg b/static/svgs/project/copy-link-gray.svg
new file mode 100644
index 000000000..6e4571720
--- /dev/null
+++ b/static/svgs/project/copy-link-gray.svg
@@ -0,0 +1,10 @@
+
+
+
+ copy-link-gray
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/copy-link-white.svg b/static/svgs/project/copy-link-white.svg
new file mode 100644
index 000000000..b175b51d6
--- /dev/null
+++ b/static/svgs/project/copy-link-white.svg
@@ -0,0 +1,10 @@
+
+
+
+ copy-link-white
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/delete-gray.svg b/static/svgs/project/delete-gray.svg
new file mode 100644
index 000000000..a13aef811
--- /dev/null
+++ b/static/svgs/project/delete-gray.svg
@@ -0,0 +1,17 @@
+
+
+
+ delete-gray
+ Created with Sketch.
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/fav-gray.svg b/static/svgs/project/fav-gray.svg
new file mode 100644
index 000000000..40fff7b6d
--- /dev/null
+++ b/static/svgs/project/fav-gray.svg
@@ -0,0 +1,10 @@
+
+
+
+ fav-gray
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/fav-yellow.svg b/static/svgs/project/fav-yellow.svg
new file mode 100644
index 000000000..32c1c25d0
--- /dev/null
+++ b/static/svgs/project/fav-yellow.svg
@@ -0,0 +1,10 @@
+
+
+
+ fav-yellow
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/love-gray.svg b/static/svgs/project/love-gray.svg
new file mode 100644
index 000000000..fe6a0ec3d
--- /dev/null
+++ b/static/svgs/project/love-gray.svg
@@ -0,0 +1,10 @@
+
+
+
+ love-gray
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/love-red.svg b/static/svgs/project/love-red.svg
new file mode 100644
index 000000000..c1e9457cf
--- /dev/null
+++ b/static/svgs/project/love-red.svg
@@ -0,0 +1,10 @@
+
+
+
+ love-red
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/remix-gray.svg b/static/svgs/project/remix-gray.svg
new file mode 100644
index 000000000..0701630be
--- /dev/null
+++ b/static/svgs/project/remix-gray.svg
@@ -0,0 +1,10 @@
+
+
+
+ remix-gray
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/remix-white.svg b/static/svgs/project/remix-white.svg
new file mode 100644
index 000000000..a82c1bf6d
--- /dev/null
+++ b/static/svgs/project/remix-white.svg
@@ -0,0 +1,10 @@
+
+
+
+ remix-white
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/report-gray.svg b/static/svgs/project/report-gray.svg
new file mode 100644
index 000000000..3e16a28d3
--- /dev/null
+++ b/static/svgs/project/report-gray.svg
@@ -0,0 +1,10 @@
+
+
+
+ report-gray
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/report-white.svg b/static/svgs/project/report-white.svg
new file mode 100644
index 000000000..e3ac6b7bb
--- /dev/null
+++ b/static/svgs/project/report-white.svg
@@ -0,0 +1,10 @@
+
+
+
+ report-white
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/restore-gray.svg b/static/svgs/project/restore-gray.svg
new file mode 100644
index 000000000..724a2aae1
--- /dev/null
+++ b/static/svgs/project/restore-gray.svg
@@ -0,0 +1,10 @@
+
+
+
+ restore-gray
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/revised-date-gray.svg b/static/svgs/project/revised-date-gray.svg
new file mode 100644
index 000000000..6f9decfeb
--- /dev/null
+++ b/static/svgs/project/revised-date-gray.svg
@@ -0,0 +1,13 @@
+
+
+
+ revised-date-gray
+ Created with Sketch.
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/scripts-gray.svg b/static/svgs/project/scripts-gray.svg
new file mode 100644
index 000000000..92551136a
--- /dev/null
+++ b/static/svgs/project/scripts-gray.svg
@@ -0,0 +1,10 @@
+
+
+
+ scripts-gray
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/see-inside-white.svg b/static/svgs/project/see-inside-white.svg
new file mode 100644
index 000000000..104cbd330
--- /dev/null
+++ b/static/svgs/project/see-inside-white.svg
@@ -0,0 +1,10 @@
+
+
+
+ see-inside-white
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/share-white.svg b/static/svgs/project/share-white.svg
new file mode 100644
index 000000000..f1ae61a59
--- /dev/null
+++ b/static/svgs/project/share-white.svg
@@ -0,0 +1,10 @@
+
+
+
+ share-white
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/studio-add-white.svg b/static/svgs/project/studio-add-white.svg
new file mode 100644
index 000000000..35142d5c9
--- /dev/null
+++ b/static/svgs/project/studio-add-white.svg
@@ -0,0 +1,10 @@
+
+
+
+ studio-add-white
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/views-gray.svg b/static/svgs/project/views-gray.svg
new file mode 100644
index 000000000..e159580e4
--- /dev/null
+++ b/static/svgs/project/views-gray.svg
@@ -0,0 +1,13 @@
+
+
+
+ views-gray
+ Created with Sketch.
+
+
+
+
+
+
+
+
\ No newline at end of file