Merge branch 'develop' into hotfix/langauges_fix

This commit is contained in:
Colby Gutierrez-Kraybill 2017-11-30 15:04:49 -05:00 committed by GitHub
commit 805224dd8f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 843 additions and 185 deletions

View file

@ -42,23 +42,43 @@ async.auto({
// on any of those route conditions.
var notPassStatement = fastlyConfig.getAppRouteCondition('../build/*', routes, extraAppRoutes, __dirname);
// For all the routes in routes.json, construct a varnish-style regex that matches
// only if NONE of those routes are matched.
var passStatement = fastlyConfig.negateConditionStatement(notPassStatement);
// For a non-pass condition, point backend at s3
var backendCondition = fastlyConfig.setBackend(
'F_s3',
S3_BUCKET_NAME,
notPassStatement
);
// For a pass condition, set forwarding headers
var forwardCondition = fastlyConfig.setForwardHeaders(passStatement);
var recvCondition = '' +
'if (' + notPassStatement + ') {\n' +
' set req.backend = F_s3;\n' +
' set req.http.host = \"' + S3_BUCKET_NAME + '\";\n' +
'} else {\n' +
' if (!req.http.Fastly-FF) {\n' +
' if (req.http.X-Forwarded-For) {\n' +
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For \", \" client.ip;\n' +
' } else {\n' +
' set req.http.Fastly-Temp-XFF = client.ip;\n' +
' }\n' +
' } else {\n' +
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For;\n' +
' }\n' +
' set req.grace = 60s;\n' +
' if (req.http.Cookie:scratchlanguage) {\n' +
' set req.http.Accept-Language = req.http.Cookie:scratchlanguage;\n' +
' } else {\n' +
' set req.http.Accept-Language = accept.language_lookup("' +
Object.keys(languages).join(':') + '", ' +
'"en", ' +
'std.tolower(req.http.Accept-Language)' +
');\n' +
' }\n' +
' if (req.url ~ "^/projects/" && !req.http.Cookie:scratchsessionid) {\n' +
' set req.http.Cookie = req.http.Cookie:scratchlanguage;\n' +
' } else {\n' +
' return(pass);\n' +
' }\n' +
'}\n';
fastly.setCustomVCL(
results.version,
'recv-condition',
backendCondition + forwardCondition,
recvCondition,
cb
);
}],

View file

