Merge pull request #881 from LLK/release/2.2.12

[Master] Release 2.2.12
This commit is contained in:
Matthew Taylor 2016-08-25 15:48:26 -04:00 committed by GitHub
commit 6f8a6eca42
50 changed files with 1129 additions and 516 deletions

View file

@ -36,6 +36,11 @@ env:
- GA_TRACKER_VAR=GA_TRACKER_$TRAVIS_BRANCH - GA_TRACKER_VAR=GA_TRACKER_$TRAVIS_BRANCH
- GA_TRACKER=${!GA_TRACKER_VAR} - GA_TRACKER=${!GA_TRACKER_VAR}
- GA_TRACKER=${GA_TRACKER:-$GA_TRACKER_STAGING} - GA_TRACKER=${GA_TRACKER:-$GA_TRACKER_STAGING}
- SCRATCH_ENV_master=production
- SCRATCH_ENV_STAGING=staging
- SCRATCH_ENV_VAR=SCRATCH_ENV_$TRAVIS_BRANCH
- SCRATCH_ENV=${!SCRATCH_ENV_VAR}
- SCRATCH_ENV=${SCRATCH_ENV:-$SCRATCH_ENV_STAGING}
- S3_BUCKET_NAME_master=scratch-www-production - S3_BUCKET_NAME_master=scratch-www-production
- S3_BUCKET_NAME_STAGING=scratch-www-staging - S3_BUCKET_NAME_STAGING=scratch-www-staging
- S3_BUCKET_NAME_VAR=S3_BUCKET_NAME_$TRAVIS_BRANCH - S3_BUCKET_NAME_VAR=S3_BUCKET_NAME_$TRAVIS_BRANCH

View file

@ -31,8 +31,9 @@ webpack:
$(WEBPACK) --bail $(WEBPACK) --bail
sync-s3: sync-s3:
$(S3CMD) sync -P --delete-removed --exclude '.DS_Store' --exclude '*.svg' ./build/ s3://$(S3_BUCKET_NAME)/ $(S3CMD) sync -P --delete-removed --exclude '.DS_Store' --exclude '*.svg' --exclude '*.js' ./build/ s3://$(S3_BUCKET_NAME)/
$(S3CMD) sync -P --delete-removed --exclude '*' --include '*.svg' --mime-type 'image/svg+xml' ./build/ s3://$(S3_BUCKET_NAME)/ $(S3CMD) sync -P --delete-removed --exclude '*' --include '*.svg' --mime-type 'image/svg+xml' ./build/ s3://$(S3_BUCKET_NAME)/
$(S3CMD) sync -P --delete-removed --exclude '*' --include '*.js' --mime-type 'application/javascript' ./build/ s3://$(S3_BUCKET_NAME)/
sync-fastly: sync-fastly:
$(NODE) ./bin/configure-fastly.js $(NODE) ./bin/configure-fastly.js

View file

