From d42802528755b23d9e08e233bfeaec74cddc0702 Mon Sep 17 00:00:00 2001 From: Colby Gutierrez-Kraybill Date: Fri, 23 Mar 2018 15:49:02 -0400 Subject: [PATCH 01/72] Need protocol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With API_HOST and FALLBACK, need full protocol. API_HOST must be localhost, as it’s the browser performing the local request and does not know the docker network hostnames assigned during docker-compose --- docker-compose.yml | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 docker-compose.yml diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..8c87e0f07 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,40 @@ +version: '3.4' +volumes: + npm_data: + runtime_data: + intl_data: + +networks: + scratchapi_scratch_network: + external: true + +services: + app: + container_name: scratch-www-app + hostname: scratch-www-app + environment: + - API_HOST=scratch-api-app + - FALLBACK=scratchr2-app:8080 + build: + context: ./ + dockerfile: Dockerfile + image: scratch-www:latest + command: ./docker_entrypoint.sh npm start + volumes: + - type: bind + source: ./ + target: /var/app/current + volume: + nocopy: true + - type: bind + source: ../scratch-gui + target: /var/app/current/scratch-gui + volume: + nocopy: true + - npm_data:/var/app/current/node_modules + - runtime_data:/runtime + - intl_data:/var/app/current/intl + ports: + - "8333:8333" + networks: + - scratchapi_scratch_network From 878116f949245a9fdef65d32e8548e1da335b7c8 Mon Sep 17 00:00:00 2001 From: Colby Gutierrez-Kraybill Date: Fri, 23 Mar 2018 15:49:40 -0400 Subject: [PATCH 02/72] Add protocol and localhost MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With API_HOST and FALLBACK, need full protocol. API_HOST must be localhost, as it’s the browser performing the local request and does not know the docker network hostnames assigned during docker-compose --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8c87e0f07..7f550e83f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,8 +13,8 @@ services: container_name: scratch-www-app hostname: scratch-www-app environment: - - API_HOST=scratch-api-app - - FALLBACK=scratchr2-app:8080 + - API_HOST=http://localhost:8491 + - FALLBACK=http://scratchr2-app:8080 build: context: ./ dockerfile: Dockerfile From 70468321c5ed1737fa7baa979205b088cfa5fce3 Mon Sep 17 00:00:00 2001 From: Colby Gutierrez-Kraybill Date: Fri, 23 Mar 2018 15:50:01 -0400 Subject: [PATCH 03/72] Docker files Completes first pass at dockerization --- Dockerfile | 10 ++++++++++ docker_entrypoint.sh | 11 +++++++++++ 2 files changed, 21 insertions(+) create mode 100644 Dockerfile create mode 100755 docker_entrypoint.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..81b021d64 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM node:6 + +RUN mkdir -p /var/app/current +WORKDIR /var/app/current +COPY . ./ +RUN rm -rf ./node_modules +RUN npm install + +EXPOSE 8333 + diff --git a/docker_entrypoint.sh b/docker_entrypoint.sh new file mode 100755 index 000000000..5e95584c3 --- /dev/null +++ b/docker_entrypoint.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +echo "App Entrypoint" + +if [ ! -f /runtime/.translations ]; then + echo "Generating intl/translations" + make translations + touch /runtime/.translations +fi + +exec "$@" From 5b1891efdbf87e763140cc486347964585487f0a Mon Sep 17 00:00:00 2001 From: Colby Gutierrez-Kraybill Date: Tue, 27 Mar 2018 14:11:24 -0400 Subject: [PATCH 04/72] node:8 is current --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 81b021d64..7ed7e0da3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:6 +FROM node:8 RUN mkdir -p /var/app/current WORKDIR /var/app/current From 89f731efa75c329591c0152162d971719dbea564 Mon Sep 17 00:00:00 2001 From: Colby Gutierrez-Kraybill Date: Tue, 27 Mar 2018 15:14:07 -0400 Subject: [PATCH 05/72] Add docker info and documentation --- README.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/README.md b/README.md index d5209408f..f2fcdae8e 100644 --- a/README.md +++ b/README.md @@ -113,5 +113,45 @@ Setting `FALLBACK=https://scratch.mit.edu` allows the web client to retrieve dat Additionally, if you set `FALLBACK=https://scratch.mit.edu`, be aware that clicking on links to parts of the website not yet migrated over (currently such as `Explore`, `Discuss`, `Profile`, etc.) will take you to the Scratch website itself. +#### Docker + +A set of [Docker](https://www.docker.com/what-docker) related files are provided to create isolated [container](https://www.docker.com/what-container) environments suitable for development: + +* Dockerfile +* docker-compose.yml +* docker_entrypoint.sh + +Unless you have access to the Scratch REST API repository, this may not be very useful for you. + +This currently requires a version of the [Scratch GUI](https://github.com/LLK/scratch-gui) repository to be checked out in parallel to scratch-www, e.g.: + +```bash +- + - scratch-www/ + - scratch-gui/ +``` + +##### Docker Quick Start (CLI) + +Make sure you already have the Scratch REST API running locally in its docker environment. + +``` +$ docker-compose up +``` + +After this has launched you will be able to access a running copy of `scratch-www` on port 8333 via `http://localhost:8333` + +##### Docker Configuration + +`Dockerfile` defines how a `scratch-www` docker image is created. + +`docker-compose.yml` takes care of launching `scratch-www` into a development environment that is composed of other components, such as the Scratch REST API server and the legacy Scratch code. If you have not already setup the Scratch REST API in your local environment, you will need to modify `docker-compose.yml` by removing `external: true` from: + +```yaml +networks: + scratchapi_scratch_network: + external: true +``` + #### Windows Some users have experienced difficulties when trying to get our web client to work on Windows. One solution could be to use [Cygwin](https://www.cygwin.com/). If that doesn't work, you might want to use [Wubi](https://wiki.ubuntu.com/WubiGuide) (Windows XP, Vista, 7) or [Wubiuefi](https://github.com/hakuna-m/wubiuefi) (Windows 8 or higher). Wubi(uefi) is a Windows Installer for Ubuntu that allows you to have Ubuntu and Windows on one disk, without the need of an extra partition. From 5deff9c9bd9917f2f0ba83b727bfeaab17da3656 Mon Sep 17 00:00:00 2001 From: Ken Date: Wed, 30 May 2018 11:17:40 +0800 Subject: [PATCH 06/72] Fix the /about research page link --- src/views/about/about.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/about/about.jsx b/src/views/about/about.jsx index 12ca85f1f..5a52ba689 100644 --- a/src/views/about/about.jsx +++ b/src/views/about/about.jsx @@ -119,7 +119,7 @@ const About = () => ( id="about.researchDescription" values={{ researchLink: ( - + ), From 2c6ffd6fd9a8dee8e2dc71aada6e066f30380132 Mon Sep 17 00:00:00 2001 From: apple502j <33279053+apple502j@users.noreply.github.com> Date: Wed, 30 May 2018 20:14:03 +0900 Subject: [PATCH 07/72] Updated link to Scratch Wiki - to new domain --- src/views/about/about.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/about/about.jsx b/src/views/about/about.jsx index 12ca85f1f..d06b3c508 100644 --- a/src/views/about/about.jsx +++ b/src/views/about/about.jsx @@ -67,7 +67,7 @@ const About = () => ( id="about.aroundTheWorldDescription" values={{ translationLink: ( - + ) From 6872cfc51fba4ff13a9786c8253c6e5cb458883f Mon Sep 17 00:00:00 2001 From: chrisgarrity Date: Thu, 31 May 2018 16:49:17 -0400 Subject: [PATCH 08/72] FIxing PR #1900 comments addresses most comments except modal refactor, and error response from sumitting report. * restore gui as a dev dependency * better separation of view container/presentation * replace absolute heights for textareas with making all sub components `display: flex` * start to make presentation more modular added subcomponents to the view folder: * share-banner (moved from components) * remix-credit * remix-list * studio-list --- package.json | 4 +- src/components/forms/inplace-input.jsx | 2 + src/components/share-banner/share-banner.jsx | 20 -- src/components/share-banner/share-banner.scss | 10 - .../thumbnailcolumn/thumbnailcolumn.jsx | 2 +- src/views/preview/presentation.jsx | 198 ++++-------------- src/views/preview/preview.jsx | 59 +++++- src/views/preview/preview.scss | 32 +-- src/views/preview/remix-credit.jsx | 34 +++ src/views/preview/remix-list.jsx | 37 ++++ src/views/preview/share-banner.jsx | 30 +++ src/views/preview/share-banner.scss | 30 +++ src/views/preview/studio-list.jsx | 37 ++++ 13 files changed, 274 insertions(+), 221 deletions(-) delete mode 100644 src/components/share-banner/share-banner.jsx delete mode 100644 src/components/share-banner/share-banner.scss create mode 100644 src/views/preview/remix-credit.jsx create mode 100644 src/views/preview/remix-list.jsx create mode 100644 src/views/preview/share-banner.jsx create mode 100644 src/views/preview/share-banner.scss create mode 100644 src/views/preview/studio-list.jsx diff --git a/package.json b/package.json index 951a6e528..de3112fd7 100644 --- a/package.json +++ b/package.json @@ -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.20180529181946", "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/components/forms/inplace-input.jsx b/src/components/forms/inplace-input.jsx index 559de34e6..c3a97b95c 100644 --- a/src/components/forms/inplace-input.jsx +++ b/src/components/forms/inplace-input.jsx @@ -38,6 +38,8 @@ class InplaceInput extends React.Component { ( -
-
- {props.children} -
-
-); - -ShareBanner.propTypes = { - children: PropTypes.node, - className: PropTypes.string -}; - -module.exports = ShareBanner; diff --git a/src/components/share-banner/share-banner.scss b/src/components/share-banner/share-banner.scss deleted file mode 100644 index 383eeddc6..000000000 --- a/src/components/share-banner/share-banner.scss +++ /dev/null @@ -1,10 +0,0 @@ -@import "../../colors"; - -$navigation-height: 50px; - -.shareBanner { - background-color: $ui-orange-25percent; - width: 100%; - overflow: hidden; - color: $ui-orange; -} diff --git a/src/components/thumbnailcolumn/thumbnailcolumn.jsx b/src/components/thumbnailcolumn/thumbnailcolumn.jsx index cec67d56b..14cf4491f 100644 --- a/src/components/thumbnailcolumn/thumbnailcolumn.jsx +++ b/src/components/thumbnailcolumn/thumbnailcolumn.jsx @@ -14,7 +14,7 @@ const ThumbnailColumn = props => ( if (props.itemType === 'preview') { return ( - {projectInfo.history && shareDate === '' && - - - - This project is not shared — so only you can see it. Click share to let everyone see it! - - - - - } + { projectInfo && projectInfo.author && projectInfo.author.id && (
@@ -122,14 +86,19 @@ class PreviewPresentation extends React.Component { }} value={projectInfo.title} /> : -
{projectInfo.title}
+ +
{projectInfo.title}
+ {'by '} + + {projectInfo.author.username} + +
}
- {sessionStatus === sessionActions.Status.FETCHED && - Object.keys(user).length > 0 && - user.id !== projectInfo.author.id && + {/* TODO: Hide Remix button for now until implemented */} + {(!userOwnsProject && false) && @@ -154,44 +123,8 @@ class PreviewPresentation extends React.Component { />
- {parentInfo && parentInfo.author && parentInfo.id && ( - - -
- Thanks to - {parentInfo.author.username} - for the original project - {parentInfo.title} - . -
-
- )} - {originalInfo && originalInfo.author && originalInfo.id && ( - - - - - )} + + {/* eslint-disable max-len */}
@@ -311,80 +244,34 @@ class PreviewPresentation extends React.Component { - { - sessionStatus === sessionActions.Status.FETCHED && - Object.keys(user).length > 0 && - user.id !== projectInfo.author.id && [ + {(!userOwnsProject) && + , - ] + }
-
- Comments go here -
+
- {/* 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 - ) : ( - - )} -
- )} + +
@@ -401,10 +288,15 @@ PreviewPresentation.propTypes = { faved: PropTypes.bool, favoriteCount: PropTypes.number, isFullScreen: PropTypes.bool, + isReportOpen: 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 +304,8 @@ PreviewPresentation.propTypes = { projectId: PropTypes.string, projectInfo: projectShape, remixes: PropTypes.arrayOf(PropTypes.object), - sessionStatus: PropTypes.string.isRequired, 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); diff --git a/src/views/preview/preview.jsx b/src/views/preview/preview.jsx index c45f92646..3918e51ab 100644 --- a/src/views/preview/preview.jsx +++ b/src/views/preview/preview.jsx @@ -24,12 +24,28 @@ 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], + reportOpen: false + }; this.addEventListeners(); } componentDidUpdate (prevProps) { @@ -89,6 +105,21 @@ class Preview extends React.Component { removeEventListeners () { window.removeEventListener('popstate', this.handlePopState); } + handleReportClick () { + this.setState({reportOpen: true}); + } + handleReportClose () { + this.setState({reportOpen: false}); + } + handleReportSubmit (formData) { + const data = { + ...formData, + id: this.state.projectId, + username: this.props.user.username + }; + console.log('submit report data', data); // eslint-disable-line no-console + this.setState({reportOpen: false}); + } handlePopState () { const path = window.location.pathname.toLowerCase(); const playerMode = path.indexOf('editor') === -1; @@ -179,6 +210,23 @@ 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 + ) + ); + } + userOwnsProject () { + return ( + this.props.sessionStatus === sessionActions.Status.FETCHED && + Object.keys(this.props.user).length > 0 && + Object.keys(this.props.projectInfo).length > 0 && + this.props.user.id === this.props.projectInfo.author.id + ); + } render () { return ( this.props.playerMode ? @@ -189,6 +237,8 @@ class Preview extends React.Component { faved={this.props.faved} favoriteCount={this.state.favoriteCount} isFullScreen={this.state.isFullScreen} + isReportOpen={this.state.reportOpen} + isShared={this.isShared()} loveCount={this.state.loveCount} loved={this.props.loved} originalInfo={this.props.original} @@ -196,11 +246,14 @@ class Preview extends React.Component { projectId={this.state.projectId} projectInfo={this.props.projectInfo} remixes={this.props.remixes} - sessionStatus={this.props.sessionStatus} 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} /> diff --git a/src/views/preview/preview.scss b/src/views/preview/preview.scss index c276448e3..5d24ec55c 100644 --- a/src/views/preview/preview.scss +++ b/src/views/preview/preview.scss @@ -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; @@ -267,6 +247,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 +274,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); diff --git a/src/views/preview/remix-credit.jsx b/src/views/preview/remix-credit.jsx new file mode 100644 index 000000000..fafeaf4fd --- /dev/null +++ b/src/views/preview/remix-credit.jsx @@ -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 ( + + +
+ Thanks to + {projectInfo.author.username} + for the original project + {projectInfo.title} + . +
+
+ ); +}; + +RemixCredit.propTypes = { + projectInfo: projectShape +}; + +module.exports = RemixCredit; diff --git a/src/views/preview/remix-list.jsx b/src/views/preview/remix-list.jsx new file mode 100644 index 000000000..a207d8cbe --- /dev/null +++ b/src/views/preview/remix-list.jsx @@ -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 ( + +
+ Remixes +
+ {remixes.length === 0 ? ( + // TODO: style remix invitation + Invite user to remix + ) : ( + + )} +
+ ); +}; + +RemixList.propTypes = { + remixes: PropTypes.arrayOf(projectShape) +}; + +module.exports = RemixList; diff --git a/src/views/preview/share-banner.jsx b/src/views/preview/share-banner.jsx new file mode 100644 index 000000000..3585e0075 --- /dev/null +++ b/src/views/preview/share-banner.jsx @@ -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 ( +
+
+ + + This project is not shared — so only you can see it. Click share to let everyone see it! + + + +
+
+ ); +}; + +ShareBanner.propTypes = { + shared: PropTypes.bool.isRequired +}; + +module.exports = ShareBanner; diff --git a/src/views/preview/share-banner.scss b/src/views/preview/share-banner.scss new file mode 100644 index 000000000..8c5140ccb --- /dev/null +++ b/src/views/preview/share-banner.scss @@ -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: ""; + } +} diff --git a/src/views/preview/studio-list.jsx b/src/views/preview/studio-list.jsx new file mode 100644 index 000000000..89998980b --- /dev/null +++ b/src/views/preview/studio-list.jsx @@ -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 ( + +
+ Studios +
+ {studios.length === 0 ? ( + // TODO: style remix invitation + Invite user to add to studio + ) : ( + + )} +
+ ); +}; + +StudioList.propTypes = { + studios: PropTypes.arrayOf(projectShape) +}; + +module.exports = StudioList; From 47b90d449b505241b694f55cbe63d1c2007625b1 Mon Sep 17 00:00:00 2001 From: chrisgarrity Date: Fri, 1 Jun 2018 09:45:49 -0400 Subject: [PATCH 09/72] Fix formatting of long project description fields Description block also needed to be `display: flex`, and the content was missing the `overflow: auto` to make it scrollable. --- src/views/preview/preview.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/views/preview/preview.scss b/src/views/preview/preview.scss index 5d24ec55c..dc49fc651 100644 --- a/src/views/preview/preview.scss +++ b/src/views/preview/preview.scss @@ -217,10 +217,11 @@ $stage-height: 404px; } .description-block { + display: flex; width: 100%; flex-direction: column; align-items: flex-start; - flex-grow: 1; + flex: 1; } .project-textlabel { @@ -238,6 +239,7 @@ $stage-height: 404px; width: calc(100% - (1rem + 2px)); white-space: pre-line; font-size: 1rem; + overflow: auto; // flex-grow flex: 1; } From 74c76fb1cf16289d1b61c78e3e47866c5aad3f2e Mon Sep 17 00:00:00 2001 From: chrisgarrity Date: Fri, 1 Jun 2018 14:25:45 -0400 Subject: [PATCH 10/72] Refactor report-modal * addresses comments for https://github.com/LLK/scratch-www/pull/1900 * renames report fields to match scratchr2 expectations * restyle modal to allow display of validation messages * removed cruft --- src/components/modal/report/modal.jsx | 151 ++++++++++++------------- src/components/modal/report/modal.scss | 43 ++++++- src/views/preview/presentation.jsx | 15 ++- src/views/preview/preview.jsx | 52 +++++---- src/views/preview/preview.scss | 2 +- 5 files changed, 160 insertions(+), 103 deletions(-) diff --git a/src/components/modal/report/modal.jsx b/src/components/modal/report/modal.jsx index c6419d4f5..74e52a06f 100644 --- a/src/components/modal/report/modal.jsx +++ b/src/components/modal/report/modal.jsx @@ -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'); @@ -20,44 +19,70 @@ 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 + reportCategory: this.props.report.category, + options: [ + { + value: '', + label: this.props.intl.formatMessage({id: 'report.reasonPlaceHolder'}), + prompt: this.props.intl.formatMessage({id: 'report.promptPlaceholder'}) + }, + { + value: '0', + label: this.props.intl.formatMessage({id: 'report.reasonCopy'}), + prompt: this.props.intl.formatMessage({id: 'report.promptCopy'}) + }, + { + value: '1', + label: this.props.intl.formatMessage({id: 'report.reasonUncredited'}), + prompt: this.props.intl.formatMessage({id: 'report.promptUncredited'}) + }, + { + value: '2', + label: this.props.intl.formatMessage({id: 'report.reasonScary'}), + prompt: this.props.intl.formatMessage({id: 'report.promptScary'}) + }, + { + value: '3', + label: this.props.intl.formatMessage({id: 'report.reasonLanguage'}), + prompt: this.props.intl.formatMessage({id: 'report.promptLanguage'}) + }, + { + value: '4', + label: this.props.intl.formatMessage({id: 'report.reasonMusic'}), + prompt: this.props.intl.formatMessage({id: 'report.promptMusic'}) + }, + { + value: '8', + label: this.props.intl.formatMessage({id: 'report.reasonImage'}), + prompt: this.props.intl.formatMessage({id: 'report.promptImage'}) + }, + { + value: '5', + label: this.props.intl.formatMessage({id: 'report.reasonPersonal'}), + prompt: this.props.intl.formatMessage({id: 'report.promptPersonal'}) + }, + { + value: '6', + label: this.props.intl.formatMessage({id: 'general.other'}), + prompt: this.props.intl.formatMessage({id: 'report.promptGuidelines'}) + } + ] }; } - 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) { + return this.state.options.find(item => item.value === value).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 {
@@ -88,68 +114,35 @@ class ReportModal extends React.Component { />