@ -84,45 +84,6 @@ var FastlyConfigMethods = {
return 'redirects/' + route.pattern;
},
/**
* Returns custom vcl configuration as a string for setting the backend
* of a request to the given backend/host.
*
* @param {string} backend name of the backend declared in fastly
* @param {string} host name of the s3 bucket to be set as the host
* @param {string} condition condition under which backend should be set
*/
setBackend: function (backend, host, condition) {
return '' +
'if (' + condition + ') {\n' +
' set req.backend = ' + backend + ';\n' +
' set req.http.host = \"' + host + '\";\n' +
'}\n';
},
/**
* Returns custom vcl configuration as a string for headers that
* should be added for the condition in which a request is forwarded.
*
* @param {string} condition condition under which to set pass headers
*/
setForwardHeaders: function (condition) {
return '' +
'if (' + condition + ') {\n' +
' if (!req.http.Fastly-FF) {\n' +
' if (req.http.X-Forwarded-For) {\n' +
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For \", \" client.ip;\n' +
' } else {\n' +
' set req.http.Fastly-Temp-XFF = client.ip;\n' +
' }\n' +
' } else {\n' +
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For;\n' +
' }\n' +
' set req.grace = 60s;\n' +
' return(pass);\n' +
'}\n';
},
/**
* Returns custom vcl configuration as a string that sets the varnish
* Time to Live (TTL) for responses that come from s3.
@ -132,9 +93,13 @@ var FastlyConfigMethods = {
setResponseTTL: function (condition) {
return '' +
'if (' + condition + ') {\n' +
' if (req.url ~ "^/projects/" && !req.http.Cookie:scratchsessionid) {\n' +
' set beresp.http.Vary = "Accept-Encoding, Accept-Language";\n' +
' } else {\n' +
' set beresp.ttl = 0s;\n' +
' set beresp.grace = 0s;\n' +
' return(pass);\n' +
' }\n' +
'}\n';
}
};

View file

@ -32,7 +32,8 @@ var Grid = React.createClass({
if (this.props.itemType == 'projects') {
return (
<Thumbnail key={key}
<Thumbnail
key={key}
showLoves={this.props.showLoves}
showFavorites={this.props.showFavorites}
showRemixes={this.props.showRemixes}
@ -42,23 +43,30 @@ var Grid = React.createClass({
href={href}
title={item.title}
src={item.image}
avatar={'https://cdn2.scratch.mit.edu/get_image/user/'
+ item.author.id + '_32x32.png'}
avatar={
'https://uploads.scratch.mit.edu/users/avatars/' +
item.author.id +
'.png'
}
creator={item.author.username}
loves={item.stats.loves}
favorites={item.stats.favorites}
remixes={item.stats.remixes}
views={item.stats.views} />
views={item.stats.views}
/>
);
}
else {
return (
<Thumbnail key={key}
<Thumbnail
key={key}
type={'gallery'}
href={href}
title={item.title}
src={item.image}
owner={item.owner} />
srcDefault={'https://uploads.scratch.mit.edu/galleries/thumbnails/default.png'}
owner={item.owner}
/>
);
}
}.bind(this))}

View file

@ -11,7 +11,7 @@ var Navigation = React.createClass({
<NavigationBox>
<ul className="ul mod-2018">
<li className="li-left mod-logo mod-2018">
<a href="/conference" className="logo-a">
<a href="/" className="logo-a">
<img
src="/images/logo_sm.png"
alt="Scratch Logo"

View file

@ -8,12 +8,20 @@ var Thumbnail = React.createClass({
propTypes: {
src: React.PropTypes.string
},
getInitialState: function () {
return {
srcFallback: false,
avatarFallback: false
};
},
getDefaultProps: function () {
return {
href: '#',
title: 'Project',
src: '',
srcDefault: 'https://uploads.scratch.mit.edu/projects/thumbnails/default.png',
avatar: '',
avatarDefault: 'https://uploads.scratch.mit.edu/users/avatars/default.png',
type: 'project',
showLoves: false,
showFavorites: false,
@ -24,6 +32,12 @@ var Thumbnail = React.createClass({
alt: ''
};
},
handleSrcError: function () {
this.setState({srcFallback: true});
},
handleAvatarError: function () {
this.setState({avatarFallback: true});
},
render: function () {
var classes = classNames(
'thumbnail',
@ -58,7 +72,8 @@ var Thumbnail = React.createClass({
<div
key="remixes"
className="thumbnail-remixes"
title={this.props.remixes + ' remixes'}>
title={this.props.remixes + ' remixes'}
>
{this.props.remixes}
</div>
);
@ -68,19 +83,48 @@ var Thumbnail = React.createClass({
<div
key="views"
className="thumbnail-views"
title={this.props.views + ' views'}>
title={this.props.views + ' views'}
>
{this.props.views}
</div>
);
}
var imgElement, titleElement, avatarElement;
if (this.props.linkTitle) {
imgElement = <a className="thumbnail-image" href={this.props.href} key="imgElement">
<img src={this.props.src} alt={this.props.alt} />
</a>;
titleElement = <a href={this.props.href} key="titleElement">
if (this.state.srcFallback) {
imgElement = (
<a
className="thumbnail-image"
href={this.props.href}
key="imgElement"
>
<img
alt={this.props.alt}
src={this.props.srcDefault}
/>
</a>
);
} else {
imgElement = (
<a
className="thumbnail-image"
href={this.props.href}
key="imgElement"
>
<img
alt={this.props.alt}
src={this.props.src}
onError={this.handleSrcError}
/>
</a>
);
}
titleElement = (
<a href={this.props.href} key="titleElement">
{this.props.title}
</a>;
</a>
);
} else {
imgElement = <img src={this.props.src} />;
titleElement = this.props.title;
@ -97,10 +141,32 @@ var Thumbnail = React.createClass({
}
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>;
if (this.state.avatarFallback) {
avatarElement = (
<a
className="creator-image"
href={'/users/' + this.props.creator + '/'}
>
<img
alt={this.props.creator}
src={this.props.avatarDefault}
/>
</a>
);
} else {
avatarElement = (
<a
className="creator-image"
href={'/users/' + this.props.creator + '/'}
>
<img
alt={this.props.creator}
src={this.props.avatar}
onError={this.handleAvatarError}
/>
</a>
);
}
}
return (
<div className={classes} >

View file

@ -47,7 +47,7 @@ var TTTTile = React.createClass({
{this.props.onGuideClick && (
<div className="ttt-tile-guides" onClick={this.props.onGuideClick}>
<FormattedMessage id='tile.guides' defaultMessage='See Cards and Guides'/>
<img className="ttt-tile-see-more" src="/svgs/ttt/see-more.svg" />
<img className="ttt-tile-open-modal" src="/svgs/modal/open-blue.svg" />
</div>
)}
</div>

View file

@ -105,14 +105,15 @@
font-size: .75rem;
font-weight: 500;
&:hover {
background-color: lighten($link-blue, 40%);
}
}
.ttt-tile-see-more {
.ttt-tile-open-modal {
display: inline-block;
padding: 0 .25rem;
vertical-align: middle;
width: 1rem;
height: 1rem;
vertical-align: text-bottom;
}

View file

@ -64,7 +64,7 @@ var Developers = React.createClass({
<div className="inner">
<section id="projects">
<span className="nav-spacer"></span>
<h2>Projects</h2>
<h2><FormattedMessage id='developers.projectsTitle' /></h2>
<p className="intro">
<FormattedMessage id='developers.projectsIntro' />
</p>
@ -94,13 +94,7 @@ var Developers = React.createClass({
<div className="body-copy column">
<h3>ScratchJr</h3>
<p>
ScratchJr is an introductory programming language{' '}
that enables young children (ages 5-7) to create{' '}
their own interactive stories and games. For more{' '}
information, visit the{' '}
<a href="https://www.scratchjr.org/">ScratchJr website</a>{' '}
or access the code and documentation{' '}
<a href="https://github.com/LLK/scratchjr">here</a>.
<FormattedHTMLMessage id='developers.jrBody' />
</p>
</div>
</FlexRow>

View file

@ -14,6 +14,7 @@
"developers.wwwTitle": "Scratch WWW",
"developers.wwwIntro": "Scratch-www is a standalone web client for the Scratch Community, built using React and Redux. Access the code and documentation through Github <a href=\"https://github.com/LLK/scratch-www\">here</a>.",
"developers.principlesIntro": "We created Scratch to empower young people to think creatively, reason systematically, and work collaboratively. We are guided by a set of <b>Learning Principles</b> and <b>Design Principles</b> that we hope you will follow as you develop new tools and technologies with Scratch Blocks.",
"developers.jrBody": "ScratchJr is an introductory programming language that enables young children (ages 5-7) to create their own interactive stories and games. For more information, visit the <a href=\"https://www.scratchjr.org/\">ScratchJr website</a> or access the code and documentation <a href=\"https://github.com/LLK/scratchjr\">on GitHub</a>.",
"developers.learningPrinciplesTitle": "Learning Principles",
"developers.learningPrinciplesProjectsBody": "People learn best when they are actively working on projects — generating new ideas, designing prototypes, making improvements and creating final products.",
"developers.learningPrinciplesPassionTitle": "Passion",
@ -37,7 +38,7 @@
"developers.donateThanks": "Thanks for supporting Scratch!",
"developers.partnersIntro": "The creation and maintenance of this open source code would not be possible without generous technical and financial support from our partners:",
"developers.faqAboutTitle": "Where can I learn more about Scratch?",
"developers.faqAboutBody": "Scratch is a free programming language and online community where young people can create their own interactive stories, games, and animations. Scratch is a project of the <a href=\"https://llk.media.mit.edu/\">Lifelong Kindergarten</a> Group at the <a href=\"http://media.mit.edu/\">MIT Media Lab</a>. You can learn more about Scratch <a href=\"/about\">here</a>.",
"developers.faqAboutBody": "Scratch is a free programming language and online community where young people can create their own interactive stories, games, and animations. Scratch is a project of the <a href=\"https://www.media.mit.edu/groups/lifelong-kindergarten/overview\">Lifelong Kindergarten</a> Group at the <a href=\"http://media.mit.edu/\">MIT Media Lab</a>. You can learn more about Scratch <a href=\"/about\">here</a>.",
"developers.faqRulesTitle": "Are there rules to using this code in my application?",
"developers.faqRulesBody": "You may use this code in accordance with the license which governs each project. We also strongly encourage you to consider the learning and design principles (above, on this page) when building creative learning experiences for kids of all ages.",
"developers.faqNameTitle": "Am I allowed to use the name \"Scratch Blocks\" in the description of my app and other public messaging?",

View file

@ -231,6 +231,7 @@ var Download = injectIntl(React.createClass({
<p><FormattedMessage id='download.knownIssuesOne' /></p>
<p><FormattedMessage id='download.knownIssuesTwo' /></p>
<p><FormattedHTMLMessage id='download.knownIssuesThree' /></p>
<p><FormattedMessage id='download.knownIssuesFour' /></p>
<a href="https://scratch.mit.edu/discuss/3/" className='button mod-link'>
<FormattedMessage id='download.reportBugs' />
</a>

View file

@ -1,7 +1,7 @@
{
"download.title": "Scratch 2.0 Offline Editor",
"download.intro": "You can install the Scratch 2.0 editor to work on projects without an internet connection. This version will work on Mac, Windows, and some versions of Linux (32 bit).",
"download.introMac": "<b>Note for Mac Users:</b> the latest version of Scratch 2.0 Offline requires Adobe Air 20. To upgrade to Adobe Air 20 manually, go <a href=\"https://get.adobe.com/air/\">here</a>.",
"download.introMac": "<b>Note for Mac Users:</b> the latest version of Scratch 2.0 Offline requires Adobe AIR 20. To upgrade to Adobe AIR 20 manually, go <a href=\"https://get.adobe.com/air/\">here</a>.",
"download.installation": "Installation",
"download.airTitle": "Adobe AIR",
"download.airBody": "If you don't already have it, download and install the latest <a href=\"http://get.adobe.com/air/\">Adobe AIR</a>",
@ -24,9 +24,10 @@
"download.otherVersionsOlder": "If you have an older computer, or cannot install the Scratch 2.0 offline editor, you can try installing <a href=\"http://scratch.mit.edu/scratch_1.4/\">Scratch 1.4</a>.",
"download.otherVersionsAdmin": "If you are a network administrator: a Scratch 2.0 MSI has been created and maintained by a member of the community and hosted for public download <a href=\"http://llk.github.io/scratch-msi/\">here</a>.",
"download.knownIssuesTitle": "Known issues",
"download.knownIssuesOne": "If your offline editor is crashing directly after Scratch is opened, install the Scratch 2 offline editor again (see step 2 above). This issue is due to a bug introduced in Adobe Air version 14 (released April 2014).",
"download.knownIssuesOne": "If your offline editor is crashing directly after Scratch is opened, install the Scratch 2 offline editor again (see step 2 above). This issue is due to a bug introduced in Adobe AIR version 14 (released April 2014).",
"download.knownIssuesTwo": "Graphic effects blocks (in \"Looks\") may slow down projects due to a known Flash bug.",
"download.knownIssuesThree": "The <b>backpack</b> is not yet available.",
"download.knownIssuesFour": "On Mac OS you may see a prompt indicating that \"Scratch 2 is trying to install a new helper tool\" and asking for your user name and password. We are currently investigating a solution to this problem.",
"download.reportBugs": "Report Bugs and Glitches",
"download.notAvailable": "Hmm, editor downloads are not available right now - please refresh the page to try again."
}

View file

@ -31,8 +31,8 @@ var Jobs = React.createClass({
<h3><FormattedMessage id='jobs.openings' /></h3>
<ul>
<li>
<a href="https://www.media.mit.edu/about/job-opportunities/junior-web-designer-scratch/">
Junior Designer
<a href="https://www.media.mit.edu/about/job-opportunities/web-designer-scratch/">
Designer
</a>
<span>
MIT Media Lab, Cambridge, MA

View file

@ -0,0 +1,115 @@
var FormattedMessage = require('react-intl').FormattedMessage;
var injectIntl = require('react-intl').injectIntl;
var MediaQuery = require('react-responsive');
var React = require('react');
var FlexRow = require('../../../components/flex-row/flex-row.jsx');
var TitleBanner = require('../../../components/title-banner/title-banner.jsx');
var TTTModal = require('../../../components/modal/ttt/modal.jsx');
var TTTTile = require('../../../components/ttt-tile/ttt-tile.jsx');
var frameless = require('../../../lib/frameless');
var tiles = require('../../tips/ttt');
require('../../../components/forms/button.scss');
require('./middle-banner.scss');
var MiddleBanner = injectIntl(React.createClass({
getInitialState: function () {
return {
currentTile: tiles[1],
TTTModalOpen: false
};
},
showTTTModal: function (tile) {
return this.setState({
currentTile: tile,
TTTModalOpen: true
});
},
hideTTTModal: function () {
return this.setState({TTTModalOpen: false});
},
renderTTTTiles: function () {
var formatMessage = this.props.intl.formatMessage;
var tileObjects = {
flyTile: {
title: formatMessage({id: tiles[1].title}),
description: formatMessage({id: tiles[1].description}),
tutorialLoc: tiles[1].tutorialLoc,
activityLoc: formatMessage({id: tiles[1].activityLoc}),
guideLoc: formatMessage({id: tiles[1].guideLoc}),
thumbUrl: tiles[1].thumbUrl,
bannerUrl: tiles[1].bannerUrl
},
musicTile: {
title: formatMessage({id: tiles[2].title}),
description: formatMessage({id: tiles[2].description}),
tutorialLoc: tiles[2].tutorialLoc,
activityLoc: formatMessage({id: tiles[2].activityLoc}),
guideLoc: formatMessage({id: tiles[2].guideLoc}),
thumbUrl: tiles[2].thumbUrl,
bannerUrl: tiles[2].bannerUrl
},
pongTile: {
title: formatMessage({id: tiles[7].title}),
description: formatMessage({id: tiles[7].description}),
tutorialLoc: tiles[7].tutorialLoc,
activityLoc: formatMessage({id: tiles[7].activityLoc}),
guideLoc: formatMessage({id: tiles[7].guideLoc}),
thumbUrl: tiles[7].thumbUrl,
bannerUrl: tiles[7].bannerUrl
}
};
return [
<TTTTile
key={1}
className="mod-banner"
onGuideClick={this.showTTTModal.bind(this, tileObjects.flyTile)}
{...tileObjects.flyTile}
/>,
<TTTTile
key={2}
className="mod-banner"
onGuideClick={this.showTTTModal.bind(this, tileObjects.musicTile)}
{...tileObjects.musicTile}
/>,
<TTTTile
key={7}
className="mod-banner mod-last-tile"
onGuideClick={this.showTTTModal.bind(this, tileObjects.pongTile)}
{...tileObjects.pongTile}
/>
];
},
render: function () {
return (
<TitleBanner className="mod-splash-middle">
<div className="middle-banner inner">
<FlexRow className="middle-banner-header">
<h1 className="middle-banner-header-h1">
<FormattedMessage id="middleBanner.header" />
</h1>
<a href="/tips" className="button mod-ttt-try-button">
<FormattedMessage id="middleBanner.ttt" />
</a>
</FlexRow>
<MediaQuery minWidth={frameless.tablet}>
<FlexRow className="middle-banner-tiles">
{this.renderTTTTiles()}
</FlexRow>
<TTTModal
isOpen={this.state.TTTModalOpen}
onRequestClose={this.hideTTTModal}
{...this.state.currentTile}
/>
</MediaQuery>
</div>
</TitleBanner>
);
}
}));
module.exports = MiddleBanner;

View file

@ -0,0 +1,49 @@
@import "../../../colors";
@import "../../../frameless";
.title-banner.mod-splash-middle {
background: url("/images/blocks-pattern.png");
background-color: $ui-purple;
background-repeat: repeat;
background-size: 180px 180px;
}
.middle-banner-header {
margin-bottom: 1rem;
justify-content: space-between;
align-items: center;
}
.middle-banner-header-h1 {
color: $type-white;
}
.mod-ttt-try-button {
&:link,
&:visited,
&:active
&:hover {
color: $type-white;
}
}
.ttt-tile.mod-banner {
background-color: $background-color;
}
@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) {
.mod-last-tile {
display: none;
}
.middle-banner-header {
flex-direction: column;
align-items: center;
}
}
@media only screen and (max-width: $tablet - 1) {
.title-banner.mod-splash-middle {
display: none;
}
}

View file

@ -0,0 +1,115 @@
var FormattedMessage = require('react-intl').FormattedMessage;
var injectIntl = require('react-intl').injectIntl;
var React = require('react');
var FlexRow = require('../../../components/flex-row/flex-row.jsx');
var TitleBanner = require('../../../components/title-banner/title-banner.jsx');
var TTTModal = require('../../../components/modal/ttt/modal.jsx');
require('../../../components/forms/button.scss');
require('./top-banner.scss');
var nameTile = {
title: 'ttt.AnimateYourNameTitle',
description: 'ttt.AnimateYourNameDescription',
thumbUrl: '/images/ttt/animate-your-name.jpg',
bannerUrl: '/images/ttt/animate-your-name-banner.jpg',
tutorialLoc: '/projects/editor/?tip_bar=name',
activityLoc: 'cards.nameCardsLink',
guideLoc: 'guides.NameGuideLink'
};
var TopBanner = injectIntl(React.createClass({
type: 'TopBanner',
propTypes: {
loggedIn: React.PropTypes.bool.isRequired
},
getInitialState: function () {
// use translated tile
var formatMessage = this.props.intl.formatMessage;
var translatedTile = {};
translatedTile = {
title: formatMessage({id: nameTile.title}),
description: formatMessage({id: nameTile.description}),
tutorialLoc: nameTile.tutorialLoc,
activityLoc: formatMessage({id: nameTile.activityLoc}),
guideLoc: formatMessage({id: nameTile.guideLoc}),
thumbUrl: nameTile.thumbUrl,
bannerUrl: nameTile.bannerUrl
};
return {currentTile: translatedTile, TTTModalOpen: false};
},
showTTTModal: function () {
this.setState({TTTModalOpen: true});
},
hideTTTModal: function () {
this.setState({TTTModalOpen: false});
},
render: function () {
return (
<TitleBanner className="mod-splash-top">
<FlexRow className="banner-top inner">
<a href="/projects/editor/?tip_bar=name">
<FlexRow className="top-animation">
<img
src="/images/hoc/s.png"
alt=""
className="top-animation-letter mod-letter-s"
/>
<img
src="/images/hoc/c1.png"
alt=""
className="top-animation-letter mod-letter-c1"
/>
<img
src="/images/hoc/r.png"
alt=""
className="top-animation-letter mod-letter-r"
/>
<img
src="/images/hoc/a.png"
alt=""
className="top-animation-letter mod-letter-a"
/>
<img
src="/images/hoc/t.png"
alt=""
className="top-animation-letter mod-letter-t"
/>
<img
src="/images/hoc/c2.png"
alt=""
className="top-animation-letter mod-letter-c2"
/>
<img
src="/images/hoc/h.png"
alt=""
className="top-animation-letter mod-letter-h"
/>
</FlexRow>
</a>
<div className="top-links">
<a href="/projects/editor/?tip_bar=name" className="button mod-top-button">
{ this.props.loggedIn ?
<FormattedMessage id="ttt.AnimateYourNameTitle" /> :
<FormattedMessage id="topBanner.getStarted" />
}
</a>
<div className="mod-guides-link" onClick={this.showTTTModal}>
&nbsp;&nbsp;
<FormattedMessage id="tile.guides" />
<img className="top-open-modal" src="/svgs/modal/open-white.svg" />
<TTTModal
isOpen={this.state.TTTModalOpen}
onRequestClose={this.hideTTTModal}
{...this.state.currentTile}/>
</div>
</div>
</FlexRow>
</TitleBanner>
);
}
}));
module.exports = TopBanner;

View file

@ -0,0 +1,228 @@
@import "../../../colors";
@import "../../../frameless";
.title-banner.mod-splash-top {
background-color: $ui-aqua;
background-image: url("/images/hoc/splash-left.png"), url("/images/hoc/splash-right.png");
background-repeat: no-repeat, no-repeat;
background-position: left bottom, right bottom;
background-size: 40% auto, 40% auto;
}
.banner-top {
background-image: url("/images/hoc/doodads.png");
background-repeat: no-repeat;
background-position: top;
background-size: 70%;
flex-direction: column;
}
.top-banner-header {
justify-content: space-between;
}
.top-banner-header-h1 {
margin-bottom: 1.25rem;
color: $type-white;
}
.banner-image.mod-top {
width: 100%;
}
.top-animation {
margin: auto;
padding-top: 2rem;
padding-bottom: 1rem;
width: 70%;
}
.top-animation-letter {
animation-duration: 1s;
animation-iteration-count: infinite;
animation-fill-mode: both;
width: 100%;
}
@keyframes jump {
from,
to {
transform: translate3d(0, 0, 0);
}
12.5%,
62.5% {
transform: translate3d(0, 10px, 0);
}
37.5%,
87.5% {
transform: translate3d(0, -10px, 0);
}
}
.mod-letter-s {
// width: 16.6%;
animation-name: jump;
width: 13.3%;
}
@keyframes pulse {
from {
transform: scale3d(1, 1, 1);
}
50% {
transform: scale3d(1.25, 1.25, 1.25);
}
to {
transform: scale3d(1, 1, 1);
}
}
.mod-letter-c1 {
// width: 12.5%;
animation-name: pulse;
width: 10%;
}
@keyframes spin-left {
90% {
transform: rotate3d(0, 0, 1, -360deg);
}
to {
transform: rotate3d(0, 0, 1, -360deg);
}
}
.mod-letter-r {
// width: 14%;
animation-name: spin-left;
width: 11.2%;
}
@keyframes swing {
25% {
transform: rotate3d(0, 0, 1, 40deg);
}
75% {
transform: rotate3d(0, 0, 1, -40deg);
}
from,
to {
transform: rotate3d(0, 0, 1, 0deg);
}
}
.mod-letter-a {
// width: 14.4%;
animation-name: swing;
width: 11.5%;
}
@keyframes shake {
from,
to {
transform: translate3d(0, 0, 0);
}
12.5%,
62.5% {
transform: translate3d(-10px, 0, 0);
}
37.5%,
87.5% {
transform: translate3d(10px, 0, 0);
}
}
.mod-letter-t {
// width: 15.5%;
animation-name: shake;
width: 12.4%;
}
@keyframes spin-right {
90% {
transform: rotate3d(0, 0, 1, 360deg);
}
to {
transform: rotate3d(0, 0, 1, 360deg);
}
}
.mod-letter-c2 {
// width: 12.5%;
animation-name: spin-right;
width: 10%;
}
@keyframes inverse-jump {
from,
to {
transform: translate3d(0, 0, 0);
}
12.5%,
62.5% {
transform: translate3d(0, -10px, 0);
}
37.5%,
87.5% {
transform: translate3d(0, 10px, 0);
}
}
.mod-letter-h {
// width: 14.4%;
animation-name: inverse-jump;
width: 11.5%;
}
.mod-top-button {
&:active &:hover,
&:link,
&:visited {
color: $type-white;
font-size: 1rem;
}
}
.mod-top-button {
border: 1px solid $active-gray;
box-shadow: none;
background-color: $ui-blue;
}
.top-links {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
}
.mod-guides-link {
cursor: pointer;
padding: 1.25rem 0;
color: $ui-white;
font-size: 1rem;
font-weight: 500;
}
.top-open-modal {
display: inline-block;
padding: 0 .25rem;
vertical-align: top;
}
@media only screen and (max-width: $tablet - 1) {
.flex-row.top-animation {
flex-direction: row;
}
}

View file

@ -0,0 +1,10 @@
{
"cards.nameCardsLink": "https://resources.scratch.mit.edu/www/cards/en/nameCards.pdf",
"cards.flyCardsLink": "https://resources.scratch.mit.edu/www/cards/en/flyCards.pdf",
"cards.musicCardsLink": "https://resources.scratch.mit.edu/www/cards/en/musicCards.pdf",
"cards.pongCardsLink": "https://resources.scratch.mit.edu/www/cards/en/pongCards.pdf",
"guides.NameGuideLink": "https://resources.scratch.mit.edu/www/guides/en/NameGuide.pdf",
"guides.FlyGuideLink": "https://resources.scratch.mit.edu/www/guides/en/FlyGuide.pdf",
"guides.MusicGuideLink": "https://resources.scratch.mit.edu/www/guides/en/MusicGuide.pdf",
"guides.PongGuideLink": "https://resources.scratch.mit.edu/www/guides/en/PongGuide.pdf"
}

View file

@ -29,6 +29,28 @@
"teacherbanner.classesButton": "My Classes",
"teacherbanner.faqButton": "Teacher Account FAQ",
"middleBanner.header": "Get Creative with Coding",
"middleBanner.ttt": "See more activities",
"topBanner.getStarted": "Get Started with Coding!",
"ttt.tutorial": "Tutorial",
"ttt.open": "Open",
"ttt.tutorialSubtitle": "Find out how to make this project using a step-by-step tutorial in Scratch.",
"ttt.activityTitle": "Activity Cards",
"ttt.activitySubtitle": "Explore new coding ideas using this set of illustrated cards you can print out.",
"ttt.educatorTitle": "Educator Guide",
"ttt.educatorSubtitle": "Use this educator guide to plan and lead a one-hour Scratch workshop.",
"tile.tryIt": "Try It",
"tile.guides": "See Cards and Guides",
"ttt.download": "Download",
"ttt.AnimateYourNameTitle": "Animate a Name",
"ttt.AnimateYourNameDescription": "Animate the letters of your username, initials, or favorite word.",
"ttt.MakeItFlyTitle": "Make It Fly",
"ttt.MakeItFlyDescription": "Animate the Scratch Cat, The Powerpuff Girls, or even a taco!",
"ttt.MakeMusicTitle": "Make Music",
"ttt.MakeMusicDescription": "Choose instruments, add sounds, and press keys to play music.",
"ttt.PongTitle": "Pong Game",
"ttt.PongDescription": "Make a bouncing ball game with sounds, points, and other effects.",
"welcome.welcomeToScratch": "Welcome to Scratch!",
"welcome.learn": "Learn how to make a project in Scratch",
"welcome.tryOut": "Try out starter projects",

View file

@ -11,6 +11,8 @@ var Box = require('../../components/box/box.jsx');
var Button = require('../../components/forms/button.jsx');
var Carousel = require('../../components/carousel/carousel.jsx');
var LegacyCarousel = require('../../components/carousel/legacy-carousel.jsx');
var TopBanner = require('./hoc/top-banner.jsx');
var MiddleBanner = require('./hoc/middle-banner.jsx');
var Intro = require('../../components/intro/intro.jsx');
var IframeModal = require('../../components/modal/iframe/modal.jsx');
var News = require('../../components/news/news.jsx');
@ -253,6 +255,8 @@ var SplashPresentation = injectIntl(React.createClass({
{this.props.isEducator ? [
<TeacherBanner key="teacherbanner" messages={messages} />
] : []}
<TopBanner loggedIn={this.props.sessionStatus === sessionActions.Status.FETCHED
&& Object.keys(this.props.user).length !== 0}/>
<div key="inner" className="inner mod-splash">
{this.props.sessionStatus === sessionActions.Status.FETCHED ? (
Object.keys(this.props.user).length !== 0 ? [
@ -273,6 +277,12 @@ var SplashPresentation = injectIntl(React.createClass({
]) : []
}
{featured.shift()}
{featured.shift()}
</div>
<MiddleBanner />
<div key="inner2" className="inner mod-splash">
{featured}
{this.props.isAdmin ? [

BIN
static/images/hoc/a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
static/images/hoc/c1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
static/images/hoc/c2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
static/images/hoc/h.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
static/images/hoc/r.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
static/images/hoc/s.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

BIN
static/images/hoc/t.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
<title>open-blue</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="open-blue" fill="#4C97FF">
<path d="M16.904265,9.16353035 L15.4298099,7.69520276 L10.2383662,11.6050888 C9.64794591,12.057779 8.79050666,11.9379804 8.33625534,11.3315664 C7.9692373,10.844951 7.97668404,10.1706865 8.33625534,9.70951497 L12.2606889,4.53591307 L10.8213398,3.10151073 C10.4107052,2.6922873 10.7011281,2.00848131 11.2670806,2.00848131 L17.3585163,2 C17.709577,2.00848131 18,2.29896634 18,2.64033925 L18,8.71932149 C18,9.28332892 17.3063891,9.56427246 16.904265,9.16353035 Z M15.3807332,18 L3.03722491,18 C2.46488952,18 2,17.5367082 2,16.9652796 L2,4.66419295 C2,4.09382454 2.46488952,3.62947257 3.03722491,3.62947257 L7.82335296,3.62947257 C8.39781598,3.62947257 8.86164168,4.09382454 8.86164168,4.66419295 C8.86164168,5.23562152 8.39781598,5.69891333 7.82335296,5.69891333 L4.07657745,5.69891333 L4.07657745,15.9305592 L14.3424445,15.9305592 L14.3424445,13.2101776 C14.3424445,12.638749 14.807334,12.1754572 15.3807332,12.1754572 C15.9541324,12.1754572 16.4200857,12.638749 16.4200857,13.2101776 L16.4200857,16.9652796 C16.4200857,17.5367082 15.9541324,18 15.3807332,18 Z" id="open-white"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
<title>open-modal-icon</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="open-modal-icon" fill="#FFFFFF">
<path d="M16.904265,9.16353035 L15.4298099,7.69520276 L10.2383662,11.6050888 C9.64794591,12.057779 8.79050666,11.9379804 8.33625534,11.3315664 C7.9692373,10.844951 7.97668404,10.1706865 8.33625534,9.70951497 L12.2606889,4.53591307 L10.8213398,3.10151073 C10.4107052,2.6922873 10.7011281,2.00848131 11.2670806,2.00848131 L17.3585163,2 C17.709577,2.00848131 18,2.29896634 18,2.64033925 L18,8.71932149 C18,9.28332892 17.3063891,9.56427246 16.904265,9.16353035 Z M15.3807332,18 L3.03722491,18 C2.46488952,18 2,17.5367082 2,16.9652796 L2,4.66419295 C2,4.09382454 2.46488952,3.62947257 3.03722491,3.62947257 L7.82335296,3.62947257 C8.39781598,3.62947257 8.86164168,4.09382454 8.86164168,4.66419295 C8.86164168,5.23562152 8.39781598,5.69891333 7.82335296,5.69891333 L4.07657745,5.69891333 L4.07657745,15.9305592 L14.3424445,15.9305592 L14.3424445,13.2101776 C14.3424445,12.638749 14.807334,12.1754572 15.3807332,12.1754572 C15.9541324,12.1754572 16.4200857,12.638749 16.4200857,13.2101776 L16.4200857,16.9652796 C16.4200857,17.5367082 15.9541324,18 15.3807332,18 Z"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="16px" height="16px" viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{opacity:0.2;fill:#4C97FF;stroke:#4C97FF;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st1{fill:none;stroke:#4C97FF;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st2{fill:#4C97FF;}
</style>
<path class="st0" d="M13,11H6c-0.6,0-1-0.4-1-1V3c0-0.6,0.4-1,1-1h7c0.6,0,1,0.4,1,1v7C14,10.6,13.6,11,13,11z"/>
<path class="st1" d="M9,13c0,0.6-0.4,1-1,1H3c-0.6,0-1-0.4-1-1V8c0-0.6,0.4-1,1-1"/>
<path class="st1" d="M13,11H6c-0.6,0-1-0.4-1-1V3c0-0.6,0.4-1,1-1h7c0.6,0,1,0.4,1,1v7C14,10.6,13.6,11,13,11z"/>
<g>
<path class="st2" d="M12,4.2v2.4c0,0.2-0.3,0.3-0.4,0.2L10.8,6l-3,2.7c-0.1,0.1-0.3,0.1-0.4,0c-0.1-0.1-0.1-0.3,0-0.4L10,5.2
L9.2,4.4C9,4.3,9.1,4,9.4,4h2.4C11.9,4,12,4.1,12,4.2z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -18,6 +18,10 @@ const clickText = (text) => {
return clickXpath(`//*[contains(text(), '${text}')]`);
};
const findText = (text) => {
return driver.wait(until.elementLocated(By.xpath(`//*[contains(text(), '${text}')]`), 5 * 1000));
};
const clickButton = (text) => {
return clickXpath(`//button[contains(text(), '${text}')]`);
};
@ -57,6 +61,7 @@ module.exports = {
clickXpath,
findByXpath,
clickText,
findText,
clickButton,
findByCss,
getLogs

View file

@ -1,6 +1,6 @@
{
"testDependencies": {
"selenium-webdriver": "2.44.0",
"chromedriver": "2.27.0"
"devDependencies": {
"selenium-webdriver": "2.45.0",
"chromedriver": "2.33.0"
}
}

View file

@ -4,20 +4,20 @@
* Test cases: https://github.com/LLK/scratch-www/wiki/Most-Important-Workflows
*/
require('chromedriver');
var tap = require('tap');
var seleniumWebdriver = require('selenium-webdriver');
const {
driver,
webdriver
} = require('../../helpers/selenium-helpers.js');
// Selenium's promise driver will be deprecated, so we should not rely on it
seleniumWebdriver.SELENIUM_PROMISE_MANAGER=0;
//chrome driver
var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome()).build();
webdriver.SELENIUM_PROMISE_MANAGER=0;
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
//timeout for each test; timeout for suite set at command line level
var options = { timeout: 20000 };
var options = { timeout: 30000 };
//number of tests in the plan
tap.plan(25);
@ -33,20 +33,16 @@ tap.beforeEach(function () {
});
// Function clicks the link and returns the url of the resulting page
function clickFooterLinks (linkText) {
// Not sure if I need this first wait - maybe it solved intermittent initial failure problem?
return driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.id('view')))
.then( function () {
return driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By
.id('footer')))
.then( function () {
return driver.findElement(seleniumWebdriver.By.linkText(linkText)); })
return driver.wait(webdriver.until.elementLocated(webdriver.By.id('footer')))
.then( function (element) {
return element.findElement(webdriver.By.linkText(linkText)); })
.then( function (element) {
return element.click(); })
.then(function () {
return driver.getCurrentUrl();
});
});
}
// ==== ABOUT SCRATCH column ====