@ -1,4 +1,8 @@
{ {
"ab": {
"locale": "ab",
"parentLocale": "az"
},
"an": { "an": {
"locale": "an", "locale": "an",
"parentLocale": "ca" "parentLocale": "ca"

View file

@ -1,9 +1,11 @@
{ {
"en": "English", "ab": "Аҧсшәа",
"ar": "العربية",
"an": "Aragonés", "an": "Aragonés",
"ast": "Asturianu", "ast": "Asturianu",
"id": "Bahasa Indonesia", "id": "Bahasa Indonesia",
"ms": "Bahasa Melayu", "ms": "Bahasa Melayu",
"bg": "Български",
"ca": "Català", "ca": "Català",
"cs": "Česky", "cs": "Česky",
"cy": "Cymraeg", "cy": "Cymraeg",
@ -12,62 +14,63 @@
"de": "Deutsch", "de": "Deutsch",
"yum": "Edible Scratch", "yum": "Edible Scratch",
"et": "Eesti", "et": "Eesti",
"el": "Ελληνικά",
"en": "English",
"eo": "Esperanto", "eo": "Esperanto",
"es": "Español", "es": "Español",
"eu": "Euskara", "eu": "Euskara",
"fa": "فارسی",
"fr": "Français", "fr": "Français",
"fur": "Furlan",
"ga": "Gaeilge", "ga": "Gaeilge",
"gd": "Gàidhlig", "gd": "Gàidhlig",
"gl": "Galego", "gl": "Galego",
"ko": "한국어",
"hy": "Հայերեն",
"he": "עִבְרִית",
"hi": "हिन्दी",
"hr": "Hrvatski", "hr": "Hrvatski",
"is": "Íslenska", "is": "Íslenska",
"it": "Italiano", "it": "Italiano",
"kn": "ಭಾಷೆ-ಹೆಸರು",
"rw": "Kinyarwanda", "rw": "Kinyarwanda",
"km": "សំលៀកបំពាក",
"ht": "Kreyòl", "ht": "Kreyòl",
"ku": "Kurdî", "ku": "Kurdî",
"la": "Latina", "la": "Latina",
"lv": "Latviešu", "lv": "Latviešu",
"lt": "Lietuvių", "lt": "Lietuvių",
"mk": "Македонски",
"hu": "Magyar", "hu": "Magyar",
"ml": "മലയാളം",
"mt": "Malti", "mt": "Malti",
"mr": "मराठी",
"cat": "Meow", "cat": "Meow",
"mn": "Монгол хэл",
"my": "မြန်မာဘာသာ",
"nl": "Nederlands", "nl": "Nederlands",
"ja": "日本語",
"ja-hr": "にほんご",
"nb": "Norsk Bokmål", "nb": "Norsk Bokmål",
"nn": "Norsk Nynorsk", "nn": "Norsk Nynorsk",
"uz": "Oʻzbekcha", "uz": "Oʻzbekcha",
"th": "ไทย",
"pl": "Polski", "pl": "Polski",
"pt": "Português", "pt": "Português",
"pt-br": "Português Brasileiro", "pt-br": "Português Brasileiro",
"ro": "Română", "ro": "Română",
"ru": "Русский",
"sc": "Sardu", "sc": "Sardu",
"sq": "Shqiptar",
"sk": "Slovenčina", "sk": "Slovenčina",
"sl": "Slovenščina", "sl": "Slovenščina",
"fi": "suomi", "sr": "Српски",
"fi": "Suomi",
"sv": "Svenska", "sv": "Svenska",
"te": "తెలుగు",
"nai": "Tepehuan", "nai": "Tepehuan",
"vi": "Tiếng Việt", "vi": "Tiếng Việt",
"tr": "Türkçe", "tr": "Türkçe",
"ab": "Аҧсшәа",
"ar": "العربية",
"bg": "Български",
"el": "Ελληνικά",
"fa": "فارسی",
"he": "עִבְרִית",
"hi": "हिन्दी",
"hy": "Հայերեն",
"ja": "日本語",
"ja-hr": "にほんご",
"km": "សំលៀកបំពាក",
"kn": "ಭಾಷೆ-ಹೆಸರು",
"ko": "한국어",
"mk": "Македонски",
"ml": "മലയാളം",
"mn": "Монгол хэл",
"mr": "मराठी",
"my": "မြန်မာဘာသာ",
"ru": "Русский",
"sr": "Српски",
"th": "ไทย",
"uk": "Українська", "uk": "Українська",
"zh-cn": "简体中文", "zh-cn": "简体中文",
"zh-tw": "正體中文" "zh-tw": "正體中文"

View file

@ -74,6 +74,7 @@
"react-modal": "1.3.0", "react-modal": "1.3.0",
"react-onclickoutside": "4.1.1", "react-onclickoutside": "4.1.1",
"react-redux": "4.4.5", "react-redux": "4.4.5",
"react-responsive": "1.1.4",
"react-slick": "0.12.2", "react-slick": "0.12.2",
"react-telephone-input": "3.4.5", "react-telephone-input": "3.4.5",
"redux": "3.5.2", "redux": "3.5.2",

View file

@ -11,6 +11,7 @@ $background-color: hsla(0, 0, 99, 1); //#FDFDFD
/* UI Secondary Colors */ /* UI Secondary Colors */
$ui-aqua: hsla(170, 70, 50, 1); //#26D9BB $ui-aqua: hsla(170, 70, 50, 1); //#26D9BB
$ui-purple: hsla(265, 55, 55, 1); //#824DCB $ui-purple: hsla(265, 55, 55, 1); //#824DCB
$ui-yellow: hsla(45, 100, 50, 1); //#FFBF00
$ui-white: #fff; $ui-white: #fff;
$ui-border: hsla(0, 0, 85, 1); //#D9D9D9 $ui-border: hsla(0, 0, 85, 1); //#D9D9D9

View file

@ -5,6 +5,8 @@ var Slider = require('react-slick');
var Thumbnail = require('../thumbnail/thumbnail.jsx'); var Thumbnail = require('../thumbnail/thumbnail.jsx');
var frameless = require('../../lib/frameless.js');
require('slick-carousel/slick/slick.scss'); require('slick-carousel/slick/slick.scss');
require('slick-carousel/slick/slick-theme.scss'); require('slick-carousel/slick/slick-theme.scss');
require('./carousel.scss'); require('./carousel.scss');
@ -27,12 +29,29 @@ var Carousel = React.createClass({
render: function () { render: function () {
var settings = this.props.settings || {}; var settings = this.props.settings || {};
defaults(settings, { defaults(settings, {
centerMode: false,
dots: false, dots: false,
infinite: false, infinite: false,
lazyLoad: true, lazyLoad: true,
slidesToShow: 5, slidesToShow: 5,
slidesToScroll: 5, slidesToScroll: 5,
variableWidth: true variableWidth: true,
responsive: [
{breakpoint: frameless.mobile, settings: {
arrows: true,
slidesToScroll: 1,
slidesToShow: 1,
centerMode: true
}},
{breakpoint: frameless.tablet, settings: {
slidesToScroll: 2,
slidesToShow: 2
}},
{breakpoint: frameless.desktop, settings: {
slidesToScroll: 4,
slidesToShow: 4
}}
]
}); });
var arrows = this.props.items.length > settings.slidesToShow; var arrows = this.props.items.length > settings.slidesToShow;
var classes = classNames( var classes = classNames(

View file

@ -35,7 +35,7 @@
.intel { .intel {
img { img {
max-height: 50px max-height: 50px;
} }
} }
} }

View file

@ -1,9 +1,14 @@
var React = require('react'); var React = require('react');
var FormattedMessage = require('react-intl').FormattedMessage; var ReactIntl = require('react-intl');
var FormattedMessage = ReactIntl.FormattedMessage;
var injectIntl = ReactIntl.injectIntl;
var FooterBox = require('../container/footer.jsx'); var FooterBox = require('../container/footer.jsx');
var LanguageChooser = require('../../languagechooser/languagechooser.jsx'); var LanguageChooser = require('../../languagechooser/languagechooser.jsx');
var MediaQuery = require('react-responsive');
var frameless = require('../../../lib/frameless');
require('./footer.scss'); require('./footer.scss');
var Footer = React.createClass({ var Footer = React.createClass({
@ -11,159 +16,197 @@ var Footer = React.createClass({
render: function () { render: function () {
return ( return (
<FooterBox> <FooterBox>
<div className="lists"> <MediaQuery maxWidth={frameless.tablet - 1}>
<dl> <div className="lists">
<dt> <dl>
<FormattedMessage id='general.about' /> <dd>
</dt> <a href="/about/">
<dd> <FormattedMessage id='general.aboutScratch' />
<a href="/about/"> </a>
<FormattedMessage id='general.aboutScratch' /> </dd>
</a> <dd>
</dd> <a href="/jobs/">
<dd> <FormattedMessage id='general.jobs' />
<a href="/parents/"> </a>
<FormattedMessage id='general.forParents' /> </dd>
</a> <dd>
</dd> <a href="/contact-us/">
<dd> <FormattedMessage id='general.contactUs' />
<a href="/educators/"> </a>
<FormattedMessage id='general.forEducators' /> </dd>
</a> </dl>
</dd> <dl>
<dd> <dd>
<a href="/developers"> <a href="/terms_of_use/">
<FormattedMessage id='general.forDevelopers' /> <FormattedMessage id='general.termsOfUse' />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/info/credits/"> <a href="/privacy_policy/">
<FormattedMessage id='general.credits' /> <FormattedMessage id='general.privacyPolicy' />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/jobs/"> <a href="/community_guidelines/">
<FormattedMessage id='general.jobs' /> <FormattedMessage id='general.guidelines' />
</a> </a>
</dd> </dd>
<dd> </dl>
<a href="http://wiki.scratch.mit.edu/wiki/Scratch_Press"> </div>
<FormattedMessage id='general.press' /> </MediaQuery>
</a> <MediaQuery minWidth={frameless.tablet}>
</dd> <div className="lists">
</dl> <dl>
<dt>
<FormattedMessage id='general.about' />
</dt>
<dd>
<a href="/about/">
<FormattedMessage id='general.aboutScratch' />
</a>
</dd>
<dd>
<a href="/parents/">
<FormattedMessage id='general.forParents' />
</a>
</dd>
<dd>
<a href="/educators/">
<FormattedMessage id='general.forEducators' />
</a>
</dd>
<dd>
<a href="/developers">
<FormattedMessage id='general.forDevelopers' />
</a>
</dd>
<dd>
<a href="/info/credits/">
<FormattedMessage id='general.credits' />
</a>
</dd>
<dd>
<a href="/jobs/">
<FormattedMessage id='general.jobs' />
</a>
</dd>
<dd>
<a href="http://wiki.scratch.mit.edu/wiki/Scratch_Press">
<FormattedMessage id='general.press' />
</a>
</dd>
</dl>
<dl>
<dt>
<FormattedMessage id='general.community' />
</dt>
<dd>
<a href="/community_guidelines/">
<FormattedMessage id='general.guidelines' />
</a>
</dd>
<dd>
<a href="/discuss/">
<FormattedMessage id='footer.discuss' />
</a>
</dd>
<dd>
<a href="https://wiki.scratch.mit.edu/">
<FormattedMessage id='general.wiki' />
</a>
</dd>
<dd>
<a href="/statistics/">
<FormattedMessage id='general.statistics' />
</a>
</dd>
</dl>
<dl> <dl>
<dt> <dt>
<FormattedMessage id='general.community' /> <FormattedMessage id='general.support' />
</dt> </dt>
<dd> <dd>
<a href="/community_guidelines/"> <a href="/help/">
<FormattedMessage id='general.guidelines' /> <FormattedMessage id='footer.help' />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/discuss/"> <a href="/info/faq/">
<FormattedMessage id='footer.discuss' /> <FormattedMessage id='general.faq' />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="https://wiki.scratch.mit.edu/"> <a href="/scratch2download/">
<FormattedMessage id='general.wiki' /> <FormattedMessage id='general.offlineEditor' />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/statistics/"> <a href="/contact-us/">
<FormattedMessage id='general.statistics' /> <FormattedMessage id='general.contactUs' />
</a> </a>
</dd> </dd>
</dl> <dd>
<a href="https://secure.donationpay.org/scratchfoundation/">
<FormattedMessage id='general.donate'/>
</a>
</dd>
</dl>
<dl> <dl>
<dt> <dt>
<FormattedMessage id='general.support' /> <FormattedMessage id='general.legal'/>
</dt> </dt>
<dd> <dd>
<a href="/help/"> <a href="/terms_of_use/">
<FormattedMessage id='footer.help' /> <FormattedMessage id='general.termsOfUse' />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/info/faq/"> <a href="/privacy_policy/">
<FormattedMessage id='general.faq' /> <FormattedMessage id='general.privacyPolicy' />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/scratch2download/"> <a href="/DMCA/">
<FormattedMessage id='general.offlineEditor' /> <FormattedMessage id='general.dmca' />
</a> </a>
</dd> </dd>
<dd> </dl>
<a href="/contact-us/">
<FormattedMessage id='general.contactUs' />
</a>
</dd>
<dd>
<a href="https://secure.donationpay.org/scratchfoundation/">
<FormattedMessage id='general.donate'/>
</a>
</dd>
</dl>
<dl> <dl>
<dt> <dt>
<FormattedMessage id='general.legal'/> <FormattedMessage id='footer.scratchFamily' />
</dt> </dt>
<dd> <dd>
<a href="/terms_of_use/"> <a href="http://scratched.gse.harvard.edu/">
<FormattedMessage id='general.termsOfUse' /> <FormattedMessage id='general.scratchEd' />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/privacy_policy/"> <a href="http://www.scratchjr.org/">
<FormattedMessage id='general.privacyPolicy' /> <FormattedMessage id='general.scratchJr' />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/DMCA/"> <a href="http://day.scratch.mit.edu/">
<FormattedMessage id='general.dmca' /> <FormattedMessage id='general.scratchday' />
</a> </a>
</dd> </dd>
</dl> <dd>
<a href="/conference/">
<dl> <FormattedMessage id='general.scratchConference' />
<dt> </a>
<FormattedMessage id='footer.scratchFamily' /> </dd>
</dt> <dd>
<dd> <a href="http://www.scratchfoundation.org/">
<a href="http://scratched.gse.harvard.edu/"> <FormattedMessage id='general.scratchFoundation' />
<FormattedMessage id='general.scratchEd' /> </a>
</a> </dd>
</dd> </dl>
<dd> </div>
<a href="http://www.scratchjr.org/"> </MediaQuery>
<FormattedMessage id='general.scratchJr' /> <LanguageChooser locale={this.props.intl.locale} />
</a>
</dd>
<dd>
<a href="http://day.scratch.mit.edu/">
<FormattedMessage id='general.scratchday' />
</a>
</dd>
<dd>
<a href="/conference/">
<FormattedMessage id='general.scratchConference' />
</a>
</dd>
<dd>
<a href="http://www.scratchfoundation.org/">
<FormattedMessage id='general.scratchFoundation' />
</a>
</dd>
</dl>
</div>
<LanguageChooser />
<div className="copyright"> <div className="copyright">
<p> <p>
@ -175,4 +218,4 @@ var Footer = React.createClass({
} }
}); });
module.exports = Footer; module.exports = injectIntl(Footer);

View file

@ -1,4 +1,5 @@
@import "../../../colors"; @import "../../../colors";
@import "../../../frameless";
#footer { #footer {
.lists { .lists {
@ -6,11 +7,10 @@
text-align: center; text-align: center;
flex-direction: row; flex-direction: row;
flex-wrap: nowrap; flex-wrap: nowrap;
justify-content: space-between; justify-content: space-around;
dl { dl {
display: inline-block; display: inline-block;
width: 130pt;
vertical-align: top; vertical-align: top;
text-align: left; text-align: left;
font-size: .8rem; font-size: .8rem;

View file

@ -12,7 +12,7 @@
display: block; display: block;
float: left; float: left;
margin-right: 1rem; margin-right: 1rem;
border: 1px solid $active-gray; border: 1px solid $active-dark-gray;
border-radius: 3px; border-radius: 3px;
width: 1.25rem; width: 1.25rem;
height: 1.25rem; height: 1.25rem;
@ -22,7 +22,7 @@
&:focus { &:focus {
transition: all .5s ease; transition: all .5s ease;
outline: none; outline: none;
box-shadow: 0 0 0 .25rem $active-gray; box-shadow: 0 0 0 .25rem $active-dark-gray;
} }
&:checked { &:checked {

View file

@ -1,13 +1,15 @@
var classNames = require('classnames');
var React = require('react');
var FormsyMixin = require('formsy-react').Mixin;
var ReactPhoneInput = require('react-telephone-input/lib/withStyles');
var allCountries = require('react-telephone-input/lib/country_data').allCountries; var allCountries = require('react-telephone-input/lib/country_data').allCountries;
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; var classNames = require('classnames');
var validationHOCFactory = require('./validations.jsx').validationHOCFactory;
var Row = require('formsy-react-components').Row;
var ComponentMixin = require('formsy-react-components').ComponentMixin; var ComponentMixin = require('formsy-react-components').ComponentMixin;
var FormsyMixin = require('formsy-react').Mixin;
var React = require('react');
var ReactPhoneInput = require('react-telephone-input/lib/withStyles');
var Row = require('formsy-react-components').Row;
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
var inputHOC = require('./input-hoc.jsx'); var inputHOC = require('./input-hoc.jsx');
var intl = require('../../lib/intl.jsx');
var validationHOCFactory = require('./validations.jsx').validationHOCFactory;
var allIso2 = allCountries.map(function (country) {return country.iso2;}); var allIso2 = allCountries.map(function (country) {return country.iso2;});
@ -62,7 +64,7 @@ var PhoneInput = React.createClass({
}); });
var phoneValidationHOC = validationHOCFactory({ var phoneValidationHOC = validationHOCFactory({
isPhone: 'Please enter a valid phone number' isPhone: <intl.FormattedMessage id="teacherRegistration.validationPhoneNumber" />
}); });
module.exports = inputHOC(defaultValidationHOC(phoneValidationHOC(PhoneInput))); module.exports = inputHOC(defaultValidationHOC(phoneValidationHOC(PhoneInput)));

View file

@ -23,6 +23,7 @@ module.exports.validations = {
return phoneNumberUtil.isValidNumber(parsed); return phoneNumberUtil.isValidNumber(parsed);
} }
}; };
module.exports.validations.notEqualsUsername = module.exports.validations.notEquals;
module.exports.validationHOCFactory = function (defaultValidationErrors) { module.exports.validationHOCFactory = function (defaultValidationErrors) {
return function (Component) { return function (Component) {

View file

@ -15,7 +15,8 @@ var Grid = React.createClass({
showLoves: false, showLoves: false,
showFavorites: false, showFavorites: false,
showRemixes: false, showRemixes: false,
showViews: false showViews: false,
showAvatar: false
}; };
}, },
render: function () { render: function () {
@ -36,10 +37,13 @@ var Grid = React.createClass({
showFavorites={this.props.showFavorites} showFavorites={this.props.showFavorites}
showRemixes={this.props.showRemixes} showRemixes={this.props.showRemixes}
showViews={this.props.showViews} showViews={this.props.showViews}
showAvatar={this.props.showAvatar}
type={'project'} type={'project'}
href={href} href={href}
title={item.title} title={item.title}
src={item.image} src={item.image}
avatar={'https://cdn2.scratch.mit.edu/get_image/user/'
+ item.author.id + '_32x32.png'}
creator={item.author.username} creator={item.author.username}
loves={item.stats.loves} loves={item.stats.loves}
favorites={item.stats.favorites} favorites={item.stats.favorites}

View file

@ -1,40 +1,89 @@
@import "../../frameless"; @import "../../frameless";
@import "../../colors";
.grid { .grid {
display: inline-block; display: inline-block;
width: 100%; width: 100%;
$project-width: 200px; $thumbnail-width: 220px;
$project-height: 150px; $thumbnail-inner-width: 204px;
$gallery-width: 200px; $project-height: 208px;
$gallery-height: 118px; $gallery-height: 164px;
.flex-row { .flex-row {
margin: 0 auto; margin: 0 auto;
padding: 12px; padding: 12px 0;
width: (96px + (4 * $project-width)) / $em; width: $cols12;
justify-content: flex-start; justify-content: flex-start;
} }
.thumbnail { .thumbnail {
padding: 12px; margin: 7px;
border-radius: 4px;
box-shadow: 0 0 3px $box-shadow-gray;
background-color: $ui-white;
padding-bottom: 4px;
width: $thumbnail-width;
.thumbnail-image {
margin: 8px auto;
width: $thumbnail-inner-width;
}
.thumbnail-info {
margin: 0 auto;
width: $thumbnail-inner-width;
.creator-image {
float: left;
img {
margin-right: 8px;
border-radius: 4px;
width: 32px;
height: 32px;
}
}
.thumbnail-title {
float: left;
max-width: 164px;
.thumbnail-creator a {
color: $type-gray;
}
}
}
&.project { &.project {
width: $project-width; height: $project-height;
img { .thumbnail-image {
width: $project-width; height: 152px;
height: $project-height;
img {
margin: 0 auto;
border: 0;
border-radius: 4px;
width: $thumbnail-inner-width;
height: 152px;
}
} }
} }
&.gallery { &.gallery {
width: $gallery-width; height: $gallery-height;
img { .thumbnail-image {
width: $gallery-width; height: 120px;
height: $gallery-height;
img {
border: 0;
border-radius: 4px;
width: $thumbnail-inner-width;
height: 120px;
}
} }
} }
} }
@ -44,15 +93,24 @@
justify-content: center; justify-content: center;
} }
@media only screen and (max-width: $tablet - 1) { //4 columns
flex-direction: column; @media only screen and (max-width: $mobile - 1) {
.flex-row {
width: $cols4;
}
} }
@media only screen and (max-width: $desktop - 1) { //6 columns
@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) {
.flex-row { .flex-row {
padding: 12px 0; width: $cols6;
width: 100%; }
justify-content: space-around; }
// 8 columns
@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) {
.flex-row {
width: $cols9;
} }
} }
} }

View file

@ -16,7 +16,7 @@ var LanguageChooser = React.createClass({
getDefaultProps: function () { getDefaultProps: function () {
return { return {
languages: languages, languages: languages,
locale: window._locale locale: 'en'
}; };
}, },
onSetLanguage: function (name, value) { onSetLanguage: function (name, value) {

View file

@ -7,11 +7,11 @@
padding-top: 5px; padding-top: 5px;
font-weight: bold; font-weight: bold;
} }
.right { .right {
float: right; float: right;
} }
.spinner { .spinner {
margin: 0 .8rem; margin: 0 .8rem;
width: 1rem; width: 1rem;

View file

@ -251,7 +251,9 @@ var Navigation = React.createClass({
<a className={dropdownClasses} <a className={dropdownClasses}
href="#" onClick={this.handleAccountNavClick}> href="#" onClick={this.handleAccountNavClick}>
<Avatar src={this.props.session.session.user.thumbnailUrl} alt="" /> <Avatar src={this.props.session.session.user.thumbnailUrl} alt="" />
{this.props.session.session.user.username} <span className='profile-name'>
{this.props.session.session.user.username}
</span>
</a> </a>
<Dropdown <Dropdown
as="ul" as="ul"

View file

@ -1,4 +1,5 @@
@import "../../../colors"; @import "../../../colors";
@import "../../../frameless";
#navigation { #navigation {
&.staging { &.staging {
@ -231,3 +232,97 @@
} }
} }
} }
//4 columns
@media only screen and (max-width: $mobile - 1) {
#navigation .inner {
width: $cols4;
> ul > li {
&.login-item {
margin-left: 0;
}
&.account-nav {
margin-left: 0;
> a {
.avatar {
margin-right: 0;
}
&:after {
display: none;
}
}
}
}
.create,
.discuss,
.explore,
.search,
.help,
.mystuff,
.profile-name {
display: none;
}
}
}
//6 columns
@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) {
#navigation .inner {
width: $cols6;
> ul > li {
&.login-item {
margin-left: 0;
}
&.account-nav {
margin-left: 0;
> a {
.avatar {
margin-right: 0;
}
&:after {
display: none;
}
}
}
}
.discuss,
.explore,
.search,
.mystuff,
.profile-name {
display: none;
}
}
}
//8 columns
@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) {
#navigation .inner {
width: $cols8;
> ul > li {
&.login-item,
&.account-nav {
margin-left: 0;
}
}
.explore,
.search,
.mystuff {
display: none;
}
}
}

View file

@ -89,38 +89,55 @@ module.exports = {
onChangeShowPassword: function (field, value) { onChangeShowPassword: function (field, value) {
this.setState({showPassword: value}); this.setState({showPassword: value});
}, },
onValidSubmit: function (formData, reset, invalidate) { validateUsername: function (username, callback) {
this.setState({waiting: true}); callback = callback || function () {};
api({ api({
host: '', host: '',
uri: '/accounts/check_username/' + formData.user.username + '/' uri: '/accounts/check_username/' + username + '/'
}, function (err, res) { }, function (err, body, res) {
var formatMessage = this.props.intl.formatMessage; var formatMessage = this.props.intl.formatMessage;
this.setState({waiting: false}); if (err || res.statusCode !== 200) {
if (err) return invalidate({all: err}); err = err || formatMessage({id: 'general.error'});
res = res[0]; this.refs.form.refs.formsy.updateInputsWithError({all: err});
switch (res.msg) { return callback(false);
}
body = body[0];
switch (body.msg) {
case 'valid username': case 'valid username':
this.setState({ this.setState({
validUsername: 'pass' validUsername: 'pass'
}); });
return this.props.onNextStep(formData); return callback(true);
case 'username exists': case 'username exists':
return invalidate({ this.refs.form.refs.formsy.updateInputsWithError({
'user.username': formatMessage({id: 'registration.validationUsernameExists'}) 'user.username': formatMessage({id: 'registration.validationUsernameExists'})
}); });
return callback(false);
case 'bad username': case 'bad username':
return invalidate({ this.refs.form.refs.formsy.updateInputsWithError({
'user.username': formatMessage({id: 'registration.validationUsernameVulgar'}) 'user.username': formatMessage({id: 'registration.validationUsernameVulgar'})
}); });
return callback(false);
case 'invalid username': case 'invalid username':
default: default:
return invalidate({ this.refs.form.refs.formsy.updateInputsWithError({
'user.username': formatMessage({id: 'registration.validationUsernameInvalid'}) 'user.username': formatMessage({id: 'registration.validationUsernameInvalid'})
}); });
return callback(false);
} }
}.bind(this)); }.bind(this));
}, },
onUsernameBlur: function (event) {
this.validateUsername(event.currentTarget.value);
},
onValidSubmit: function (formData) {
this.setState({waiting: true});
this.validateUsername(formData.user.username, function (isValid) {
this.setState({waiting: false});
if (isValid) return this.props.onNextStep(formData);
}.bind(this));
},
render: function () { render: function () {
var formatMessage = this.props.intl.formatMessage; var formatMessage = this.props.intl.formatMessage;
return ( return (
@ -146,7 +163,7 @@ module.exports = {
)} )}
</p> </p>
<Card> <Card>
<Form onValidSubmit={this.onValidSubmit}> <Form onValidSubmit={this.onValidSubmit} ref="form">
<div> <div>
<div className="username-label"> <div className="username-label">
<b>{formatMessage({id: 'registration.createUsername'})}</b> <b>{formatMessage({id: 'registration.createUsername'})}</b>
@ -159,6 +176,7 @@ module.exports = {
<Input className={this.state.validUsername} <Input className={this.state.validUsername}
type="text" type="text"
name="user.username" name="user.username"
onBlur={this.onUsernameBlur}
validations={{ validations={{
matchRegexp: /^[\w-]*$/, matchRegexp: /^[\w-]*$/,
minLength: 3, minLength: 3,
@ -215,6 +233,7 @@ module.exports = {
ChoosePasswordStep: intl.injectIntl(React.createClass({ ChoosePasswordStep: intl.injectIntl(React.createClass({
getDefaultProps: function () { getDefaultProps: function () {
return { return {
username: null,
showPassword: false, showPassword: false,
waiting: false waiting: false
}; };
@ -246,7 +265,7 @@ module.exports = {
validations={{ validations={{
minLength: 6, minLength: 6,
notEquals: 'password', notEquals: 'password',
notEqualsField: 'user.username' notEqualsUsername: this.props.username
}} }}
validationErrors={{ validationErrors={{
minLength: formatMessage({ minLength: formatMessage({
@ -255,7 +274,7 @@ module.exports = {
notEquals: formatMessage({ notEquals: formatMessage({
id: 'registration.validationPasswordNotEquals' id: 'registration.validationPasswordNotEquals'
}), }),
notEqualsField: formatMessage({ notEqualsUsername: formatMessage({
id: 'registration.validationPasswordNotUsername' id: 'registration.validationPasswordNotUsername'
}) })
}} }}
@ -504,14 +523,6 @@ module.exports = {
onChooseOrganization: function (name, values) { onChooseOrganization: function (name, values) {
this.setState({otherDisabled: values.indexOf(this.organizationL10nStems.indexOf('orgChoiceOther')) === -1}); this.setState({otherDisabled: values.indexOf(this.organizationL10nStems.indexOf('orgChoiceOther')) === -1});
}, },
onValidSubmit: function (formData, reset, invalidate) {
if (formData.organization.type.length < 1) {
return invalidate({
'organization.type': this.props.intl.formatMessage({id: 'teacherRegistration.validationRequired'})
});
}
return this.props.onNextStep(formData);
},
render: function () { render: function () {
var formatMessage = this.props.intl.formatMessage; var formatMessage = this.props.intl.formatMessage;
return ( return (
@ -525,7 +536,7 @@ module.exports = {
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} /> tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
</p> </p>
<Card> <Card>
<Form onValidSubmit={this.onValidSubmit}> <Form onValidSubmit={this.props.onNextStep}>
<Input label={formatMessage({id: 'teacherRegistration.organization'})} <Input label={formatMessage({id: 'teacherRegistration.organization'})}
type="text" type="text"
name="organization.name" name="organization.name"
@ -559,11 +570,19 @@ module.exports = {
value={[]} value={[]}
options={this.getOrganizationOptions()} options={this.getOrganizationOptions()}
onChange={this.onChooseOrganization} onChange={this.onChooseOrganization}
validations={{
minLength: 1
}}
validationErrors={{
minLength: formatMessage({
id: 'teacherRegistration.validationRequired'
})
}}
required /> required />
</div> </div>
<div className="other-input"> <div className="other-input">
<Input type="text" <Input name="organization.other"
name="organization.other" type="text"
validations={{ validations={{
maxLength: 50 maxLength: 50
}} }}
@ -573,7 +592,8 @@ module.exports = {
}) })
}} }}
disabled={this.state.otherDisabled} disabled={this.state.otherDisabled}
required="isFalse" required={!this.state.otherDisabled}
help={null}
placeholder={formatMessage({id: 'general.other'})} /> placeholder={formatMessage({id: 'general.other'})} />
</div> </div>
<div className="url-input"> <div className="url-input">
@ -595,7 +615,7 @@ module.exports = {
placeholder={'http://'} /> placeholder={'http://'} />
</div> </div>
<NextStepButton waiting={this.props.waiting} <NextStepButton waiting={this.props.waiting}
text={<intl.FormattedMessage id="registration.nextStep" />} /> text={<intl.FormattedMessage id="registration.nextStep" />} />
</Form> </Form>
</Card> </Card>
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} /> <StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />

View file

@ -20,6 +20,10 @@
.row { .row {
margin-left: .5rem; margin-left: .5rem;
.validation-message {
transform: translate(14rem, 0);
}
} }
} }
@ -102,14 +106,12 @@
} }
&.usescratch-step { &.usescratch-step {
.form { .form-group {
.form-group { margin-bottom: 0;
margin-bottom: 0;
&.has-error { &.has-error {
.textarea { .textarea {
border: 1px solid $ui-orange; border: 1px solid $ui-orange;
}
} }
} }
} }
@ -189,28 +191,3 @@
} }
} }
} }
/* IE10 and IE11 fallback */
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
.registration-step {
&.username-step,
&.demographics-step,
&.name-step,
&.phone-step,
&.organization-step,
&.address-step,
&.email-step {
.validation-message {
position: relative;
transform: none;
margin: inherit;
width: 100%;
height: inherit;
&:before {
display: none;
}
}
}
}
}

View file

@ -16,9 +16,11 @@ var Tabs = React.createClass({
this.props.className this.props.className
); );
return ( return (
<SubNavigation className={classes}> <div className='tab-background'>
{this.props.children} <SubNavigation className={classes}>
</SubNavigation> {this.props.children}
</SubNavigation>
</div>
); );
} }
}); });

View file

@ -1,38 +1,53 @@
@import "../../colors"; @import "../../colors";
@import "../../frameless";
.tab-background {
box-shadow: 0 0 1px $box-shadow-gray;
background-color: $ui-white;
width: 100%;
}
.tabs { .tabs {
background-color: $ui-gray; background-color: $ui-white;
padding: 0 0 0 20px; padding: 0;
width: calc(100% - 20px); width: $cols12;
justify-content: flex-start; justify-content: center;
} }
.tabs li { .tabs li {
opacity: .75; margin: 0;
margin-bottom: -4px; border: 0;
border-top-left-radius: 10px; border-radius: 0;
border-top-right-radius: 10px; width: $cols2;
border-bottom-left-radius: 0; text-align: center;
border-bottom-right-radius: 0;
background-color: $ui-white;
color: $header-gray; color: $header-gray;
&.active {
border-bottom: 3px solid $ui-aqua;
&:hover {
border-bottom: 3px solid $ui-aqua;
color: $header-gray;
}
}
&:active {
box-shadow: none;
padding: .75em 1.5em;
}
&:hover {
border-bottom: 3px solid $active-dark-gray;
}
.tab-icon {
display: block;
margin: 0 auto;
margin-bottom: 4px;
width: 24px;
}
&:hover { &:hover {
opacity: 1;
border-color: $active-gray;
background-color: $ui-white;
color: $header-gray;
}
}
.tabs li.active {
opacity: 1;
border-bottom: 4px solid $ui-white;
&:hover {
opacity: 1;
border-bottom: 4px solid $ui-white;
background-color: $ui-white; background-color: $ui-white;
color: $header-gray; color: $header-gray;
} }

View file

@ -1,5 +1,5 @@
var React = require('react');
var classNames = require('classnames'); var classNames = require('classnames');
var React = require('react');
require('./thumbnail.scss'); require('./thumbnail.scss');
@ -13,11 +13,13 @@ var Thumbnail = React.createClass({
href: '#', href: '#',
title: 'Project', title: 'Project',
src: '', src: '',
avatar: '',
type: 'project', type: 'project',
showLoves: false, showLoves: false,
showFavorites: false, showFavorites: false,
showRemixes: false, showRemixes: false,
showViews: false, showViews: false,
showAvatar: false,
linkTitle: true, linkTitle: true,
alt: '' alt: ''
}; };
@ -29,13 +31,8 @@ var Thumbnail = React.createClass({
this.props.className this.props.className
); );
var extra = []; var extra = [];
if (this.props.creator) { var info = [];
extra.push(
<div key="creator" className="thumbnail-creator">
by <a href={'/users/' + this.props.creator + '/'}>{this.props.creator}</a>
</div>
);
}
if (this.props.loves && this.props.showLoves) { if (this.props.loves && this.props.showLoves) {
extra.push( extra.push(
<div <div
@ -76,7 +73,7 @@ var Thumbnail = React.createClass({
</div> </div>
); );
} }
var imgElement,titleElement; var imgElement,titleElement,avatarElement;
if (this.props.linkTitle) { if (this.props.linkTitle) {
imgElement = <a className="thumbnail-image" href={this.props.href}> imgElement = <a className="thumbnail-image" href={this.props.href}>
<img src={this.props.src} alt={this.props.alt} /> <img src={this.props.src} alt={this.props.alt} />
@ -87,11 +84,30 @@ var Thumbnail = React.createClass({
titleElement = this.props.title; titleElement = this.props.title;
} }
info.push(titleElement);
if (this.props.creator) {
info.push(
<div key="creator" className="thumbnail-creator">
<a href={'/users/' + this.props.creator + '/'}>{this.props.creator}</a>
</div>
);
}
if (this.props.avatar && this.props.showAvatar) {
avatarElement =
<a className="creator-image" href={'/users/' + this.props.creator + '/'}>
<img src={this.props.avatar} alt={this.props.creator} />
</a>;
}
return ( return (
<div className={classes} > <div className={classes} >
{imgElement} {imgElement}
<div className="thumbnail-title"> <div className="thumbnail-info">
{titleElement} {avatarElement}
<div className="thumbnail-title">
{info}
</div>
</div> </div>
{extra} {extra}
</div> </div>

View file

@ -90,9 +90,11 @@
$project-height: 108px; $project-height: 108px;
width: $project-width; width: $project-width;
img { .thumbnail-image {
width: $project-width; img {
height: $project-height; width: $project-width;
height: $project-height;
}
} }
} }

View file

@ -16,6 +16,7 @@
"general.discuss": "Discuss", "general.discuss": "Discuss",
"general.dmca": "DMCA", "general.dmca": "DMCA",
"general.emailAddress": "Email Address", "general.emailAddress": "Email Address",
"general.error": "Oops! Something went wrong",
"general.explore": "Explore", "general.explore": "Explore",
"general.faq": "FAQ", "general.faq": "FAQ",
"general.female": "Female", "general.female": "Female",
@ -56,6 +57,7 @@
"general.privacyPolicy": "Privacy Policy", "general.privacyPolicy": "Privacy Policy",
"general.projects": "Projects", "general.projects": "Projects",
"general.profile": "Profile", "general.profile": "Profile",
"general.resourcesTitle": "Educator Resources",
"general.scratchConference": "Scratch Conference", "general.scratchConference": "Scratch Conference",
"general.scratchday": "Scratch Day", "general.scratchday": "Scratch Day",
"general.scratchEd": "ScratchEd", "general.scratchEd": "ScratchEd",
@ -153,5 +155,7 @@
"registration.waitForApprovalDescription": "You can log into your Scratch Account now, but the features specific to Teachers are not yet available. Your information is being reviewed. Please be patient, the approval process can take up to 24 hours. You will receive an email indicating your account has been upgraded once your account has been approved.", "registration.waitForApprovalDescription": "You can log into your Scratch Account now, but the features specific to Teachers are not yet available. Your information is being reviewed. Please be patient, the approval process can take up to 24 hours. You will receive an email indicating your account has been upgraded once your account has been approved.",
"registration.welcomeStepDescription": "You have successfully set up a Scratch account! You are now a member of the class:", "registration.welcomeStepDescription": "You have successfully set up a Scratch account! You are now a member of the class:",
"registration.welcomeStepPrompt": "To get started, click on the button below.", "registration.welcomeStepPrompt": "To get started, click on the button below.",
"registration.welcomeStepTitle": "Hurray! Welcome to Scratch!" "registration.welcomeStepTitle": "Hurray! Welcome to Scratch!",
"thumbnail.by": "by"
} }

9
src/lib/frameless.js Normal file
View file

@ -0,0 +1,9 @@
/* This file contains breakpoints from _frameless.scss, to be used in MediaQuery elements.
* All units are in px, as per _frameless.scss and the MediaQuery default arguments.
*/
module.exports = {
desktop: 942,
tablet: 640,
mobile: 480
};

View file

@ -164,7 +164,7 @@ dl {
dt { dt {
font-weight: 700; font-weight: 700;
} }
dd { dd {
margin: 0; margin: 0;
} }

View file

@ -47,32 +47,28 @@
"pattern": "^/conference/plan/?$", "pattern": "^/conference/plan/?$",
"routeAlias": "/conference(?!/201[4-5])", "routeAlias": "/conference(?!/201[4-5])",
"view": "conference/plan/plan", "view": "conference/plan/plan",
"title": "Plan Your Visit", "title": "Plan Your Visit"
"viewportWidth": "device-width"
}, },
{ {
"name": "conference-expectations", "name": "conference-expectations",
"pattern": "^/conference/expect/?$", "pattern": "^/conference/expect/?$",
"routeAlias": "/conference(?!/201[4-5])", "routeAlias": "/conference(?!/201[4-5])",
"view": "conference/expect/expect", "view": "conference/expect/expect",
"title": "What to Expect", "title": "What to Expect"
"viewportWidth": "device-width"
}, },
{ {
"name": "conference-schedule", "name": "conference-schedule",
"pattern": "^/conference/schedule/?$", "pattern": "^/conference/schedule/?$",
"routeAlias": "/conference(?!/201[4-5])", "routeAlias": "/conference(?!/201[4-5])",
"view": "conference/schedule/schedule", "view": "conference/schedule/schedule",
"title": "Conference Schedule", "title": "Conference Schedule"
"viewportWidth": "device-width"
}, },
{ {
"name": "conference-details", "name": "conference-details",
"pattern": "^/conference/:id/details/?$", "pattern": "^/conference/:id/details/?$",
"routeAlias": "/conference(?!/201[4-5])", "routeAlias": "/conference(?!/201[4-5])",
"view": "conference/details/details", "view": "conference/details/details",
"title": "Event Details", "title": "Event Details"
"viewportWidth": "device-width"
}, },
{ {
"name": "developers", "name": "developers",
@ -104,11 +100,10 @@
}, },
{ {
"name": "teacherregistration", "name": "teacherregistration",
"pattern": "^/educators/register$", "pattern": "^/educators/register/?$",
"routeAlias": "/educators(?:/(faq|register|waiting))?/?$", "routeAlias": "/educators(?:/(faq|register|waiting))?/?$",
"view": "teacherregistration/teacherregistration", "view": "teacherregistration/teacherregistration",
"title": "Teacher Registration", "title": "Teacher Registration"
"viewportWidth": "device-width"
}, },
{ {
"name": "teacherwaitingroom", "name": "teacherwaitingroom",

View file

@ -7,7 +7,7 @@ module.exports = {
'and animations.', 'and animations.',
// override if mobile-friendly // override if mobile-friendly
viewportWidth: 942, viewportWidth: 'device-width',
// Open graph // Open graph
og_image: 'https://scratch.mit.edu/images/scratch-og.png', og_image: 'https://scratch.mit.edu/images/scratch-og.png',

View file

@ -6,7 +6,6 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge"> <meta http-equiv="x-ua-compatible" content="ie=edge">
<meta http-equiv="x-frame-options" content="deny">
<meta name="viewport" content="width={{viewportWidth}}, initial-scale=1"> <meta name="viewport" content="width={{viewportWidth}}, initial-scale=1">
<title>Scratch - {{title}}</title> <title>Scratch - {{title}}</title>

View file

@ -7,8 +7,11 @@ var render = require('../../lib/render.jsx');
var api = require('../../lib/api'); var api = require('../../lib/api');
var Page = require('../../components/page/www/page.jsx'); var Page = require('../../components/page/www/page.jsx');
var Box = require('../../components/box/box.jsx');
var Tabs = require('../../components/tabs/tabs.jsx'); var Tabs = require('../../components/tabs/tabs.jsx');
var TitleBanner = require('../../components/title-banner/title-banner.jsx');
var Button = require('../../components/forms/button.jsx');
var Form = require('../../components/forms/form.jsx');
var Select = require('../../components/forms/select.jsx');
var SubNavigation = require('../../components/subnavigation/subnavigation.jsx'); var SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
var Grid = require('../../components/grid/grid.jsx'); var Grid = require('../../components/grid/grid.jsx');
@ -27,16 +30,19 @@ var Explore = injectIntl(React.createClass({
stories: 'stories' stories: 'stories'
}; };
var typeOptions = ['projects','studios']; var typeOptions = ['projects','studios'];
var modeOptions = ['trending', 'popular', 'recent', ''];
var pathname = window.location.pathname.toLowerCase(); var pathname = window.location.pathname.toLowerCase();
if (pathname[pathname.length - 1] === '/') { if (pathname[pathname.length - 1] === '/') {
pathname = pathname.substring(0, pathname.length - 1); pathname = pathname.substring(0, pathname.length - 1);
} }
var slash = pathname.lastIndexOf('/'); var options = pathname.split('/');
var currentCategory = pathname.substring(slash + 1,pathname.length); var type = options[2];
var typeStart = pathname.indexOf('explore/'); var currentCategory = options[3];
var type = pathname.substring(typeStart + 8,slash); var currentMode = options.length > 4 ? options[4] : '';
if (Object.keys(categoryOptions).indexOf(currentCategory) === -1 || typeOptions.indexOf(type) === -1) { if (Object.keys(categoryOptions).indexOf(currentCategory) === -1 ||
typeOptions.indexOf(type) === -1 ||
modeOptions.indexOf(currentMode) === -1){
window.location = window.location.origin + '/explore/projects/all/'; window.location = window.location.origin + '/explore/projects/all/';
} }
@ -44,7 +50,9 @@ var Explore = injectIntl(React.createClass({
category: currentCategory, category: currentCategory,
acceptableTabs: categoryOptions, acceptableTabs: categoryOptions,
acceptableTypes: typeOptions, acceptableTypes: typeOptions,
acceptableModes: modeOptions,
itemType: type, itemType: type,
mode: currentMode,
loadNumber: 16 loadNumber: 16
}; };
}, },
@ -59,12 +67,13 @@ var Explore = injectIntl(React.createClass({
}, },
getExploreMore: function () { getExploreMore: function () {
var qText = '&q=' + this.props.acceptableTabs[this.props.category] || '*'; var qText = '&q=' + this.props.acceptableTabs[this.props.category] || '*';
api({ api({
uri: '/search/' + this.props.itemType + uri: '/search/' + this.props.itemType +
'?limit=' + this.props.loadNumber + '?limit=' + this.props.loadNumber +
'&offset=' + this.state.offset + '&offset=' + this.state.offset +
'&language=' + this.props.intl.locale + '&language=' + this.props.intl.locale +
'&mode=' + (this.props.mode ? this.props.mode : 'trending') +
qText qText
}, function (err, body) { }, function (err, body) {
if (!err) { if (!err) {
@ -84,14 +93,20 @@ var Explore = injectIntl(React.createClass({
break; break;
} }
} }
window.location = window.location.origin + '/explore/' + newType + '/' + this.props.tab; window.location = window.location.origin + '/explore/' + newType + '/' + this.props.tab + '/' + this.props.mode;
},
changeSortMode: function (name, value) {
if (this.props.acceptableModes.indexOf(value) !== -1) {
window.location = window.location.origin + '/explore/' +
this.props.itemType + '/' + this.props.category + '/' + value;
}
}, },
getBubble: function (type) { getBubble: function (type) {
var classes = classNames({ var classes = classNames({
active: (this.props.category === type) active: (this.props.category === type)
}); });
return ( return (
<a href={'/explore/' + this.props.itemType + '/' + type + '/'}> <a href={'/explore/' + this.props.itemType + '/' + type + '/' + this.props.mode}>
<li className={classes}> <li className={classes}>
<FormattedMessage id={'general.' + type} /> <FormattedMessage id={'general.' + type} />
</li> </li>
@ -103,18 +118,33 @@ var Explore = injectIntl(React.createClass({
active: (this.props.itemType === type) active: (this.props.itemType === type)
}); });
return ( return (
<a href={'/explore/' + type + '/' + this.props.category + '/'}> <a href={'/explore/' + type + '/' + this.props.category + '/' + this.props.mode}>
<li className={classes}> <li className={classes}>
{this.props.itemType === type ? [
<img src={'/svgs/tabs/' + type + '-active.svg'} className={'tab-icon ' + type} />
] : [
<img src={'/svgs/tabs/' + type + '-inactive.svg'} className={'tab-icon ' + type} />
]}
<FormattedMessage id={'general.' + type} /> <FormattedMessage id={'general.' + type} />
</li> </li>
</a> </a>
); );
}, },
render: function () { render: function () {
return ( return (
<div> <div>
<div className='outer'> <div className='outer'>
<Box title={'Explore'}> <TitleBanner className="masthead">
<div className="inner">
<h1>Explore</h1>
</div>
</TitleBanner>
<Tabs>
{this.getTab('projects')}
{this.getTab('studios')}
</Tabs>
<div className="sort-controls">
<SubNavigation className='categories'> <SubNavigation className='categories'>
{this.getBubble('all')} {this.getBubble('all')}
{this.getBubble('animations')} {this.getBubble('animations')}
@ -123,25 +153,29 @@ var Explore = injectIntl(React.createClass({
{this.getBubble('music')} {this.getBubble('music')}
{this.getBubble('stories')} {this.getBubble('stories')}
</SubNavigation> </SubNavigation>
<Tabs> <Form className='sort-mode'>
{this.getTab('projects')} <Select name="sort"
{this.getTab('studios')} options={[
</Tabs> {value: 'trending', label: 'Trending'},
<div id='projectBox' key='projectBox'> {value: 'popular', label: 'Popular'},
<Grid items={this.state.loaded} {value: 'recent', label: 'Recent'}
itemType={this.props.itemType} ]}
showLoves={false} value={this.props.mode}
showFavorites={false} onChange={this.changeSortMode}/>
showViews={false} /> </Form>
<SubNavigation className='load'> </div>
<button onClick={this.getExploreMore}> <div id='projectBox' key='projectBox'>
<li> <Grid items={this.state.loaded}
<FormattedMessage id='general.loadMore' /> itemType={this.props.itemType}
</li> cards={true}
</button> showLoves={false}
</SubNavigation> showFavorites={false}
</div> showViews={false}
</Box> showAvatar={true}/>
<Button onClick={this.getExploreMore} className="white">
<FormattedMessage id='general.loadMore' />
</Button>
</div>
</div> </div>
</div> </div>

View file

@ -4,62 +4,163 @@
$base-bg: $ui-white; $base-bg: $ui-white;
#view { #view {
.box { background-color: $ui-gray;
display: block; padding: 0;
margin-right: auto; }
margin-bottom: 20px;
margin-left: auto;
.box-content { .outer {
.title-banner {
&.masthead {
margin-bottom: 0;
background-color: $ui-yellow;
padding: 0; padding: 0;
}
}
.categories { h1 {
display: inline-block; text-align: center;
background-color: $ui-gray; color: $ui-white;
padding-left: 10px; font-size: 3rem;
width: calc(100% - 10px); }
justify-content: left;
li { p {
opacity: .75; margin: 0;
background-color: $ui-white; width: $cols6;
color: $header-gray; text-align: left;
color: $ui-white;
&:hover {
opacity: 1; a {
border-color: $active-dark-gray; border-bottom: 1px solid $ui-white;
color: $ui-white;
}
} }
} }
}
li.active { .left-pusher {
opacity: 1; margin-right: auto;
border-color: $active-dark-gray; width: 8.75rem;
}
.tabs {
width: 58.75rem;
}
/* HACK: sort controls are terrible. There's some sort of magic formula for height of formsy components that I can't control. */
.sort-controls {
display: flex;
margin: 0 auto;
border-bottom: 1px solid $ui-border;
padding: 8px 0;
width: 58.75rem;
justify-content: space-between;
}
.sort-mode {
margin-top: -4px;
width: 13.75rem;
.select {
select {
margin-bottom: 0;
border: 0;
background-color: transparent;
height: 32px;
color: $header-gray;
&:focus,
&:active {
background-color: transparent;
}
}
.help-block {
display: none;
}
}
}
.categories {
justify-content: flex-start;
li {
border: 0;
background-color: $active-gray;
color: $ui-white;
&.active {
opacity: 1;
background-color: $ui-aqua;
color: $ui-white;
}
&:active {
padding: .75em 1.5em;
}
&:hover { &:hover {
opacity: 1; background-color: $active-dark-gray;
border-color: $active-dark-gray;
} }
} }
} }
#projectBox { #projectBox {
border-top: 2px solid; background-color: $ui-gray;
border-color: $active-gray; padding-top: 16px;
background-color: $ui-white; padding-bottom: 32px;
padding-bottom: 30px;
width: 100%; width: 100%;
}
.load button { .button {
outline: None; display: block;
border: None; margin: 0 auto;
background-color: $ui-white; }
padding: 0; }
}
li {
color: $header-gray; //4 columns
@media only screen and (max-width: $mobile - 1) {
.outer {
.tabs {
width: $cols4;
}
.sort-controls {
width: $cols4;
}
}
}
//6 columns
@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) {
.outer {
.tabs {
width: $cols6;
}
.sort-controls {
width: $cols6;
}
}
}
// 8 columns
@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) {
.outer {
.tabs {
width: $cols8;
}
.sort-controls {
width: $cols8;
}
#projectBox {
.grid {
.flex-row {
width: $cols9;
}
}
} }
} }
} }

View file

@ -13,9 +13,9 @@
"faq.makeGameTitle":"How do I make a game or animation with Scratch?", "faq.makeGameTitle":"How do I make a game or animation with Scratch?",
"faq.makeGameBody":"Check out the <a href= \"/help \">help page</a> to see lots of ways to get started with Scratch. Or just <a href= \"/projects/editor/?tip_bar=getStarted \">dive in</a> to the project editor.", "faq.makeGameBody":"Check out the <a href= \"/help \">help page</a> to see lots of ways to get started with Scratch. Or just <a href= \"/projects/editor/?tip_bar=getStarted \">dive in</a> to the project editor.",
"faq.requirementsTitle":"What are the system requirements for Scratch?", "faq.requirementsTitle":"What are the system requirements for Scratch?",
"faq.requirementsBody":"To run Scratch 2, you need a relatively recent web browser (<a href= \"http://google.com/chrome/ \">Chrome</a> 35 or later, <a href= \"http://www.mozilla.org/en-US/firefox/new/ \">Firefox</a> 31 or later, or Internet Explorer 9 or later) with <a href= \" \">Adobe Flash Player</a> version 10.2 or later installed. Scratch 2 is designed to support screen sizes 1024 x 768 or larger. If your computer doesnt meet these requirements, you can try downloading and installing <a href = \"/scratch_1.4 \">Scratch 1.4</a>, which you can still use to share projects to the Scratch 2 website.", "faq.requirementsBody":"To run Scratch 2, you need to be using (1) a Mac, Linux, or Windows computer; (2) a version of <a href= \" \">Adobe Flash Player</a> released on or after June 15, 2016; (3) a relatively recent web browser: one of the latest two versions of <a href=\"http://google.com/chrome/\">Chrome</a>, <a href=\"http://www.mozilla.org/en-US/firefox/new/\">Firefox</a>, <a href=\"https://support.apple.com/downloads/safari\">Safari</a> (Mac or Windows only), <a href=\"https://www.microsoft.com/EN-US/windows/microsoft-edge\">Edge</a> (Windows only), or <a href=\"https://www.microsoft.com/en-us/download/internet-explorer.aspx\">Internet Explorer 10+</a> (Windows only). If your computer doesnt meet these requirements, you can try downloading and installing <a href=\"/scratch_1.4\">Scratch 1.4</a>, which you can still use to share projects to the Scratch 2 website.",
"faq.offlineTitle":"Do you have a downloadable version so I can create and view projects offline?", "faq.offlineTitle":"Do you have a downloadable version so I can create and view projects offline?",
"faq.offlineBody":"The <a href= \"/scratch2download/ \">Scratch 2 offline editor (beta version)</a> is now available. You can also still use <a href = \"/scratch_1.4 \">Scratch 1.4</a>. Note: You can have both Scratch 1.4 and 2 on your computer.", "faq.offlineBody":"The Scratch 2 offline editor allows you to create Scratch projects without an internet connection. You can download Scratch 2 from the <a href= \"/scratch2download/ \">website</a>. You can also still use <a href = \"/scratch_1.4 \">Scratch 1.4</a>. Note: You can have both Scratch 1.4 and 2 on your computer.",
"faq.uploadOldTitle":"Can I still upload projects created with older versions of Scratch to the website?", "faq.uploadOldTitle":"Can I still upload projects created with older versions of Scratch to the website?",
"faq.uploadOldBody":"Yes - you can share or upload projects made with earlier versions of Scratch, and they will be visible and playable. (However, you cant download projects made with or edited in later versions of Scratch and open them in earlier versions. For example, you cant open a Scratch 2 project in <a href =\"/scratch_1.4\">Scratch 1.4</a>, because <a href =\"/scratch_1.4\">Scratch 1.4</a> doesnt know how to read the .sb2 project file format.)", "faq.uploadOldBody":"Yes - you can share or upload projects made with earlier versions of Scratch, and they will be visible and playable. (However, you cant download projects made with or edited in later versions of Scratch and open them in earlier versions. For example, you cant open a Scratch 2 project in <a href =\"/scratch_1.4\">Scratch 1.4</a>, because <a href =\"/scratch_1.4\">Scratch 1.4</a> doesnt know how to read the .sb2 project file format.)",
"faq.recordVideoTitle":"Can I record a video of my Scratch project?", "faq.recordVideoTitle":"Can I record a video of my Scratch project?",

View file

@ -4,6 +4,7 @@
.top { .top {
img { img {
margin-bottom: 10px; margin-bottom: 10px;
width: 100%;
} }
} }

View file

@ -6,8 +6,10 @@ var render = require('../../lib/render.jsx');
var api = require('../../lib/api'); var api = require('../../lib/api');
var Page = require('../../components/page/www/page.jsx'); var Page = require('../../components/page/www/page.jsx');
var Box = require('../../components/box/box.jsx'); var TitleBanner = require('../../components/title-banner/title-banner.jsx');
var SubNavigation = require('../../components/subnavigation/subnavigation.jsx'); var Form = require('../../components/forms/form.jsx');
var Input = require('../../components/forms/input.jsx');
var Button = require('../../components/forms/button.jsx');
var Tabs = require('../../components/tabs/tabs.jsx'); var Tabs = require('../../components/tabs/tabs.jsx');
var Grid = require('../../components/grid/grid.jsx'); var Grid = require('../../components/grid/grid.jsx');
@ -62,6 +64,7 @@ var Search = injectIntl(React.createClass({
'?limit=' + this.props.loadNumber + '?limit=' + this.props.loadNumber +
'&offset=' + this.state.offset + '&offset=' + this.state.offset +
'&language=' + this.props.intl.locale + '&language=' + this.props.intl.locale +
'&mode=popular' +
termText termText
}, function (err, body) { }, function (err, body) {
var loadedSoFar = this.state.loaded; var loadedSoFar = this.state.loaded;
@ -71,16 +74,21 @@ var Search = injectIntl(React.createClass({
this.setState({offset: currentOffset}); this.setState({offset: currentOffset});
}.bind(this)); }.bind(this));
}, },
onSearchSubmit: function (formData) {
window.location.href = '/search/projects?q=' + formData.q;
},
getTab: function (type) { getTab: function (type) {
var term = this.props.searchTerm.split(' ').join('+'); var term = this.props.searchTerm.split(' ').join('+');
var allTab = <a href={'/search/' + type + '?q=' + term + '/'}> var allTab = <a href={'/search/' + type + '?q=' + term + '/'}>
<li> <li>
<img src={'/svgs/tabs/' + type + '-inactive.svg'} className={'tab-icon ' + type} />
<FormattedMessage id={'general.' + type} /> <FormattedMessage id={'general.' + type} />
</li> </li>
</a>; </a>;
if (this.props.tab == type) { if (this.props.tab == type) {
allTab = <a href={'/search/' + type + '?q=' + term + '/'}> allTab = <a href={'/search/' + type + '?q=' + term + '/'}>
<li className='active'> <li className='active'>
<img src={'/svgs/tabs/' + type + '-active.svg'} className={'tab-icon ' + type} />
<FormattedMessage id={'general.' + type} /> <FormattedMessage id={'general.' + type} />
</li> </li>
</a>; </a>;
@ -89,32 +97,41 @@ var Search = injectIntl(React.createClass({
}, },
render: function () { render: function () {
var formatMessage = this.props.intl.formatMessage; var formatMessage = this.props.intl.formatMessage;
return ( return (
<div> <div>
<div className='outer'> <div className='outer'>
<Box title={formatMessage({id: 'general.results'}) + ':'} <TitleBanner className="masthead">
subtitle={this.props.searchTerm} <div className="inner">
moreProps={{className: 'subnavigation'}}> <h1>Search</h1>
<div className="search">
<Form onSubmit={this.onSearchSubmit}>
<Button type="submit" className="btn-search" />
<Input type="text"
aria-label={formatMessage({id: 'general.search'})}
placeholder={formatMessage({id: 'general.search'})}
defaultValue={decodeURI(this.props.searchTerm)}
name="q" />
</Form>
</div>
</div>
</TitleBanner>
<Tabs> <Tabs>
{this.getTab('projects')} {this.getTab('projects')}
{this.getTab('studios')} {this.getTab('studios')}
</Tabs> </Tabs>
<div id='projectBox' key='projectBox'> <div id='projectBox' key='projectBox'>
<Grid items={this.state.loaded} <Grid items={this.state.loaded}
itemType={this.props.tab} itemType={this.props.tab}
showLoves={false} cards={true}
showFavorites={false} showAvatar={true}
showViews={false} /> showLoves={false}
<SubNavigation className='load'> showFavorites={false}
<button onClick={this.getSearchMore}> showViews={false} />
<li> <Button onClick={this.getSearchMore} className="white">
<FormattedMessage id='general.loadMore' /> <FormattedMessage id='general.loadMore' />
</li> </Button>
</button>
</SubNavigation>
</div> </div>
</Box>
</div> </div>
</div> </div>
); );

View file

@ -4,33 +4,181 @@
$base-bg: $ui-white; $base-bg: $ui-white;
#view { #view {
.box { background-color: $ui-gray;
display: block; padding: 0;
margin-right: auto; }
margin-bottom: 20px;
margin-left: auto;
.box-content { .outer {
.title-banner {
&.masthead {
margin-bottom: 0;
background-color: darken($ui-blue, 10%);
padding: 0; padding: 0;
}
}
#projectBox { h1 {
border-top: 2px solid; text-align: center;
border-color: $active-gray; color: $ui-white;
font-size: 3rem;
}
p {
margin: 0;
width: $cols6;
text-align: left;
color: $ui-white;
a {
border-bottom: 1px solid $ui-white;
color: $ui-white;
}
}
}
}
.search {
margin: 0 auto;
border-right: 0;
width: $cols6;
color: $type-white;
.form {
margin: 0;
}
.row {
.help-block {
display: none;
}
}
.input,
.button {
display: inline-block;
margin-top: 5px;
outline: none;
border: 0;
background-color: $active-gray;
height: 14px;
&[type=text] {
transition: .15s ease background-color;
padding: 0;
padding-right: 10px;
padding-left: 40px;
width: calc(100% - 50px);
height: 40px;
color: $type-white;
font-size: .85em;
&::placeholder {
$placeholder-transparent: rgba(255, 255, 255, .75);
color: $placeholder-transparent;
}
&:focus {
transition: .15s ease background-color;
background-color: $active-dark-gray;
}
.ie9 & {
width: 70px;
}
}
}
.btn-search {
position: absolute;
box-shadow: none;
background-color: transparent;
background-image: url("/images/nav-search-glass.png");
background-repeat: no-repeat;
background-position: center center;
background-size: 14px 14px;
width: 40px;
height: 40px;
&:hover {
box-shadow: none;
}
}
}
.select {
select {
margin-bottom: 0;
color: $header-gray;
}
.help-block {
display: none;
}
}
.tab-background {
box-shadow: 0 0 1px $box-shadow-gray;
background-color: $ui-white; background-color: $ui-white;
padding-bottom: 30px;
width: 100%; width: 100%;
} }
.load button { #projectBox {
outline: None; margin-top: 16px;
border: None; background-color: $ui-gray;
background-color: $ui-white; padding-bottom: 32px;
padding: 0; width: 100%;
li { .button {
color: $header-gray; display: block;
margin: 0 auto;
}
}
}
//4 columns
@media only screen and (max-width: $mobile - 1) {
.outer {
.search {
width: $cols4;
.btn-search {
left: 40px;
}
}
.tabs {
width: $cols4;
}
.sort-controls {
width: $cols4;
}
}
}
//6 columns
@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) {
.outer {
.tabs {
width: $cols6;
}
.sort-controls {
width: $cols6;
}
}
}
// 8 columns
@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) {
.outer {
.tabs {
width: $cols8;
}
.sort-controls {
width: $cols8;
} }
} }
} }

View file

@ -26,7 +26,6 @@
"teacherbanner.greeting": "Hi", "teacherbanner.greeting": "Hi",
"teacherbanner.subgreeting": "Teacher Account", "teacherbanner.subgreeting": "Teacher Account",
"teacherbanner.classesButton": "My Classes", "teacherbanner.classesButton": "My Classes",
"teacherbanner.resourcesButton": "Educator Resources",
"teacherbanner.faqButton": "Teacher Account FAQ", "teacherbanner.faqButton": "Teacher Account FAQ",
"welcome.welcomeToScratch": "Welcome to Scratch!", "welcome.welcomeToScratch": "Welcome to Scratch!",

View file

@ -21,6 +21,9 @@ var Page = require('../../components/page/www/page.jsx');
var TeacherBanner = require('../../components/teacher-banner/teacher-banner.jsx'); var TeacherBanner = require('../../components/teacher-banner/teacher-banner.jsx');
var Welcome = require('../../components/welcome/welcome.jsx'); var Welcome = require('../../components/welcome/welcome.jsx');
var MediaQuery = require('react-responsive');
var frameless = require('../../lib/frameless');
require('./splash.scss'); require('./splash.scss');
var Splash = injectIntl(React.createClass({ var Splash = injectIntl(React.createClass({
@ -186,16 +189,12 @@ var Splash = injectIntl(React.createClass({
var rows = [ var rows = [
<Box <Box
title={formatMessage({ title={formatMessage({id: 'splash.featuredProjects'})}
id: 'splash.featuredProjects',
defaultMessage: 'Featured Projects'})}
key="community_featured_projects"> key="community_featured_projects">
<Carousel items={this.state.featuredGlobal.community_featured_projects} /> <Carousel items={this.state.featuredGlobal.community_featured_projects} />
</Box>, </Box>,
<Box <Box
title={formatMessage({ title={formatMessage({id: 'splash.featuredStudios'})}
id: 'splash.featuredStudios',
defaultMessage: 'Featured Studios'})}
key="community_featured_studios"> key="community_featured_studios">
<Carousel items={this.state.featuredGlobal.community_featured_studios} <Carousel items={this.state.featuredGlobal.community_featured_studios}
settings={{slidesToShow: 4, slidesToScroll: 4, lazyLoad: false}} /> settings={{slidesToShow: 4, slidesToScroll: 4, lazyLoad: false}} />
@ -213,8 +212,8 @@ var Splash = injectIntl(React.createClass({
this.state.featuredGlobal.curator_top_projects[0].curator_name} this.state.featuredGlobal.curator_top_projects[0].curator_name}
moreTitle={formatMessage({id: 'general.learnMore', defaultMessage: 'Learn More'})} moreTitle={formatMessage({id: 'general.learnMore', defaultMessage: 'Learn More'})}
moreHref="/studios/386359/"> moreHref="/studios/386359/">
<Carousel
items={this.state.featuredGlobal.curator_top_projects} /> <Carousel items={this.state.featuredGlobal.curator_top_projects} />
</Box> </Box>
); );
} }
@ -226,14 +225,12 @@ var Splash = injectIntl(React.createClass({
<Box <Box
key="scratch_design_studio" key="scratch_design_studio"
title={ title={
formatMessage({ formatMessage({id: 'splash.scratchDesignStudioTitle'})
id: 'splash.scratchDesignStudioTitle',
defaultMessage: 'Scratch Design Studio' })
+ ' - ' + this.state.featuredGlobal.scratch_design_studio[0].gallery_title} + ' - ' + this.state.featuredGlobal.scratch_design_studio[0].gallery_title}
moreTitle={formatMessage({id: 'splash.visitTheStudio', defaultMessage: 'Visit the studio'})} moreTitle={formatMessage({id: 'splash.visitTheStudio', defaultMessage: 'Visit the studio'})}
moreHref={'/studios/' + this.state.featuredGlobal.scratch_design_studio[0].gallery_id + '/'}> moreHref={'/studios/' + this.state.featuredGlobal.scratch_design_studio[0].gallery_id + '/'}>
<Carousel
items={this.state.featuredGlobal.scratch_design_studio} /> <Carousel items={this.state.featuredGlobal.scratch_design_studio} />
</Box> </Box>
); );
} }
@ -243,14 +240,9 @@ var Splash = injectIntl(React.createClass({
this.state.featuredGlobal.community_newest_projects.length > 0) { this.state.featuredGlobal.community_newest_projects.length > 0) {
rows.push( rows.push(
<Box <Box title={formatMessage({id: 'splash.recentlySharedProjects'})}
title={ key="community_newest_projects">
formatMessage({ <Carousel items={this.state.featuredGlobal.community_newest_projects} />
id: 'splash.recentlySharedProjects',
defaultMessage: 'Recently Shared Projects' })}
key="community_newest_projects">
<Carousel
items={this.state.featuredGlobal.community_newest_projects} />
</Box> </Box>
); );
} }
@ -259,12 +251,9 @@ var Splash = injectIntl(React.createClass({
this.state.featuredCustom.custom_projects_by_following.length > 0) { this.state.featuredCustom.custom_projects_by_following.length > 0) {
rows.push( rows.push(
<Box title={ <Box title={formatMessage({id: 'splash.projectsByScratchersFollowing'})}
formatMessage({
id: 'splash.projectsByScratchersFollowing',
defaultMessage: 'Projects by Scratchers I\'m Following'})}
key="custom_projects_by_following"> key="custom_projects_by_following">
<Carousel items={this.state.featuredCustom.custom_projects_by_following} /> <Carousel items={this.state.featuredCustom.custom_projects_by_following} />
</Box> </Box>
); );
@ -273,12 +262,9 @@ var Splash = injectIntl(React.createClass({
this.state.featuredCustom.custom_projects_loved_by_following.length > 0) { this.state.featuredCustom.custom_projects_loved_by_following.length > 0) {
rows.push( rows.push(
<Box title={ <Box title={formatMessage({id: 'splash.projectsLovedByScratchersFollowing'})}
formatMessage({
id: 'splash.projectsLovedByScratchersFollowing',
defaultMessage: 'Projects Loved by Scratchers I\'m Following'})}
key="custom_projects_loved_by_following"> key="custom_projects_loved_by_following">
<Carousel items={this.state.featuredCustom.custom_projects_loved_by_following} /> <Carousel items={this.state.featuredCustom.custom_projects_loved_by_following} />
</Box> </Box>
); );
@ -288,31 +274,22 @@ var Splash = injectIntl(React.createClass({
this.state.featuredCustom.custom_projects_in_studios_following.length > 0) { this.state.featuredCustom.custom_projects_in_studios_following.length > 0) {
rows.push( rows.push(
<Box title={ <Box title={formatMessage({id:'splash.projectsInStudiosFollowing'})}
formatMessage({
id:'splash.projectsInStudiosFollowing',
defaultMessage: 'Projects in Studios I\'m Following'})}
key="custom_projects_in_studios_following"> key="custom_projects_in_studios_following">
<Carousel items={this.state.featuredCustom.custom_projects_in_studios_following} /> <Carousel items={this.state.featuredCustom.custom_projects_in_studios_following} />
</Box> </Box>
); );
} }
rows.push( rows.push(
<Box title={ <Box title={formatMessage({id: 'splash.communityRemixing'})}
formatMessage({
id: 'splash.communityRemixing',
defaultMessage: 'What the Community is Remixing' })}
key="community_most_remixed_projects"> key="community_most_remixed_projects">
<Carousel items={shuffle(this.state.featuredGlobal.community_most_remixed_projects)} <Carousel items={shuffle(this.state.featuredGlobal.community_most_remixed_projects)}
showRemixes={true} /> showRemixes={true} />
</Box>, </Box>,
<Box title={ <Box title={formatMessage({id: 'splash.communityLoving'})}
formatMessage({
id: 'splash.communityLoving',
defaultMessage: 'What the Community is Loving' })}
key="community_most_loved_projects"> key="community_most_loved_projects">
<Carousel items={shuffle(this.state.featuredGlobal.community_most_loved_projects)} <Carousel items={shuffle(this.state.featuredGlobal.community_most_loved_projects)}
@ -347,7 +324,7 @@ var Splash = injectIntl(React.createClass({
'teacherbanner.greeting': formatMessage({id: 'teacherbanner.greeting'}), 'teacherbanner.greeting': formatMessage({id: 'teacherbanner.greeting'}),
'teacherbanner.subgreeting': formatMessage({id: 'teacherbanner.subgreeting'}), 'teacherbanner.subgreeting': formatMessage({id: 'teacherbanner.subgreeting'}),
'teacherbanner.classesButton': formatMessage({id: 'teacherbanner.classesButton'}), 'teacherbanner.classesButton': formatMessage({id: 'teacherbanner.classesButton'}),
'teacherbanner.resourcesButton': formatMessage({id: 'teacherbanner.resourcesButton'}), 'teacherbanner.resourcesButton': formatMessage({id: 'general.resourcesTitle'}),
'teacherbanner.faqButton': formatMessage({id: 'teacherbanner.faqButton'}) 'teacherbanner.faqButton': formatMessage({id: 'teacherbanner.faqButton'})
}; };
if (this.state.projectCount === this.getInitialState().projectCount) { if (this.state.projectCount === this.getInitialState().projectCount) {
@ -393,7 +370,9 @@ var Splash = injectIntl(React.createClass({
<News items={this.state.news} messages={messages} /> <News items={this.state.news} messages={messages} />
</div> </div>
] : [ ] : [
<Intro projectCount={this.state.projectCount} messages={messages} key="intro"/> <MediaQuery minWidth={frameless.desktop}>
<Intro projectCount={this.state.projectCount} messages={messages} key="intro"/>
</MediaQuery>
]) : [] ]) : []
} }

View file

@ -1,3 +1,5 @@
@import "../../frameless";
.splash { .splash {
.splash-header { .splash-header {
display: flex; display: flex;
@ -24,7 +26,7 @@
.news { .news {
width: 40%; width: 40%;
img { img {
flex-shrink: 0; flex-shrink: 0;
} }
@ -38,4 +40,42 @@
.box { .box {
margin-bottom: 20px; margin-bottom: 20px;
} }
} }
//4 columns
@media only screen and (max-width: $mobile - 1) {
.splash {
.splash-header {
flex-wrap: wrap;
justify-content: center;
.box {
width: $cols4;
}
}
}
}
//6 columns
@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) {
.splash {
.splash-header {
flex-wrap: wrap;
justify-content: center;
.box {
width: $cols6;
}
}
}
}
//6 columns
@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) {
.splash {
.splash-header {
margin: 0 auto;
width: $cols8;
}
}
}

View file

@ -132,7 +132,8 @@ var StudentCompleteRegistration = intl.injectIntl(React.createClass({
{this.props.must_reset_password ? {this.props.must_reset_password ?
<Steps.ChoosePasswordStep onNextStep={this.advanceStep} <Steps.ChoosePasswordStep onNextStep={this.advanceStep}
showPassword={true} showPassword={true}
waiting={this.state.waiting} /> waiting={this.state.waiting}
username={this.props.studentUsername} />
: :
[] []
} }

View file

@ -20,18 +20,22 @@ var TeacherFaq = injectIntl(React.createClass({
<dl> <dl>
<dt><FormattedMessage id='teacherfaq.teacherWhatTitle' /></dt> <dt><FormattedMessage id='teacherfaq.teacherWhatTitle' /></dt>
<dd><FormattedHTMLMessage id='teacherfaq.teacherWhatBody' /></dd> <dd><FormattedHTMLMessage id='teacherfaq.teacherWhatBody' /></dd>
<dt><FormattedMessage id='teacherfaq.teacherQuestionsTitle' /></dt> <iframe width="565" height="318" src="https://www.youtube.com/embed/7Hl9GxA1zwQ"
<dd><FormattedHTMLMessage id='teacherfaq.teacherQuestionsBody' /></dd> frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
<dt><FormattedMessage id='teacherfaq.teacherGoogleTitle' /></dt> <dt><FormattedMessage id='teacherfaq.teacherSignUpTitle' /></dt>
<dd><FormattedHTMLMessage id='teacherfaq.teacherGoogleBody' /></dd> <dd><FormattedHTMLMessage id='teacherfaq.teacherSignUpBody' /></dd>
<dt><FormattedMessage id='teacherfaq.teacherEdTitle' /></dt>
<dd><FormattedHTMLMessage id='teacherfaq.teacherEdBody' /></dd>
<dt><FormattedMessage id='teacherfaq.teacherWaitTitle' /></dt> <dt><FormattedMessage id='teacherfaq.teacherWaitTitle' /></dt>
<dd><FormattedHTMLMessage id='teacherfaq.teacherWaitBody' /></dd> <dd><FormattedHTMLMessage id='teacherfaq.teacherWaitBody' /></dd>
<dt><FormattedMessage id='teacherfaq.teacherPersonalTitle' /></dt> <dt><FormattedMessage id='teacherfaq.teacherPersonalTitle' /></dt>
<dd><FormattedHTMLMessage id='teacherfaq.teacherPersonalBody' /></dd> <dd><FormattedHTMLMessage id='teacherfaq.teacherPersonalBody' /></dd>
<dt><FormattedMessage id='teacherfaq.teacherGoogleTitle' /></dt>
<dd><FormattedHTMLMessage id='teacherfaq.teacherGoogleBody' /></dd>
<dt><FormattedMessage id='teacherfaq.teacherEdTitle' /></dt>
<dd><FormattedHTMLMessage id='teacherfaq.teacherEdBody' /></dd>
<dt><FormattedMessage id='teacherfaq.teacherMultipleTitle' /></dt> <dt><FormattedMessage id='teacherfaq.teacherMultipleTitle' /></dt>
<dd><FormattedHTMLMessage id='teacherfaq.teacherMultipleBody' /></dd> <dd><FormattedHTMLMessage id='teacherfaq.teacherMultipleBody' /></dd>
<dt><FormattedMessage id='teacherfaq.teacherQuestionsTitle' /></dt>
<dd><FormattedHTMLMessage id='teacherfaq.teacherQuestionsBody' /></dd>
</dl> </dl>
</section> </section>
<section id="student-accounts"> <section id="student-accounts">

View file

@ -1,19 +1,21 @@
{ {
"teacherfaq.title": "Scratch Teacher Account FAQ", "teacherfaq.title": "Scratch Teacher Account FAQ",
"teacherfaq.teacherWhatTitle": "What are teacher accounts?", "teacherfaq.teacherWhatTitle": "What are teacher accounts?",
"teacherfaq.teacherWhatBody": "A Scratch Teacher Account provides teachers and other educators with additional features to manage student participation on Scratch, including the ability to create student accounts, organize student projects into studios, and monitor student comments. Learn more about Teacher Accounts in <a href=\"https://www.youtube.com/watch?v=7Hl9GxA1zwQ\">this video</a>.", "teacherfaq.teacherWhatBody": "A Scratch Teacher Account provides teachers and other educators with additional features to manage student participation on Scratch, including the ability to create student accounts, organize student projects into studios, and monitor student comments. Learn more about Teacher Accounts in the video below:",
"teacherfaq.teacherQuestionsTitle": "What if I have any questions or comments on Teacher Accounts?", "teacherfaq.teacherSignUpTitle": "How do I request a teacher account?",
"teacherfaq.teacherQuestionsBody": "If you have any questions or feedback on Teacher Accounts, you can message us at <a href=\"mailto:teacher-accounts@scratch.mit.edu\">teacher-accounts@scratch.mit.edu</a>.", "teacherfaq.teacherSignUpBody": "To request a Teacher Account, go to the teacher account <a href=\"/educators/register\">request form</a>.",
"teacherfaq.teacherGoogleTitle": "Are teacher accounts integrated with Google Classroom or any other classroom managment service?",
"teacherfaq.teacherGoogleBody": "Scratch Teacher accounts are not integrated with any classroom management services.",
"teacherfaq.teacherEdTitle": "Are ScratchEd & Scratch Teacher accounts the same thing?",
"teacherfaq.teacherEdBody": "<a href=\"http://scratched.gse.harvard.edu/\">ScratchEd</a> accounts are not linked to Scratch Teacher accounts.",
"teacherfaq.teacherWaitTitle": "Why do I have to wait 24 hours for my account?", "teacherfaq.teacherWaitTitle": "Why do I have to wait 24 hours for my account?",
"teacherfaq.teacherWaitBody": "The Scratch Team uses this time to manually review account creation submissions to verify the account creator is an educator.", "teacherfaq.teacherWaitBody": "The Scratch Team uses this time to manually review account creation submissions to verify the account creator is an educator.",
"teacherfaq.teacherPersonalTitle": "Why do you need to know my personal information during registration?", "teacherfaq.teacherPersonalTitle": "Why do you need to know my personal information during registration?",
"teacherfaq.teacherPersonalBody": "We use this information to verify the account creator is an educator. We will not share this information with anyone else, and it will not be shared publicly on the site.", "teacherfaq.teacherPersonalBody": "We use this information to verify the account creator is an educator. We will not share this information with anyone else, and it will not be shared publicly on the site.",
"teacherfaq.teacherGoogleTitle": "Are teacher accounts integrated with Google Classroom or any other classroom managment service?",
"teacherfaq.teacherGoogleBody": "Scratch Teacher accounts are not integrated with any classroom management services.",
"teacherfaq.teacherEdTitle": "Are Scratch Teacher accounts linked to ScratchEd accounts?",
"teacherfaq.teacherEdBody": "No, Scratch Teacher accounts are not linked to <a href=\"http://scratched.gse.harvard.edu/\">ScratchEd</a> accounts.",
"teacherfaq.teacherMultipleTitle": "Can a class have multiple teachers?", "teacherfaq.teacherMultipleTitle": "Can a class have multiple teachers?",
"teacherfaq.teacherMultipleBody": "A class can only have one teacher account associated with it.", "teacherfaq.teacherMultipleBody": "A class can only have one teacher account associated with it.",
"teacherfaq.teacherQuestionsTitle": "What if I have any questions or comments on Teacher Accounts?",
"teacherfaq.teacherQuestionsBody": "If you have any questions or feedback on Teacher Accounts, you can message us at <a href=\"mailto:teacher-accounts@scratch.mit.edu\">teacher-accounts@scratch.mit.edu</a>.",
"teacherfaq.studentAccountsTitle": "Student Accounts", "teacherfaq.studentAccountsTitle": "Student Accounts",
"teacherfaq.studentVerifyTitle": "Do I have to verify each of my students' emails?", "teacherfaq.studentVerifyTitle": "Do I have to verify each of my students' emails?",
@ -21,7 +23,7 @@
"teacherfaq.studentEndTitle": "What happens when I \"end\" my class?", "teacherfaq.studentEndTitle": "What happens when I \"end\" my class?",
"teacherfaq.studentEndBody": "When you end a class, your class profile page will be hidden and your students will no longer be able to log in (but their projects and the class studios will still be visible on the site). You may re-open the class at any time. ", "teacherfaq.studentEndBody": "When you end a class, your class profile page will be hidden and your students will no longer be able to log in (but their projects and the class studios will still be visible on the site). You may re-open the class at any time. ",
"teacherfaq.studentForgetTitle": "What happens if a student forgets their password?", "teacherfaq.studentForgetTitle": "What happens if a student forgets their password?",
"teacherfaq.studentForgetBody": "You can manually reset a student password from within your Scratch Teacher Account. First, navigate to <a href=\"/educators/classes\">My Classes</a>. From there, find the correct Class and click on the Students link. You can then reset the password at the student level using the Settings menu. ", "teacherfaq.studentForgetBody": "You can manually reset a student password from within your Scratch Teacher Account. First, navigate to My Classes (either from the purple banner on the homepage or in the dropdown menu next to your user icon). From there, find the correct Class and click on the Students link. You can then reset the password at the student level using the Settings menu. ",
"teacherfaq.studentUnsharedTitle": "Can I see unshared student projects?", "teacherfaq.studentUnsharedTitle": "Can I see unshared student projects?",
"teacherfaq.studentUnsharedBody": "Teacher accounts can only access shared student projects.", "teacherfaq.studentUnsharedBody": "Teacher accounts can only access shared student projects.",
"teacherfaq.studentDeleteTitle": "Can I delete student accounts?", "teacherfaq.studentDeleteTitle": "Can I delete student accounts?",

View file

@ -8,7 +8,6 @@
"teacherlanding.generalUsageSettings": "<b>Settings:</b> schools, museums, libraries, community centers", "teacherlanding.generalUsageSettings": "<b>Settings:</b> schools, museums, libraries, community centers",
"teacherlanding.generalUsageGradeLevels": "<b>Grade Levels:</b> elementary, middle, and high school (and some colleges too!)", "teacherlanding.generalUsageGradeLevels": "<b>Grade Levels:</b> elementary, middle, and high school (and some colleges too!)",
"teacherlanding.generalUsageSubjectAreas": "<b>Subject Areas:</b> language arts, science, social studies, math, computer science, foreign languages, and the arts", "teacherlanding.generalUsageSubjectAreas": "<b>Subject Areas:</b> language arts, science, social studies, math, computer science, foreign languages, and the arts",
"teacherlanding.resourcesTitle": "Educator Resources",
"teacherlanding.scratchEdTitle": "A Community for Educators", "teacherlanding.scratchEdTitle": "A Community for Educators",
"teacherlanding.scratchEdDescription": "<a href=\"http://scratched.gse.harvard.edu/\">ScratchEd</a> is an online community where Scratch educators <a href=\"http://scratched.gse.harvard.edu/stories\">share stories</a>, exchange resources, ask questions, and find people. ScratchEd is developed and supported by the Harvard Graduate School of Education.", "teacherlanding.scratchEdDescription": "<a href=\"http://scratched.gse.harvard.edu/\">ScratchEd</a> is an online community where Scratch educators <a href=\"http://scratched.gse.harvard.edu/stories\">share stories</a>, exchange resources, ask questions, and find people. ScratchEd is developed and supported by the Harvard Graduate School of Education.",
"teacherlanding.meetupTitle": "In-Person Gatherings", "teacherlanding.meetupTitle": "In-Person Gatherings",

View file

@ -64,7 +64,7 @@ var Landing = injectIntl(React.createClass({
</section> </section>
<section id="resources"> <section id="resources">
<span className="nav-spacer"></span> <span className="nav-spacer"></span>
<h2><FormattedMessage id="teacherlanding.resourcesTitle" /></h2> <h2><FormattedMessage id="general.resourcesTitle" /></h2>
<FlexRow className="educator-community"> <FlexRow className="educator-community">
<div> <div>
<h3><FormattedMessage id="teacherlanding.scratchEdTitle" /></h3> <h3><FormattedMessage id="teacherlanding.scratchEdTitle" /></h3>
@ -82,19 +82,25 @@ var Landing = injectIntl(React.createClass({
<h3 id="guides-header"><FormattedMessage id="teacherlanding.guidesTitle" /></h3> <h3 id="guides-header"><FormattedMessage id="teacherlanding.guidesTitle" /></h3>
<FlexRow className="guides-and-tutorials"> <FlexRow className="guides-and-tutorials">
<div> <div>
<img src="/svgs/teachers/resources.svg" alt="resources icon" /> <a href="/help">
<img src="/svgs/teachers/resources.svg" alt="resources icon" />
</a>
<p> <p>
<FormattedHTMLMessage id="teacherlanding.helpPage" /> <FormattedHTMLMessage id="teacherlanding.helpPage" />
</p> </p>
</div> </div>
<div> <div>
<img src="/svgs/teachers/tips-window.svg" alt="tips window icon" /> <a href="/projects/editor/?tip_bar=home">
<img src="/svgs/teachers/tips-window.svg" alt="tips window icon" />
</a>
<p> <p>
<FormattedHTMLMessage id="teacherlanding.tipsWindow" /> <FormattedHTMLMessage id="teacherlanding.tipsWindow" />
</p> </p>
</div> </div>
<div> <div>
<img src="/svgs/teachers/creative-computing.svg" alt="creative computing icon" /> <a href="http://scratched.gse.harvard.edu/guide/">
<img src="/svgs/teachers/creative-computing.svg" alt="creative computing icon" />
</a>
<p> <p>
<FormattedHTMLMessage id="teacherlanding.creativeComputing" /> <FormattedHTMLMessage id="teacherlanding.creativeComputing" />
</p> </p>

View file

@ -85,7 +85,7 @@ var Terms = React.createClass({
(for example, in the event of a loss, theft, or unauthorized disclosure (for example, in the event of a loss, theft, or unauthorized disclosure
of your password), promptly change your password. If you cannot access of your password), promptly change your password. If you cannot access
your account to change your password, notify us at{' '} your account to change your password, notify us at{' '}
<a href="mailto:help@scratch.mit.edu"></a>. <a href="mailto:help@scratch.mit.edu">help@scratch.mit.edu</a>.
</p> </p>
</section> </section>
<section id="rules-of-usage"> <section id="rules-of-usage">

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>Icons</title><path d="M13.39,3.29V9.4a0.41,0.41,0,0,1-.14.31,4.18,4.18,0,0,1-5.51,0,3.42,3.42,0,0,0-2.23-.84,3.35,3.35,0,0,0-2.07.72v4.05a0.42,0.42,0,1,1-.84,0V3.29A0.41,0.41,0,0,1,2.87,2.9,4.17,4.17,0,0,1,8.27,3a3.39,3.39,0,0,0,4.45,0,0.39,0.39,0,0,1,.43-0.06A0.4,0.4,0,0,1,13.39,3.29Z" fill="#26d9bb" stroke="#22b296" stroke-linecap="round" stroke-linejoin="round"/></svg>

After

Width:  |  Height:  |  Size: 474 B

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>Icons</title><path d="M13.39,3.29V9.4a0.41,0.41,0,0,1-.14.31,4.18,4.18,0,0,1-5.51,0,3.42,3.42,0,0,0-2.23-.84,3.35,3.35,0,0,0-2.07.72v4.05a0.42,0.42,0,1,1-.84,0V3.29A0.41,0.41,0,0,1,2.87,2.9,4.17,4.17,0,0,1,8.27,3a3.39,3.39,0,0,0,4.45,0,0.39,0.39,0,0,1,.43-0.06A0.4,0.4,0,0,1,13.39,3.29Z" fill="none" stroke="#6b6b6b" stroke-linecap="round" stroke-linejoin="round"/></svg>

After

Width:  |  Height:  |  Size: 471 B

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>Icons</title><polyline points="6.88 12.46 6.88 12.46 13.58 12.46 13.58 8 13.58 8" fill="none" stroke="#22b296" stroke-linecap="round" stroke-linejoin="round"/><polyline points="4.65 10.23 4.65 10.23 11.35 10.23 11.35 5.77 11.35 5.77" fill="none" stroke="#22b296" stroke-linecap="round" stroke-linejoin="round"/><rect x="2.42" y="3.54" width="6.69" height="4.46" transform="translate(11.54 11.54) rotate(-180)" fill="#26d9bb" stroke="#22b296" stroke-linecap="round" stroke-linejoin="round"/></svg>

After

Width:  |  Height:  |  Size: 596 B

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>Icons</title><polyline points="4.65 10.23 4.65 10.23 11.35 10.23 11.35 5.77 11.35 5.77" fill="none" stroke="#6b6b6b" stroke-linecap="round" stroke-linejoin="round"/><rect x="2.42" y="3.54" width="6.69" height="4.46" transform="translate(11.54 11.54) rotate(-180)" fill="none" stroke="#6b6b6b" stroke-linecap="round" stroke-linejoin="round"/><polyline points="6.88 12.46 6.88 12.46 13.58 12.46 13.58 8 13.58 8" fill="none" stroke="#6b6b6b" stroke-linecap="round" stroke-linejoin="round"/></svg>

After

Width:  |  Height:  |  Size: 593 B