View file

@ -0,0 +1,59 @@
/*
* Tests from:
*
* https://github.com/LLK/scratchr2/wiki/Smoke-Testing-Test-Cases
*
*/
const {
clickText,
findByXpath,
findText,
clickXpath,
clickButton,
driver
} = require('../../helpers/selenium-helpers.js');
var username = process.env.SMOKE_USERNAME;
var password = process.env.SMOKE_PASSWORD;
var tap = require('tap');
const test = tap.test;
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
var url = rootUrl + '/discuss';
tap.plan(2);
tap.tearDown(function () {
driver.quit();
});
tap.beforeEach(function () {
return driver.get(url);
});
test('Sign in to Scratch using scratchr2 navbar', t => {
clickText('Sign in')
.then(() => findByXpath('//input[@id="login_dropdown_username"]'))
.then((element) => element.sendKeys(username))
.then(() => findByXpath('//input[@name="password"]'))
.then((element) => element.sendKeys(password))
.then(() => clickButton('Sign in'))
.then(() => findByXpath('//li[contains(@class, "logged-in-user")'
+ 'and contains(@class, "dropdown")]/span'))
.then((element) => element.getText('span'))
.then((text) => t.match(text.toLowerCase(), username.substring(0,10).toLowerCase(),
'first part of username should be displayed in navbar'))
.then(() => t.end());
});
test('Sign out of Scratch using scratchr2 navbar', t => {
clickXpath('//span[contains(@class, "user-name")'
+ ' and contains(@class, "dropdown-toggle")]/img[@class="user-icon"]')
.then(() => clickXpath('//input[@value="Sign out"]'))
.then(() => findText('Sign in'))
.then((element) => t.ok(element, 'Sign in reappeared on the page after signing out'))
.then(() => t.end());
});

View file

@ -1,5 +1,5 @@
/*
* Tests signing in according to smoke-tests at:
* Tests from:
*
* https://github.com/LLK/scratchr2/wiki/Smoke-Testing-Test-Cases
*
@ -7,6 +7,7 @@
const {
clickText,
findText,
findByXpath,
clickXpath,
driver
@ -20,7 +21,7 @@ const test = tap.test;
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
tap.plan(1);
tap.plan(2);
tap.tearDown(function () {
driver.quit();
@ -44,3 +45,14 @@ test('Sign in to Scratch using scratch-www navbar', t => {
'first part of username should be displayed in navbar'))
.then(() => t.end());
});
test('Sign out of Scratch using scratch-www navbar', t => {
clickXpath('//a[@class="user-info"]')
.then(() => clickText('Sign out'))
.then(() => findText('Sign in'))
.then((element) => t.ok(element, 'Sign in reappeared on the page after signing out'))
.then(() => t.end());
});

View file

@ -59,44 +59,17 @@ tap.test('getAppRouteCondition', function (t) {
t.end();
});
tap.test('testSetBackend', function (t) {
var backend = fastlyConfig.setBackend('wemust', 'goback', 'marty');
t.equal(backend, '' +
'if (marty) {\n' +
' set req.backend = wemust;\n' +
' set req.http.host = \"goback\";\n' +
'}\n'
);
t.end();
});
tap.test('testSetForward', function (t) {
var forward = fastlyConfig.setForwardHeaders('alwaysforward');
t.equal(forward, '' +
'if (alwaysforward) {\n' +
' if (!req.http.Fastly-FF) {\n' +
' if (req.http.X-Forwarded-For) {\n' +
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For ", " client.ip;\n' +
' } else {\n' +
' set req.http.Fastly-Temp-XFF = client.ip;\n' +
' }\n' +
' } else {\n' +
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For;\n' +
' }\n' +
' set req.grace = 60s;\n' +
' return(pass);\n' +
'}\n'
);
t.end();
});
tap.test('testSetTTL', function (t) {
var ttl = fastlyConfig.setResponseTTL('itsactuallyttyl');
t.equal(ttl, '' +
'if (itsactuallyttyl) {\n' +
' if (req.url ~ "^/projects/" && !req.http.Cookie:scratchsessionid) {\n' +
' set beresp.http.Vary = "Accept-Encoding, Accept-Language";\n' +
' } else {\n' +
' set beresp.ttl = 0s;\n' +
' set beresp.grace = 0s;\n' +
' return(pass);\n' +
' }\n' +
'}\n'
);
t.end();