From 79499b677f48f054f854f8ab9f19d9b945a72437 Mon Sep 17 00:00:00 2001 From: jwzimmer Date: Mon, 23 Oct 2017 20:15:37 -0400 Subject: [PATCH 01/25] Updated selenium & chromedriver versions, which fixed test failures in test_footer_links. Also fixed the clickFooterLinks function so it only checks links in the footer, to remove false positives (e.g. test finds 'Tips' in navbar and passes). --- test/integration/package.json | 4 +-- .../smoke-testing/test_footer_links.js | 31 +++++++++---------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/test/integration/package.json b/test/integration/package.json index db2942f14..fa26009a5 100644 --- a/test/integration/package.json +++ b/test/integration/package.json @@ -1,6 +1,6 @@ { "testDependencies": { - "selenium-webdriver": "2.44.0", - "chromedriver": "2.27.0" + "selenium-webdriver": "2.45.0", + "chromedriver": "2.33.0" } } diff --git a/test/integration/smoke-testing/test_footer_links.js b/test/integration/smoke-testing/test_footer_links.js index 57349d152..1fcb50d56 100644 --- a/test/integration/smoke-testing/test_footer_links.js +++ b/test/integration/smoke-testing/test_footer_links.js @@ -11,13 +11,14 @@ var seleniumWebdriver = require('selenium-webdriver'); // 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(); +const { + driver +} = require('../../helpers/selenium-helpers.js'); 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 +34,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)); }) - .then( function (element) { - return element.click(); }) - .then(function () { - return driver.getCurrentUrl(); - }); - }); + +function clickFooterLinks (linkText) { + return driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.id('footer'))) + .then( function (element) { + return element.findElement(seleniumWebdriver.By.linkText(linkText)); }) + .then( function (element) { + return element.click(); }) + .then(function () { + return driver.getCurrentUrl(); + }); } // ==== ABOUT SCRATCH column ==== From 658a65c8074b0bdc73b1d45d7b0a4b3eae15ca88 Mon Sep 17 00:00:00 2001 From: jwzimmer Date: Mon, 23 Oct 2017 21:17:55 -0400 Subject: [PATCH 02/25] add findText helper function, add test for signing out of homepage --- test/helpers/selenium-helpers.js | 5 ++ .../smoke-testing/test_signing_in_homepage.js | 46 ------------------- 2 files changed, 5 insertions(+), 46 deletions(-) delete mode 100644 test/integration/smoke-testing/test_signing_in_homepage.js diff --git a/test/helpers/selenium-helpers.js b/test/helpers/selenium-helpers.js index 2b4be3bde..a850a6aac 100644 --- a/test/helpers/selenium-helpers.js +++ b/test/helpers/selenium-helpers.js @@ -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 diff --git a/test/integration/smoke-testing/test_signing_in_homepage.js b/test/integration/smoke-testing/test_signing_in_homepage.js deleted file mode 100644 index 21e676c14..000000000 --- a/test/integration/smoke-testing/test_signing_in_homepage.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Tests signing in according to smoke-tests at: - * - * https://github.com/LLK/scratchr2/wiki/Smoke-Testing-Test-Cases - * - */ - -const { - clickText, - findByXpath, - clickXpath, - 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'; - -tap.plan(1); - -tap.tearDown(function () { - driver.quit(); -}); - -tap.beforeEach(function () { - return driver.get(rootUrl); -}); - -test('Sign in to Scratch using scratch-www navbar', t => { - clickText('Sign in') - .then(() => findByXpath('//input[@id="frc-username-1088"]')) - .then((element) => element.sendKeys(username)) - .then(() => findByXpath('//input[@id="frc-password-1088"]')) - .then((element) => element.sendKeys(password)) - .then(() => clickXpath('//button[contains(@class, "button") and ' - + 'contains(@class, "submit-button") and contains(@class, "white")]')) - .then(() => findByXpath('//span[@class="profile-name"]')) - .then((element) => element.getText()) - .then((text) => t.match(text.toLowerCase(), username.substring(0,10).toLowerCase(), - 'first part of username should be displayed in navbar')) - .then(() => t.end()); -}); From 2813218dc9c377c6c97ef9aeaf055545b03189b4 Mon Sep 17 00:00:00 2001 From: jwzimmer Date: Mon, 23 Oct 2017 21:39:33 -0400 Subject: [PATCH 03/25] added signing out from discuss (scratchr2) --- .../test_signing_in_and_out_discuss.js | 59 +++++++++++++++++++ .../test_signing_in_and_out_homepage.js | 58 ++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 test/integration/smoke-testing/test_signing_in_and_out_discuss.js create mode 100644 test/integration/smoke-testing/test_signing_in_and_out_homepage.js diff --git a/test/integration/smoke-testing/test_signing_in_and_out_discuss.js b/test/integration/smoke-testing/test_signing_in_and_out_discuss.js new file mode 100644 index 000000000..65d48c64b --- /dev/null +++ b/test/integration/smoke-testing/test_signing_in_and_out_discuss.js @@ -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()); +}); diff --git a/test/integration/smoke-testing/test_signing_in_and_out_homepage.js b/test/integration/smoke-testing/test_signing_in_and_out_homepage.js new file mode 100644 index 000000000..43775dee2 --- /dev/null +++ b/test/integration/smoke-testing/test_signing_in_and_out_homepage.js @@ -0,0 +1,58 @@ +/* + * Tests from: + * + * https://github.com/LLK/scratchr2/wiki/Smoke-Testing-Test-Cases + * + */ + +const { + clickText, + findText, + findByXpath, + clickXpath, + 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'; + +tap.plan(2); + +tap.tearDown(function () { + driver.quit(); +}); + +tap.beforeEach(function () { + return driver.get(rootUrl); +}); + +test('Sign in to Scratch using scratch-www navbar', t => { + clickText('Sign in') + .then(() => findByXpath('//input[@id="frc-username-1088"]')) + .then((element) => element.sendKeys(username)) + .then(() => findByXpath('//input[@id="frc-password-1088"]')) + .then((element) => element.sendKeys(password)) + .then(() => clickXpath('//button[contains(@class, "button") and ' + + 'contains(@class, "submit-button") and contains(@class, "white")]')) + .then(() => findByXpath('//span[@class="profile-name"]')) + .then((element) => element.getText()) + .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 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()); +}); + + + From 44bb646ab2d7798ba7cf8c34ea8f648e6b965593 Mon Sep 17 00:00:00 2001 From: jwzimmer Date: Tue, 24 Oct 2017 11:38:07 -0400 Subject: [PATCH 04/25] removed redundant require statements --- .../smoke-testing/test_footer_links.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/test/integration/smoke-testing/test_footer_links.js b/test/integration/smoke-testing/test_footer_links.js index 1fcb50d56..330b2d548 100644 --- a/test/integration/smoke-testing/test_footer_links.js +++ b/test/integration/smoke-testing/test_footer_links.js @@ -4,17 +4,16 @@ * Test cases: https://github.com/LLK/scratch-www/wiki/Most-Important-Workflows */ -require('chromedriver'); var tap = require('tap'); -var seleniumWebdriver = require('selenium-webdriver'); - -// Selenium's promise driver will be deprecated, so we should not rely on it -seleniumWebdriver.SELENIUM_PROMISE_MANAGER=0; const { - driver + driver, + webdriver } = require('../../helpers/selenium-helpers.js'); +// Selenium's promise driver will be deprecated, so we should not rely on it +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 @@ -36,9 +35,9 @@ tap.beforeEach(function () { // Function clicks the link and returns the url of the resulting page function clickFooterLinks (linkText) { - return driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.id('footer'))) + return driver.wait(webdriver.until.elementLocated(webdriver.By.id('footer'))) .then( function (element) { - return element.findElement(seleniumWebdriver.By.linkText(linkText)); }) + return element.findElement(webdriver.By.linkText(linkText)); }) .then( function (element) { return element.click(); }) .then(function () { From 4fe3997df4663a766fc5ac099c31c0fea91c9750 Mon Sep 17 00:00:00 2001 From: chrisgarrity Date: Thu, 26 Oct 2017 10:00:34 -0400 Subject: [PATCH 05/25] Top and middle banners for HoC 2017 Css animated top banner Middle banner of activities - responsive https://github.com/LLK/scratchr2/issues/4869 Also updated ttt see-more/open- modal link to be consistent with the new icon from Carl. --- src/components/ttt-tile/ttt-tile.jsx | 2 +- src/components/ttt-tile/ttt-tile.scss | 7 +- src/views/splash/hoc/middle-banner.jsx | 115 ++++++++++++ src/views/splash/hoc/middle-banner.scss | 49 +++++ src/views/splash/hoc/top-banner.jsx | 108 +++++++++++ src/views/splash/hoc/top-banner.scss | 227 ++++++++++++++++++++++++ src/views/splash/l10n-static.json | 10 ++ src/views/splash/l10n.json | 21 +++ src/views/splash/presentation.jsx | 9 + static/images/hoc/a.png | Bin 0 -> 4444 bytes static/images/hoc/c1.png | Bin 0 -> 3015 bytes static/images/hoc/c2.png | Bin 0 -> 3027 bytes static/images/hoc/doodads.png | Bin 0 -> 12323 bytes static/images/hoc/h.png | Bin 0 -> 4791 bytes static/images/hoc/r.png | Bin 0 -> 3000 bytes static/images/hoc/s.png | Bin 0 -> 5374 bytes static/images/hoc/splash-left.png | Bin 0 -> 11683 bytes static/images/hoc/splash-right.png | Bin 0 -> 8649 bytes static/images/hoc/t.png | Bin 0 -> 4002 bytes static/svgs/modal/open-blue.svg | 12 ++ static/svgs/modal/open-white.svg | 12 ++ static/svgs/ttt/see-more.svg | 17 -- 22 files changed, 568 insertions(+), 21 deletions(-) create mode 100644 src/views/splash/hoc/middle-banner.jsx create mode 100644 src/views/splash/hoc/middle-banner.scss create mode 100644 src/views/splash/hoc/top-banner.jsx create mode 100644 src/views/splash/hoc/top-banner.scss create mode 100644 src/views/splash/l10n-static.json create mode 100644 static/images/hoc/a.png create mode 100644 static/images/hoc/c1.png create mode 100644 static/images/hoc/c2.png create mode 100644 static/images/hoc/doodads.png create mode 100644 static/images/hoc/h.png create mode 100644 static/images/hoc/r.png create mode 100644 static/images/hoc/s.png create mode 100644 static/images/hoc/splash-left.png create mode 100644 static/images/hoc/splash-right.png create mode 100644 static/images/hoc/t.png create mode 100644 static/svgs/modal/open-blue.svg create mode 100644 static/svgs/modal/open-white.svg delete mode 100644 static/svgs/ttt/see-more.svg diff --git a/src/components/ttt-tile/ttt-tile.jsx b/src/components/ttt-tile/ttt-tile.jsx index db543c26f..4bcb3d109 100644 --- a/src/components/ttt-tile/ttt-tile.jsx +++ b/src/components/ttt-tile/ttt-tile.jsx @@ -47,7 +47,7 @@ var TTTTile = React.createClass({ {this.props.onGuideClick && (
- +
)} diff --git a/src/components/ttt-tile/ttt-tile.scss b/src/components/ttt-tile/ttt-tile.scss index 9a244bcc1..2078777da 100644 --- a/src/components/ttt-tile/ttt-tile.scss +++ b/src/components/ttt-tile/ttt-tile.scss @@ -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: 1.25em; + height: 1.25em; + vertical-align: top; } diff --git a/src/views/splash/hoc/middle-banner.jsx b/src/views/splash/hoc/middle-banner.jsx new file mode 100644 index 000000000..31ce74fd1 --- /dev/null +++ b/src/views/splash/hoc/middle-banner.jsx @@ -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 [ + , + , + + ]; + }, + render: function () { + return ( + +
+ +

+ +

+ + + +
+ + + {this.renderTTTTiles()} + + + +
+
+ ); + } +})); + +module.exports = MiddleBanner; diff --git a/src/views/splash/hoc/middle-banner.scss b/src/views/splash/hoc/middle-banner.scss new file mode 100644 index 000000000..2e39668d4 --- /dev/null +++ b/src/views/splash/hoc/middle-banner.scss @@ -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; + } +} diff --git a/src/views/splash/hoc/top-banner.jsx b/src/views/splash/hoc/top-banner.jsx new file mode 100644 index 000000000..5d2dccfa6 --- /dev/null +++ b/src/views/splash/hoc/top-banner.jsx @@ -0,0 +1,108 @@ +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', + 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 ( + + + + C + C + R + A + T + C + H + + +
+ + + +
+    + + + +
+
+
+
+ ); + } +})); + +module.exports = TopBanner; + diff --git a/src/views/splash/hoc/top-banner.scss b/src/views/splash/hoc/top-banner.scss new file mode 100644 index 000000000..00ddcfb37 --- /dev/null +++ b/src/views/splash/hoc/top-banner.scss @@ -0,0 +1,227 @@ +@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 { + 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; + } +} diff --git a/src/views/splash/l10n-static.json b/src/views/splash/l10n-static.json new file mode 100644 index 000000000..12fd6c5a1 --- /dev/null +++ b/src/views/splash/l10n-static.json @@ -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" +} diff --git a/src/views/splash/l10n.json b/src/views/splash/l10n.json index b289b440e..4a87ddc13 100644 --- a/src/views/splash/l10n.json +++ b/src/views/splash/l10n.json @@ -28,6 +28,27 @@ "teacherbanner.subgreeting": "Teacher Account", "teacherbanner.classesButton": "My Classes", "teacherbanner.faqButton": "Teacher Account FAQ", + + "middle-banner.header": "Get Creative with Coding", + "middle-banner.ttt": "See more activities", + "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", diff --git a/src/views/splash/presentation.jsx b/src/views/splash/presentation.jsx index 4504675cf..e0827551d 100644 --- a/src/views/splash/presentation.jsx +++ b/src/views/splash/presentation.jsx @@ -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,7 @@ var SplashPresentation = injectIntl(React.createClass({ {this.props.isEducator ? [ ] : []} +
{this.props.sessionStatus === sessionActions.Status.FETCHED ? ( Object.keys(this.props.user).length !== 0 ? [ @@ -273,6 +276,12 @@ var SplashPresentation = injectIntl(React.createClass({ ]) : [] } + {featured.shift()} + {featured.shift()} +
+ +
+ {featured} {this.props.isAdmin ? [ diff --git a/static/images/hoc/a.png b/static/images/hoc/a.png new file mode 100644 index 0000000000000000000000000000000000000000..d0a33f66fff89b16ce8a0cecfce903b5621e696f GIT binary patch literal 4444 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 z zwYA+weiIA^>o;xMR8M|)&`ya|)HP1Z6nGQ`D}nKN{KfF_@RymSOm6{c2#3Re=UvxC zA~8KWI{J<3{vWU)FW@%f=I7`Cqop3r7ErlxU0&p6sPBiQ_$;eXI`iV175C}s>Fa?& zpo61>eYC2o>NBs`Tf=C^<<0HexA(TRv<#{K6e4Agg&X0TnHTV%jvYJ3wZ3cDu08GZ z`98UI>sAwV79fBFc`4bkY{U@hgYl!wE#w_Ja%Attix=P6d0(PhckJA`^QM{l_V)I6 zXpjf^i5fEI2!y}`l8e^-&YwU3$>GC?$DFu#EEc<2T3Wipi0|1!kfpONkTPjW0^KzB zL4%Z+mw#^Z=d*ALQWB(;Qva*S=tYPTQq$1TP``Qe<}R2F&{CP>8s*BBD<^b(HBNhWnc_1r62dAn;j>CD3s4JG`p1a>IM&qE^sSTj<5+PMI3`o~X1)+-?hMteMRfWxu6{a0YvMPZsB9r&_?c1?C zckX_x;n?$xTtV#48yC#fDx9wxw*MM zjqzyS;i`pHs!=@|Fsqt0FpXS(;1?pRPgpz4(3?ezlA9inD;4C;u)26I0)^i1H#IfY zmtmGAE?`yD1`cz0vZ&91$QCI0eA!iu_g$~xEaNt?2GUS&8 z@=jL4Kz$A=)MvqxmraEVtv=!Pc6bAfs?UN(HWm7_$bKNK!ux!_x8%NC$vIc=)|iW zdKo2fIWUQ@%LyEC%LCAWLr9>D(OJkb+q4M$!DY|UWoPPd;Ry_dLJwW`9H|7#5HX)l zLfEOo+iBzu$XH!R-(+ZX)Cwi2h{a;JjqEfP46rBf42wMOidiWryEAkgEy*8|-X(Ut z=ZK5tbc7;CdEC3GL<9_y)<}Q*oIyQKI3aX zrC2p<#H2{evi}@m9jz>U#B9q+@xj5t>)6g*(P?$+FWX`$YzwX?H~1g6Ggp9ve}=UL z0?OeZ#aqw_KKO`-3I(LXNWFUX3e_lIjPfEd1=t0PQpgoyt^`6zM2IGf?aU1Ut^@)g z3Lpeh#kO{r1TKj<)ZgFVjcv^ZmR=x}b%^)M^hq=t{aaaC+3&E;*#IJKcs!n3E9->0 zROpiZcMzN#4H|ZM_Uu_FA&=_@Rd6MirJsQ`Hn={^EU{GuPZgS))K-5uIXU@lWo6|- zlp*s_WR*$B8s(xw7o`lFD32dMei~J&d8o3=*b<-!awvSDR;966>|dw|jYE~0teU$Y zD7}k98&XOmIQUPVJVDpO1R!sO;xuCAT1K~K`6X30NgNN2xeXbzwLrwF8Qv0@#*BJQ ztg5Q&eDL7GUMrcq3WYpKOp+nhPPrW_RB6;K>n*!>?drR5;X)IH{y?fSlQ%dxsFj^8 zYIl7+l166BU@-U_G*Omk5QTQpXtPolWEh2}*$X`k)G12hd&%qW?#`AHsKn|$%Z=3l>l9o)Ws`$8g-7$Zd1$g*6LujGN1 z+0f8%Plq8mB{7P!XeC64Zb?jo4CeLC&CNIS>1AQaT`@5+(F?Dh=*-z`$?s!HV0wzp z&}=${iQKkr+ff(q{!L9pU%PhgI}>?&Vc*GXmUUqfB&WiRISk|DfddEXtE;PP)~#Fj zTemC}>&#VXi}bM~Fl`POQ*~;}w}HgL`}XZSNQkTl73;L&1D0eY66qTsAMYC-9UX(+ zTA@(rjgAzrnwOMR^T{F+s6>^-AfqK|w1ex{uRm(%cxk}=H#AI2?_ONu65Esqfy*Ym ztpE%k6fhM zmvsw5EF5&6j=UK)0&ll@CH-m7-3l;NY+Y8fJJu%5qppzHrtKC*>R^WIE%o*CmcTSv zrR{bKvkLcVRWQj6OMoyjMBRJ>~IJ2 zi9o=IR0`*4X-G6-NDk?#Ni002ov JPDHLkV1gS?vAO^N literal 0 HcmV?d00001 diff --git a/static/images/hoc/c2.png b/static/images/hoc/c2.png new file mode 100644 index 0000000000000000000000000000000000000000..23d10fd80424c87ce6ccce95ba48eaa01c8eac90 GIT binary patch literal 3027 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 VnP$~wZHoW^002ovPDHLkV1kTlt{VUV literal 0 HcmV?d00001 diff --git a/static/images/hoc/doodads.png b/static/images/hoc/doodads.png new file mode 100644 index 0000000000000000000000000000000000000000..be7be7733b940ece195aa2c85cf973ef73525748 GIT binary patch literal 12323 zcmeIYfdNDt@*N~2OE&A=eiAYFq9C@tNg(m5av0@5wbNQ1yd z3S8|mI ze6i4%+y+1NZxr<2ys$^UaWi$YfaJ~X%`8}zY)vgKo?DojdpLGmNJ9{RyV5`MTJAqL zXVlagl^CIUv1FOPYaJ3TQIu^o`d{vFT(yydvpvOdY<`R`x@p78arK@>sDt`Vr?>9v z0uERFv|W!G*;_T-rfl7&2Jej}UORtMiQV!%EiTXjlJxbhx0^~e6y*++FRvq)`s0%E zP{&sJS0E_Qh$O6$++rib`0x>imJ=U!e3KO)LWL9?F?HCWdo{;z>upNL=00S!KY2|G zA*~gtAxO?ehyMmbbJO|2!GTw$38syJ1iY@8Ly%@MR`aJs0`^NuBGgQJEea2kt4yah z`1ZL6GO5DpCG({QwbrJFVteiybORaSd6rWS=&m>(zVP4~QPlNfZmj*I6qRwQ2Zxeed;SW~_ zn4rE0QXn49FlZ$W0_v$Z4W4yA_}?b<#PfNUt;QSZ?+k9Q*Ik3OdU@j^h&OgDwAF24 z*?GKSUhmHkJpl?MOa%6I9f&4?47Yvw=eHM-zHZU=`n}g8;PPD>ki~M~KGYYCQ%`t2vheJqsB<6s-%W6U5yID?z0aX+5Tte} zYi;MSDd#{6t!UsD46pQboE%WZAk>`G!t1otr9vSAKOvy}SLM}Y(ktXy(zlrKQ73wDFpABR>|~<3Zh%Ya=o{Sy@30?cbFH0^S1x z+65EI+$yQ<8=P*f)a%ftt+5NA(Z0FM)5NyqbiaT z8hjl?xB=8`+W{_EX1*k0LZm!;^(_Kw9`t=i2<=ySh}6u4TTV8g?v}e`BT!cYC<7;IkoKO$x}_xXvp8Nc|8_yFZzlW+G~jgb1%uj6mvw; zRK4gnet5J;bwwLXvEl8o0-@^Z0!TB1#3G5%3vS0x@dc3PZwR|laAkjVHHNw6^zEXX zE`Kb{UTpS{GSd?dAB`3204GDsn_xXsA(UqbzD6|*9QokE;Xf{T6;a4=?+ohgpY93! zT1LUx4%DTG7)gBEs)1iWJsyjEc7OyDcmR%P-Li3hcd-1~3ZWbuFt0g_7`OaBYbM^Q8>d*yOY`{s zVRYMe*JQhMVVf%+v~^#O=!bV2w^(wAe5i$tC@*5^>*caVVQG)4$27^){>NuGJi+Ok zs@D~}SW-eYkw9yEYQ;kdb=DD;n{qX16GF|OghScn`fYBL zNy5=dm`~y<7Nx%~%v|;Qe!O2g{(8NL!7|f}?=vaQ1Tc#;-y^SQ7wO=RZu#J$r0Bn} zWFntuZUCzctmrbM?a^Gcc>^nd7WA~fO|~h}_TMUk(BAv3Q1f%(Uh;B{T9z}G4Fw20 z7FFK}3eX65veVZ{mUwYJCAwbN*fv)PHB;Qe^(!Sz*p{wp*6>GTxo%sNM*FB6iu$+j zo?bbB;3t^|HT#eblWS_MW;O7aoxSW^JOR7e-{T*)iA7&ub`9)gJiHZPKbR(|#~Dg- z6frYCn5vFKO3X4x^hsd2UghsXxp)!Eq-*#g0W%8`!^7=E|~&VxHU*^cHit3zp29s>HLxv zk4{obn`KQrvdXy;{&2Yc0m``_I>37l%y^Hfys4 zWYwOIdMi?SUKKkzxd%}4w!7O+7G-biNz?5_O!OxmEj&3abKfi2gq5-2-i8OMUEpwv z$z^G(q~6s=ij6l~jLqLCU(X9(E_{6!3oNYP$3w#VWUSPI)^SS86xP&%;ds2JVReZW zq2%~sW2{oNSN|H{Bto(P^$8qV@`4B96q99`jGBB|aBTlwuZ24L0rQX4jmrwNr*%bIVLmFQtohN5@x&93&kwvxYaL&p(mHh~&va zWG)R-4Z)!?3Zj)yP+poOz=~eIk6jj3iYihyv1!<@+3buy9qXq^-fn1p~~gV zJ76GD8q9od6Wih)zh&I3diG16Eb#0TNqp21#^D$*;}!nSY0PL*y# zsrT1m5pKk#X*30+&j~{x5O%0A5Ua(XKVh6*bHAt;~PJQ0F!eqGl9*z zX2JHY^t8LDq*L3HDZkhPZ2SXDBafDMj{OxDxA|M04CRGhrHf4cDyk6fL4IuP>LL3> zHu|JbafYw#%z>#KI5nYhyyRR%vov4sOFz97YSOndi`F@(veXo;`Zl=|@gLwEg4+9O z>sM}5cHW(mW=OSYuYYM9ynhfH=0!9*kO4$i zaUEzk&KJx?l?UuIKD@#JqPbd<|B2rVXGq1AW+`s}I$zVuSIu`&5`W$TZRh+jRgIi% zzV#~ngSK&=%&Ue^@*%bFQZ6B(?tdwG>0YMRAs|`^(Xs&o6_g>2QvF^?;G}?OjwilK%Pz`8*TKXP|7$@F!i1bUN_Xflz_hZOxh3F7B?ru{+{v9z6{vYj5*5U1n25S( zE#1hI>FgcczB}2O_&*0mv5yPrAE&#~N7{N92F3Dw;Q{72tXRD*_$Hq~k5gA-d#B?k ze4qUBsrq%G0DXsIJ6AB5YxQ)`McRR9T%G9^)pgd+{uiWe+j-o2yj+?@&1NUb;D)sa zv&U5lB9K!%TbW(lJ4asXY#w0O{;_vl#jw~Le8C!FO9>`Szvjq?bYvj&?^b2;P@2UQM34W8`BAcU+LClH| z@3>zlBmb12k!{}26V0L(bJaWB%%bEK?G~&#eXlk8nxkD^{T}jw3?%upCJhE`=G@(_ zOs4|Y`WyD^uACxYvE2fcgkk_w?(KuQo>_=|gyBqLua`6(*w zuI!4zR?%4hNdb@`bH@{404*0*a3x%IyzfGWyxrN^B%EdrNwxrn64QJ*AE{@nGY4Q^ z^D*dS4@b+5#5Bg}_$i-8oMPYYKGESj8o%B->_X$w$6tWD`+cGZGw*anuwZ-Q^g6QX z&7)%~zfyqTAsS_yZR%@Lwt>4@|EfP1;EkUfv3@cp%yY$D<+XzZsZBe7it9;k;_+B- z%yC?u##WvJ1fjBTcHZ$aG&xSh?!Q89y1V^Q!VX))U0MVhQ@RvTHrL4bDh?05YYl_v zu)SCCA!t4xKp!c`hO&%$`0P3#J-(T~u;jtEI*SKgfK4|kU+_|}Ev$Spf)xOH2*K8I zN9rnD6^|4KhKH_{U^*EAU?ont3qjBo-vu_&{w4`Al=${!R5ccK{|9W}4fBe3on254 zR$H9+HhY(Q)k(0RDI0gfPX`#?`-s&UJ+4JPNKp5;xYRVnjmb|jiy>-&UG1E9Ug=Un z+0+){@*+riUG$guA@DBSseB>0j1Yo$k1AF=X)C7dyf})<)u|OgY8J1|-#gd8TeuE} z%;$<(Ft_gv$zs#K4y4`f5ei+Vpf^BHEi8+yE^73dU4sDh@2ta**0=CS=HyRm1q zn^j*Zpm#y;JcH%vQD_U=N0bmuZ3qGj?7K?+cf&Rly}lb8Ngtx&ALXmeU(T3vzjg!X z7uCMY%b1rW$8rrd@}>@ zpmdab4`6N3LKXkkeETt+r|c!^yL};f!Pkl)zX1c5!KvTipUOJ>c<%P7Mm%*;8;SA( z4gc@&|7qa=s|HX?m0G|KAjn0yrtjy%vg=x))665&_kvXUdI1w>i-DlTl$K=y{Y{bB zN-lfPR4dmX>o)?BhZKLU-Gq-Qp*0LWLch8GSd1jraBg1($Jjs+(k`e4jhz+HUD8_DIrg|wA3<+ElM!J4zmKZP`MdgJ=YpNPAuZ@F>{dLFI`HQZBur&lXppjW zF;S6G=i2k%V(df-aE=Cp@m~&RG;scl|4b1D_t5@~@Q}KY$I%)P6vt?Aqh9;Mu%eX# z`LX-IfizXhCA7q2O(p*(8nsrW4w;K)kVc2J$#fz|2Z+?r-> z%d&&Rg}T3XiT=#yL`Kz+`qcZ<#z`3=va_ainh#4M)MaCbI4PaqwXS3g z08!y4v39)-*3y3a%b2qrPQ>^gd$x%UTEi?cJX{>-VC)z9alEE$-@5I&uzx(OwDHpm z=W;VYhIF_{vK{W}4pFbnw(sIR&9d{+PFB9`Qj_2H-eM1a+-xGl0K`=jC&265d`}c~ z6ZKKNMD>p($Y2*;7c<$rv&z?&8xJW!N4a~3ae9`8ZIb7oc>1K};;$gK%b%Ip)X%R^ z2`=@W0v8RCM-M@lQU78yl7{t^OMs@6foICFeCeD3wEf^M-V&%Nd=E#m3ji z9vRLU^%h${TeYQ9WkgtGKk`So6Hx4~Ebf3`<-}g2p)BeNd(kwqjU zG48n#bkynjU7i+Bq-;HyccZj194Aq7;aGnOaY8F^c{Q63EF7<@ES&uC?82u7YuIU* z5r}WjC2%0gNZ2usGziw6Rm2Smr^Pq0K&UuMm&3kqbm}~mioCpe(Ucbc7LoRLilHP! zO3t-G>b+FR$Hrot1AjF7+ac~Y$yH?YmIoBMcTDJk3ak|SgipxDYhax+`h=7JN)5X5!kBnE=n+l4dmd2R)zxNkKII@1D{$={$E4S4pbZ3Vd5vGD3+d!-A zw*7@#rL^s&yb~+~(dd#!YcpT2<({9ZFnwuS-$Wa661x$!gY&)Tg$s`+@|AV1lY=9` z?l{Y^B(ArrFQu`o#=iN#x7RJ)h&y`Ly$W#?@m5XFI5{MpQ~P>LwfA>q>OE8=`OSh3 zU7p~&WJZ%)rkBJv(N1p}&)&uL4lNz)pAAgN#k{FA=&+S;9#BLVy0)mb6`>=!CJ!>>sD7LXEC_ZsPpbRR>(R^DE2i%nmTi2>J5grx67J_CuLB zbbK#@MIt6!2&Vd|@8cFnN$S;Zu;8#-`A`vLp(gco+!+VPp}g-sK22Z|ta%J!PKSIs zR?2rmXCK!x+R}5c$?ta0{eY!TR?Hf+e0kOXX1`4uHIs~S8m7OmvEBXO^tGP60X9tc zLzB!WD7Scsy46)mPRIVD#7q(%51OA+R{Ist*fC$g0#x_)wg0z(@+=_`37xX6_0~MA z;r}kH{4p?>?!ReY>T%V*{fl#5wXu3+R6c#a*OY zzFUlWP^!g-lV0ts&>GPJZu0E^#)p3u3J-5%g>$0omy@^w9E38JbaHCZ04=M#A7{SA zxsO*NaaQ&Y+g~bKj9j%I{GF!kdAlNz(Gv5@pxjy;cU$GsinS^iK=#1j(B{}pI{Vjf zZHu*PcfdCoqeXOD{%hRO^;g;J%wOpFmJ-cRDQy&W)o>rVhmw~E01Vw&SQd({yi&Li z=GI8%#x)g`3H7D{V7%yVE;#98WK!vqi*tIYg0Cx;OG`5?8^3oiKDNt(TTL8IZ#@TJjTv<=d#|DXLOX2)}*JoOsI zH@~oy+KJ5?6y5?FNF>7(O8gA!FqOY|#bPI5=j+_D8d<4;D_Se$0_}Mb5PhE#NrYig z`9;^K#?1_qq&{4*P80&;V!*gVjGu7lZHfTIY8F!$H!v>L+AY=qiKiHZpGMgSI?hvf z98o9+?vsfSPXV;@NqFHzKg*!Rh{c>ZHOb8)84E}^^|be+EizD0mKfktcZ0jaBt1E6!$7=VL7T~Yvn^&7>* zl8pGvpLOWQd1gsZEK{H;CJJ2Lt#U81UnbmT=p*wzmQSugB1sVaXztFfPPS0_a^0*V z-LEN=Q6H9~4f7qL+coSi-+NisJV(20Uy?@cKRRaU@u(U9Gj<>PL3PDkb1+CrijlCV z>M=ZJcLjmZj*Q+P6@lCgycS!N52(cGSvtH)CsKqfuS270V3m5b&;YlWRCY&?trWup z=u5L+!+?$M*U%%A6$B^Cn^uBX>HVKuYSf(SWt{exEjA3WWlssZG9}0ou{J0ELh?eW z<;Iet?2VocdTe<{ID5F`4Bo5vU`V}9tYL%e;Ni(0KfivEz3H8+QSy5X!lf)VPpPRh ze!+JBVKG@N;YjAg_0>$GRs?~LL6W7F<_L%l~mG6m)swCly>M5H052% zbGe6<_L>YWs!Mv{r#k|vII7V1+)zJI!GYBX=@E86OZ~k!KY$O+uAjJ9uD|WUn)ee zH6>sboS=)@?4abiOVp7rr2<{|A`2l$~ujZg+FS#wjA{c9%w@&x+C*?-UWMHv3oy$tVyZ- zoh(@Pbf?1+HZ7N)&&A-tyxx9jJ!E%e)wEsF&5^nmHxir!H`uDdE51-R#-43bz7r|gOjLoM2B#+EtF!zAaJ9{H?WgRxceJm!+ok;D$K7@(HE9d)+4sk zzIwCgrOavJh04*YBz`NUwh=zqh+GOuv*MI{Ku;S~_iwCTZqK}4e2G4b<6uZ zeZCO#i>?W#YTfMCRosDHvBdnT=n4{k3*_`wnXyE9a#68@lBM?5iQv14h4UsnsuKMQ zcUT8nsF!rHXo&Fg;1RXvV@K~X(J{G^HM47D?_Z1Lg78GzM4qEDqU;9~U`1>ebqL{bl zsFpLDx~e3FP1Gg%r(A8ISFZXAI_><>|EYJLQ`}aCXF&q|CTjpmo8seFSKH*ZmP`uL z0Q|N8B5BHk?p0>e!;JO__?@m*9BZ1KaTnG|F{%_BIvighFbFOl>>l%!FQYH*gpbiY z16AkHv}^J_EF<*O4Exe9ixxBW>B}%uIB^r-iNN9!_hfXQwFQ*%s8z z8qnDEwegm%lH2eTdHI=>6Z2#UX>wU&szY1N77*X7)Vj`e|}!{ zO2)p?c+sJKux+=W#>HaPu;NX!9B|5=(@kftiFQD+WH_{?BnA{sAMr%ei*+GdIc3&} zSMJLSlCdEpJBZHr{?xKu_#GWGKXe@JL&Wqn^xtOTX1%y@9@xkd?3(BB;%MD?G-Q)# z`cS3vNjrNIinB^dvRauMo8caQg7y$`U)2%>#dDLO#W(wFz25cTXHqRb?{tf_;l*z- zybuZ4T=Q9azub4b-j+jy-T4C|%wP1dkoW7-%=08kVlR#d{&?I#i$_u^7l}RE?joJiHBK+#Z`kVLRgrFnjCs?!M9kI=-wcAhavtP{mjk2_@!Ty| zTxUpLp`LC)HomS0BD-m~IS!J1Hg(LKBxP^zlWIpX_kWt^cMnDHs`E%Il6lrlWw4EZ zR8Y*u6-ty1^jEN>0SY}hco2#R45VOe6x19qG@-@XjA|3*)zUHbD&nC*iXhWR8r~u- z+8*0*E)mLm=9lVHtJ=Ca)Zx<|Eb5c{f$doiPl_r%(lpn!Qz})OeddMy9e@V#^ZhC8 zok}WR*BN+zzuJE@z&MhSqpDRcBlDr#T7n+O0BTxk;uE&$3_ngf{8Y%lx+zsBnf*o` zOOF+2LVjC@6nXLI6gC%7M;c&oox~O$Y2rlzhS{WQST%klzfC?|O1SRn-Qqx}?eNgG#AB zyPN51XYfU5I+nK#g^>l=xz|l1?HdmE`$K*4RCnZvsFi1w#$SA%Km6mY-w#m|k^dCT zT*-;d_`|MOZR5a;Z2ZOiWG|9pYT|e9EwgdGgQFv#o!bJ)nD)N-`YRTz@Et*D$M<` z)X6ksH-5~)carXMpjk@zS)7=3NlDK*Yp{hPOTwc+ceWggKC`gNdYx;^206vo>2=up z41^FDWd!XL#w3lMEB8gGu4t0)Gi@klTI^8KBRL(Sxl4;D#A&j>_}j4Vn$^S2ALOYrd5wi3 z36>JxJxEH{P>s{jKAVG2zvix}m5BxJ&*jL;VKBTd_Ylb8o8(de37r4A@N(5S<>aH~ z&|kCojGA<5Id2;(XetD^4AMz@jdooN2j$N~&PT+{964nyZ!);-9r87#%7EHU%dyAPldV$j z<~O&iGU}}@dTwvTP7Xe?jLw;qqIbowPv0}Ok)Y)DCuftjwk`+IvVWK%1 z!MoP4zP5#-$+MoKgM+|(7U}?UG>@E(LpZFr@ekaO!q+b+zRQ#&#aE|STgPS^ou^t| zA%5gH$MwZ@C2fCFc9yv-)^uq5V~H?jjpm(zKdnpaZz=@8akeEH4~VjwnA$^Lc2Lqk zHL?b!;Dw)wPSVcHEhnA(&b-Q$Q(sj!RcOtUIgv7UgU57z$Znydbn3oDQ0J5pL$Ln? ztlYjD-%KXS6#7M7N)PI+j+v)Q97&i+Ot*s0)zk1(o)npJ=Yz!HrI`g)k0B5w(3k_j zLz$lYip?brR0~(Y%bBqE^46v@omxaA-pzB6ocr~B8iwuFs=37n48O4?hM~Mk~mJ6XBu;YPE5iA=rB39!+j{bjNFbwSid!6~R|i;cYq9ko9ofoXHmugc%kuyx zRG2c?H8!eBFOb;PdqxUXq$VwgV?F$Hd4MOLsp-A(Pkj#qmcX9U@dc)f#g5B*HF^ zGDSd~21^$ZBjKJy0=R0Tu5SfGyCA?&An7<62a2c|SOhhzvRCkqlyc{HGe+DkUVOa0 zs7k?fYGUF`7m^V$sh?HKJ#PoxfDrZ$LO(hMqE|Hu@-KINJ!+t{^M?hMEtpzqKgK9O?X#RT()*S>=9o)P5y&h+C`=mb^1O9r z^vE2F6}b`Z(o>X*EI9k9CU!dKQiuHBFzR~ql!}wb%lP%`dTL^>)1 z&YN{5e`rT6WngsS(03tt_YAodo_iPS^=dp$RTG;$g2y9Pe*Muz@)%D2q`Z9~Aii?a z-oU(lgZ=zW72NnjmqEv>5lj2ik7{l6iAAb3!z<77lPm{8494ck?4AH9|0jD#%afY8 zXR-e|(_#D9cBps5gOHg#=q=07ZVln^G7D4-wdAwEgPLkbc0te8LNlR(^Xcu4cDn3T2462#%c@@N$34*sdWV}5M)#~jDl#> zoAv+P9-UVtMYZopr(EDCT2U~4uEJ4up=NFohe)4r5BE_}tk^J8G6e^P$ntkcOF!rg zD<^+VfU+{sFMk#V$W!AE{M`%*l~FiJG`4R|cvsq@9Pcnx{+I87MJdo@?Up8}Z}p)C zDV3RQ_LamiFCJ?!;dKo+AMW+NJ=VZyV%5P+{D-XDPP&3>{q6y{?_U}ii(GxQpr!1KQ2#MjgX#XMJS>H~K&!3kO z-obRr>P#Afk!DaY+L5jIa$R1i8JB*+U}@K6iBH|8d)vLLzJ&=UMgZF3Fj#s~EUE;eL5b^-VRA_y(h=i3jX!PBf(v5-o0an?!n{G=fr@OD;v_H6$ zzvK^wNtCkf_^b4(mT;#OC6uVhRQBHmxvFaNhhyn*Ya%&(Tyc!I=1(&lQFm2t4W>d< z_F5Ra3-FbWsh%r4V41S(4uC9oB@?YVRAIo~<= z+;a#~P*6}%P*6}%P*6}%P*70Vz*t*bi__x}J!0&6f*$ACYjHI~_aY+UO>U+S&5=kX z$)5Mn2mR)D&?7;QnBiYW4Ci&6q{m_XGZZ8f1SSOM*p1b1V$8)yW|*K5_IVTZI1jH= zAsJEPflmMqG1We|<{x_0$m0@%#9A-_q3h9khB6HS3IMbB-6l5PZ=9s2;&~+q{tE%j z!mG%B)*q-ad=^$g(2)MFxcW^STo9ONa1h~GbtuN>uONK7h8M$(yC{v|12TxmE6xb4 z5(FRcE%GrtsRY3vV2|i2jo=fSk&oFx7^99D1z|t-L7SYMT&53o><9(W1(blAV;-<< zHha3cx%mKlx=N2}r4hVA67zs1lgYD&r?-@8@J1V8=LckBVq((pbXl1OZvqBO_fVW7xukLJ7h~(Fn0vY!3+` z&2({u$h^eOC~QD8DCIjQIN*~$tb8jhZ3@=BQ3iJp&=SzSap0K^I$D6FPmwD zLKxggjVL%DEKS>D;W;RdAQ;`42dlQWcIS;7H(n2;Fks>cKo*Ck7_@tNg5n4Qk&t*C zn^hb^9HiJ{cKi12NsDoWpi-3*1oucvA_!9!rxwWivb2<_M)X^x$ok1-GNmpg1d~!k z@_-U1jj&sW0n6v}S(7nTtB(Tltpq|a_V(LvpEfzQnJ5TzACDm3*s@kA6t4C6_y3aq z)L&Q}QhN=U;t1z3yO0oGjz*(C?P`MDqWDo+t=;-Kai&M*C5Q3mhLxxBk zo2ST;^;|`w1i_uh077s;;;}1`5Fu!?C`F!PYinz}$;-_oM3KdyFGLEDVS0MnDq(et z5(F_G!+;Efbn4WpF^f|TMB>b46z(Gx1laHz!#+Tqb%r8y9T$iSD7ivXh5?$Gm^f|` zJD6dfLoo~*AxJ@JmSKP__`z~m1RzjBV8J9Qh+;aOo+ldsk$?mtLDr*+r~%vCYHDhp zFryCim{l5KgCLvDp0zkt6}D^D0?-D)4UWh#I8#$om#i#?5(M`+C<{Nx<#Ji25xhef z@()54URE5Ta;%2!f~u89C>v65Z9}0ESmzTd0!gsf(PE9EnuHL3U(|Cl49d-$H{W~U zfd}5TI9*HDcRm*?m_*dvBqZH1H+jII0Tb*!f1?i{ zvFFfK^swRmdcsb)K`-{zH3@L*)~$aL>pBQ-`i-Cw zMnWRfDYwa}bJiq3JaV zDpUr3U{)t-aB%SVv$M0uppFuPpE$sl@ud}ngbadU6o8{gk3J5>5#i4iPAVfl3c?|u zRB6R17K`&$RaKq(^QEPwf7aL6KZ*D!KPoCN;U`BxA4~#%p#MESKHiI52#83Ms`;c1 zdWZxW%f|A$k390oYZ6%8{mgxpMk7cOQM_{H%0=)DC^0--U0vNS!B2(>Hq6YfQ~@Q_ z=+ z5yPwz2Gl+B4GFXyfko>S{J{w236G-F2(pyd4H4o)Lqos9Xd3pbd*d%!TU%iZe;X;Y z2&mVBkW{zEUvzYI?CbCE2jaL14M$M%gJ8gld;E@+Q7qhRjnJ)bOJIO)dG`ly8dz{c zk;MRz%jNXNvs$P3*h`HNQ@11-G&MEtVb$avS9bAI5R^6u2(WVc=9_Q!lJ$)^(32AO zVbpQ|$3wGlP~DQ?aP8W)F~$@0=A=;NWyNoepv*$>@r2TRid>LhgF--PZ*T84JT36F zff9re1M)2g2M2fR);j1ofN~i?v3<#ut1ci=W9Z0I-bv!3Q5a0WJQZmePwazWDgU!onHZ zt|OPrO$OpAvMt+EVRdyiJ3c-Rc@uYbKMxHJ{gGId%o!OOc>&%p%e7ftOO+pjARTx& z-t2(-2~_3&L(*&8>Q@{=8nXa)|A!xbIPla{PhBeK|Bf9yHV-BPOb2iX%-8tcci&Bw zytLwr`zS*V1f2uC^2#fF;ikI%tQ$9Oyx!K<_8P`(H0ecU*}hn`Mre{j8>G|evm%55 z06VCW5=ApjSMRxLU|ZYzunPfnW%=ZjPlTy-6ZMM%7QH5bC^mRz8f3{<1{ZMCrcHnH zfF%DH+qHi6)mM8h_zz?@7rfF4=Oyv0zpsQ>lDlK{#^&a5E=Nh@Uu6j`5Q`epLIi;o zbP7vV)lw|68$V6fTlg%pBSl#6N9slV1x$_v*P{91J~oHw=EJPXvqxPUK(mhJ5?4%L z#3@j-b4h~hidvlzlDx`@VoUC&=HUA1H;~X5iAnCF2th!dqewl4z1z2Mw^~~ObC7jO6w!}Ci4e>S_ZqM#tHM%j zcX#)g#c6JOcbV?93>$uz*)ns3ND4<2sUlNA&T4_i%K{`61z6A$xZx;$DYeTGr|dAwy?I!yrWg2 zrhy<^ay5g^UIS4#5FJU(X0u}!T*%f8mL&)>gj1m=fX$M#UWC#u(2WDDt!O3u4I(wd z0Cs-x%{Sk?>)>s5E=ghn+c-E#M%gS<5W~>>M3?VPF3!rg1LUq^#!NC@^9O2~d zIRrZQ*(Qs6mDp5Xbv!dzSa}$;5WqHd*s{(UjI>*C0qgcY8`sfxO{E;ufWAMIO;|Z2 zKnP+ONg_;umtK15X^YcU#KD?Mbr}QDCpj-@CS|m?u|OX-^9ac|94)9%b|A+>YB6VI zxYL&}U*-`em>DfDzv4W$Yld164&G*~2^|RnYd(#-ERIcD8mzcw-3l}-ABbvFP+nly z**-7QG{CZRLSnO!6TlC^Bz%g^G&D9g?&Q%2ryi2#U;tjlH~^)nt*zbZvD@2+aWbr| ztnjw+InKFoVj83fA{olf@;HK9vWX5eGc&yFt`jK;Qh*a$UM3D!-yC>NDBb6G-g$>d zkR9h-L>vr44>JvD0XvyEn^-^)(k}S{Tz?O?>voWL0VMIJD2))qECjIF!V!Y9W`%5K z;mVaOAKSdeaS1_<$CBAhKwShw)3hxkBO_fNf6WDq*B+0@&)5*fMc0DB7;+Dh01XWd zTWr*$TZF#HHBS(v*w_i)f{kf#wbIBI+t7^#m_*k0SBN9Pdo%iSFfbS=PMq*;%RBQH zP;TTI9UVO&s*JFmAjGf>!o`ahU$Ci*1gLfK75lLg#jn5qdd%i6w?zp;60;D%+JeJ6 zL2hCtXgSiZUArFQoCcDZ5hVx{m<6GXRW~;r0bQmtm_z6@Witu#B2B}uF$==<^z@hw zFMt{0cDM5&w(I`!#~%+upDEGiP_v35VD}n0HZo2AUo*L4A;20|E@}R4rv2dBbLD<+ z#6k$pVHN~%8KcqYUW?P$MSKSx2N;6{M$=$X_aRq_0Pr+I9J@AvPHUXgH6+9U(_nF` zLA3jbe!qWXV`CR5n9>+lApnxd37%EC5e6xjp+P;y<8iD0QFVg8CWh&nEsA87qT;Q5 zW*i|wke9{bw5icy+ODPyet>QaQmNGOty{Mqv~iUSj3<2J8Bv2Aej6bu z5cic((3K6OUeamK^|~$!c8_Ub8OzUO)dm+XT;Nq%UlcJY7|jAFLt7JYz2+Q&3In9B zAI2yM9UUF*93jXM_vP}?X~L^lufE4QT}1@+dydHFOLO)&P75#z=+e?0)Qh|`EX3t< zyPenYJVCahV{~-X%2sX?AOuGUvW2FpsVOJ*9-^LV1U5J^DwTtprtN|iKb!pnp28-g zXj?+g<~q^s_U7zxE(I(y&3dU3fG;GeYWfX*d=Q#WBkhoGyZ_$-(A7Mcot^#I!P|fn zxote+IH2@%#0u;)`0)_s^1JCJC$uc0wK2M|x`SGgK#*sM`@$cN1`yK0@3%TRF)^|4 z;fEjg#lx@(B)K9?*hr#H(T|_4C2_fDqa2Npw{ho=>#cI&$AA9$=TAG>epWVfc94wW zmN20z8^|HWrIS<$oa}}!vYBRC#+5CE7=QTQp`oEW4KX`AJG<_?@4jBxe_(Z!frM69 zS3hJ{SsZrr9H4NJs2Qrd(wjGLZgM+GMGJzicC(BtI10EJX78o`{{A~IkbX0d9zEI) zId|!Fdfpf@gV&#V=9xX)wr%UmWHOf^3&~~^htXELt+tM8??Nt@%Q~2#SyA6t3CU_D z0wP%gk#;KtthY6q^#vO@S=R*I);wN$4W+ZMR5F9aCIO#;ra%^$$}hkC((dXeTs85%y|u7MP6J+#vFMBZ5AsjRiGw>~B8-tBtPuHe$)sEF+Sr zPsm)58o>-V8F48R|GKIcMF_#2*$|0j=VTIhir(lI01q-stBc(-;xj`vT2FZD-%V-y} zFR%$v5dGbefUdcfefI(}@5rhrnKUvDVw?^0?Dc{dHw{)H^ZESWDSOwbsvc`;Y1xN_ zAFLAhm1BK14NjofMbTvXH#efc|{AjdZUWX89sV-RY)fiQK*1>Se3GBKyv0DwX>8V~;(SudlCvNYk_? zks4rbZti2Mfo7=o`Khg~%}Ffdwijk0NYfw-?pk?;T1~N7Y!CECfVC4nNe6D~_3PJ< z)0^4rByq>#%A%1MEHWWj@#;dAAn?KUaA3F9v(G-e)3_=+K0e+%Jv}`Mi$*(k?AS*& z$&)UGz{r<8g%Sk5N43qZXQc}Z{C(=wsj&*m-8in5s06_qlzJ>dLP+2YBUzLIHEcF& zB=|j|1+Pe=(g@{YGZE7(vQoHSYF8RT4DxKOYD(PKoDcDs3=xo}tTckyNwbyB%$1TE zTEol-XpLHd-{}icRDvKL5bbOc_<6p#j!F=`0ffufnAU*DdP6Ki53M(t#3x|(-!_<; z1$GpbAcO}EJ?#EP*6}%P*6}%P*6}%kO==1U;w7vlx1Fe Rm!bdw002ovPDHLkV1kK>tmXg! literal 0 HcmV?d00001 diff --git a/static/images/hoc/r.png b/static/images/hoc/r.png new file mode 100644 index 0000000000000000000000000000000000000000..e7579f99f675665d14b2975208374aba5296d487 GIT binary patch literal 3000 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 literal 0 HcmV?d00001 diff --git a/static/images/hoc/splash-left.png b/static/images/hoc/splash-left.png new file mode 100644 index 0000000000000000000000000000000000000000..0c637c13f5bfad7a7ec44a8cc31c128caf721cfd GIT binary patch literal 11683 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 literal 0 HcmV?d00001 diff --git a/static/images/hoc/t.png b/static/images/hoc/t.png new file mode 100644 index 0000000000000000000000000000000000000000..1589eade14efdf5fd01555e750edc808968ca0bd GIT binary patch literal 4002 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 IM6N<$g12sI=l}o! literal 0 HcmV?d00001 diff --git a/static/svgs/modal/open-blue.svg b/static/svgs/modal/open-blue.svg new file mode 100644 index 000000000..96b6da932 --- /dev/null +++ b/static/svgs/modal/open-blue.svg @@ -0,0 +1,12 @@ + + + + open-blue + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/static/svgs/modal/open-white.svg b/static/svgs/modal/open-white.svg new file mode 100644 index 000000000..e916103ff --- /dev/null +++ b/static/svgs/modal/open-white.svg @@ -0,0 +1,12 @@ + + + + open-modal-icon + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/static/svgs/ttt/see-more.svg b/static/svgs/ttt/see-more.svg deleted file mode 100644 index ff6505931..000000000 --- a/static/svgs/ttt/see-more.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - From 58ddff4001a7dcc859fc1ce07528e859ca193893 Mon Sep 17 00:00:00 2001 From: Sheshank Shankar Date: Tue, 7 Nov 2017 13:42:53 -0800 Subject: [PATCH 06/25] Fix #1649: Edit Scratch Conf Website - Correct Info for Costa Rica Conf (#1650) * Adds fix to #1644 * Update l10n.json * Add files via upload * Add files via upload * Delete index.jsx * Delete index.scss * Delete l10n.json * Update index.jsx * Update l10n.json * Update l10n.json * Update index.jsx --- src/views/conference/2017/index/index.jsx | 11 ++--------- src/views/conference/2017/index/l10n.json | 1 - 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/views/conference/2017/index/index.jsx b/src/views/conference/2017/index/index.jsx index b1f0b3137..2717f2b9a 100644 --- a/src/views/conference/2017/index/index.jsx +++ b/src/views/conference/2017/index/index.jsx @@ -217,13 +217,6 @@ var ConferenceSplash = React.createClass({ - - {' - '} - - + +
diff --git a/src/views/conference/2017/index/l10n.json b/src/views/conference/2017/index/l10n.json index 1cf860137..0953d7854 100644 --- a/src/views/conference/2017/index/l10n.json +++ b/src/views/conference/2017/index/l10n.json @@ -8,7 +8,6 @@ "conference-2017.audience": "Audience", "conference-2017.language": "Language", "conference-2017.website": "Visit Website", - "conference-2017.contact": "Contact Organizer", "conference-2017.franceTitle": "Scratch2017BDX", "conference-2017.franceSubTitle": "Opening, Inspiring, Connecting", From 39c691588407fce02c82a0806843fdbf2c6c1d70 Mon Sep 17 00:00:00 2001 From: The_Grits Date: Tue, 7 Nov 2017 17:30:11 -0500 Subject: [PATCH 07/25] Fix gh-1634: Fix Localization on Developers Page (#1636) * Add ScratchJr Section Localization * Add Localization for ScratchJr and "Projects" * Update ScratchJr String / Remove ScratchJr Title * Readd "ScratchJr" --- src/views/developers/developers.jsx | 10 ++-------- src/views/developers/l10n.json | 1 + 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/views/developers/developers.jsx b/src/views/developers/developers.jsx index 86bf79290..7c9035a59 100644 --- a/src/views/developers/developers.jsx +++ b/src/views/developers/developers.jsx @@ -64,7 +64,7 @@ var Developers = React.createClass({
-

Projects

+

@@ -94,13 +94,7 @@ var Developers = React.createClass({

ScratchJr

- 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{' '} - ScratchJr website{' '} - or access the code and documentation{' '} - here. +

diff --git a/src/views/developers/l10n.json b/src/views/developers/l10n.json index 4986dfb33..64ed35e1b 100644 --- a/src/views/developers/l10n.json +++ b/src/views/developers/l10n.json @@ -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 here.", "developers.principlesIntro": "We created Scratch to empower young people to think creatively, reason systematically, and work collaboratively. We are guided by a set of Learning Principles and Design Principles 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 ScratchJr website or access the code and documentation on GitHub.", "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", From c143d6c7066a104ae15f2d9b16e4cb00f4d2cce5 Mon Sep 17 00:00:00 2001 From: Sheshank Shankar Date: Tue, 7 Nov 2017 14:31:52 -0800 Subject: [PATCH 08/25] Fixes #1645 and #1644 (#1646) * Fixes #1645 * Fixes #1644 as well * hopefully this should fix it * Update download.jsx * Update download.jsx --- src/views/download/download.jsx | 1 + src/views/download/l10n.json | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/views/download/download.jsx b/src/views/download/download.jsx index 2413a2227..e607512fa 100644 --- a/src/views/download/download.jsx +++ b/src/views/download/download.jsx @@ -231,6 +231,7 @@ var Download = injectIntl(React.createClass({

+

diff --git a/src/views/download/l10n.json b/src/views/download/l10n.json index bdb89fd39..48ecdd50c 100644 --- a/src/views/download/l10n.json +++ b/src/views/download/l10n.json @@ -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": "Note for Mac Users: the latest version of Scratch 2.0 Offline requires Adobe Air 20. To upgrade to Adobe Air 20 manually, go here.", + "download.introMac": "Note for Mac Users: the latest version of Scratch 2.0 Offline requires Adobe AIR 20. To upgrade to Adobe AIR 20 manually, go here.", "download.installation": "Installation", "download.airTitle": "Adobe AIR", "download.airBody": "If you don't already have it, download and install the latest Adobe AIR", @@ -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 Scratch 1.4.", "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 here.", "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 backpack 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." } From 60a2a51bb5494e4e66f5d26cb144273f56a3555f Mon Sep 17 00:00:00 2001 From: chrisgarrity Date: Wed, 8 Nov 2017 09:04:33 -0500 Subject: [PATCH 09/25] minor formatting changes for consistency --- src/components/ttt-tile/ttt-tile.scss | 6 +++--- src/views/splash/hoc/top-banner.jsx | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/ttt-tile/ttt-tile.scss b/src/components/ttt-tile/ttt-tile.scss index 2078777da..71bb685aa 100644 --- a/src/components/ttt-tile/ttt-tile.scss +++ b/src/components/ttt-tile/ttt-tile.scss @@ -113,7 +113,7 @@ .ttt-tile-open-modal { display: inline-block; padding: 0 .25rem; - width: 1.25em; - height: 1.25em; - vertical-align: top; + width: 1rem; + height: 1rem; + vertical-align: text-bottom; } diff --git a/src/views/splash/hoc/top-banner.jsx b/src/views/splash/hoc/top-banner.jsx index 5d2dccfa6..3c55076a2 100644 --- a/src/views/splash/hoc/top-banner.jsx +++ b/src/views/splash/hoc/top-banner.jsx @@ -49,37 +49,37 @@ var TopBanner = injectIntl(React.createClass({ C C R A T C H From ef4f851e6d93e1192d3059f71ebd0917c059ef05 Mon Sep 17 00:00:00 2001 From: chrisgarrity Date: Wed, 8 Nov 2017 16:50:14 -0500 Subject: [PATCH 10/25] Switch CTA button based on logged in state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Invite logged-out users to ‘Get Started with Coding!’, and make the whole Scratch animation clickable (goes to the Animate a Name tutorial). See #1664 --- src/views/splash/hoc/middle-banner.jsx | 4 +- src/views/splash/hoc/top-banner.jsx | 85 ++++++++++++++------------ src/views/splash/hoc/top-banner.scss | 1 + src/views/splash/l10n.json | 5 +- src/views/splash/presentation.jsx | 3 +- 5 files changed, 54 insertions(+), 44 deletions(-) diff --git a/src/views/splash/hoc/middle-banner.jsx b/src/views/splash/hoc/middle-banner.jsx index 31ce74fd1..718220075 100644 --- a/src/views/splash/hoc/middle-banner.jsx +++ b/src/views/splash/hoc/middle-banner.jsx @@ -90,10 +90,10 @@ var MiddleBanner = injectIntl(React.createClass({

- +

- +
diff --git a/src/views/splash/hoc/top-banner.jsx b/src/views/splash/hoc/top-banner.jsx index 3c55076a2..c82ce4b4f 100644 --- a/src/views/splash/hoc/top-banner.jsx +++ b/src/views/splash/hoc/top-banner.jsx @@ -21,6 +21,9 @@ var nameTile = { var TopBanner = injectIntl(React.createClass({ type: 'TopBanner', + propTypes: { + loggedIn: React.PropTypes.bool.isRequired + }, getInitialState: function () { // use translated tile var formatMessage = this.props.intl.formatMessage; @@ -46,47 +49,51 @@ var TopBanner = injectIntl(React.createClass({ return ( - - - - - - - - - - + + + + + + + + + + +
- + { this.props.loggedIn ? + : + + }
   diff --git a/src/views/splash/hoc/top-banner.scss b/src/views/splash/hoc/top-banner.scss index 00ddcfb37..9df878291 100644 --- a/src/views/splash/hoc/top-banner.scss +++ b/src/views/splash/hoc/top-banner.scss @@ -31,6 +31,7 @@ } .top-animation { + margin: auto; padding-top: 2rem; padding-bottom: 1rem; width: 70%; diff --git a/src/views/splash/l10n.json b/src/views/splash/l10n.json index 4a87ddc13..6956d38b0 100644 --- a/src/views/splash/l10n.json +++ b/src/views/splash/l10n.json @@ -29,8 +29,9 @@ "teacherbanner.classesButton": "My Classes", "teacherbanner.faqButton": "Teacher Account FAQ", - "middle-banner.header": "Get Creative with Coding", - "middle-banner.ttt": "See more activities", + "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.", diff --git a/src/views/splash/presentation.jsx b/src/views/splash/presentation.jsx index e0827551d..8e699bc6c 100644 --- a/src/views/splash/presentation.jsx +++ b/src/views/splash/presentation.jsx @@ -255,7 +255,8 @@ var SplashPresentation = injectIntl(React.createClass({ {this.props.isEducator ? [ ] : []} - +
{this.props.sessionStatus === sessionActions.Status.FETCHED ? ( Object.keys(this.props.user).length !== 0 ? [ From f2f51453bf5c8c5d7830a8902b6a7f3e6aa1a219 Mon Sep 17 00:00:00 2001 From: Kerr <26391179+VutonDesign@users.noreply.github.com> Date: Tue, 14 Nov 2017 17:48:47 +0000 Subject: [PATCH 11/25] Update l10n.json --- src/views/developers/l10n.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/developers/l10n.json b/src/views/developers/l10n.json index 64ed35e1b..13c2cbc89 100644 --- a/src/views/developers/l10n.json +++ b/src/views/developers/l10n.json @@ -38,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 Lifelong Kindergarten Group at the MIT Media Lab. You can learn more about Scratch here.", + "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 Lifelong Kindergarten Group at the MIT Media Lab. You can learn more about Scratch here.", "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?", From 8b61ce72e1455377ce31377b8d51d426b11ba8d9 Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Wed, 15 Nov 2017 13:33:30 -0500 Subject: [PATCH 12/25] Scratch itch --- test/integration/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/package.json b/test/integration/package.json index fa26009a5..06fc0f859 100644 --- a/test/integration/package.json +++ b/test/integration/package.json @@ -1,5 +1,5 @@ { - "testDependencies": { + "devDependencies": { "selenium-webdriver": "2.45.0", "chromedriver": "2.33.0" } From 2ba03708c39f3fd97fa1b125aee853094a1bba4c Mon Sep 17 00:00:00 2001 From: Sheshank Shankar Date: Mon, 20 Nov 2017 06:12:12 -0800 Subject: [PATCH 13/25] Fixes #1633 - Scratch Conference 2018 Website Phase 1 (#1673) * add 2018 conference page (new subdirectory and index page) * Redirect /conference route to 2018, and add new route for /conference/2017 for last year's conferences. * Update mailto link in the footer --- .../footer/conference/2018/footer.jsx | 84 +++++++++ .../navigation/conference/2018/navigation.jsx | 29 ++++ .../conference/2018/navigation.scss | 39 +++++ src/components/page/conference/2018/page.jsx | 27 +++ src/routes.json | 10 +- src/views/conference/2018/index/index.jsx | 123 +++++++++++++ src/views/conference/2018/index/index.scss | 161 ++++++++++++++++++ src/views/conference/2018/index/l10n.json | 28 +++ 8 files changed, 500 insertions(+), 1 deletion(-) create mode 100644 src/components/footer/conference/2018/footer.jsx create mode 100644 src/components/navigation/conference/2018/navigation.jsx create mode 100644 src/components/navigation/conference/2018/navigation.scss create mode 100644 src/components/page/conference/2018/page.jsx create mode 100644 src/views/conference/2018/index/index.jsx create mode 100644 src/views/conference/2018/index/index.scss create mode 100644 src/views/conference/2018/index/l10n.json diff --git a/src/components/footer/conference/2018/footer.jsx b/src/components/footer/conference/2018/footer.jsx new file mode 100644 index 000000000..06daa2e92 --- /dev/null +++ b/src/components/footer/conference/2018/footer.jsx @@ -0,0 +1,84 @@ +var React = require('react'); +var ReactIntl = require('react-intl'); + +var injectIntl = ReactIntl.injectIntl; +var FormattedMessage = ReactIntl.FormattedMessage; + +var FlexRow = require('../../../flex-row/flex-row.jsx'); +var FooterBox = require('../../container/footer.jsx'); +var LanguageChooser = require('../../../languagechooser/languagechooser.jsx'); + +require('../footer.scss'); + +var ConferenceFooter = React.createClass({ + type: 'ConferenceFooter', + render: function () { + return ( + + +
+

+ + +
  • + Scratch +
  • +
  • + ScratchJr +
  • +
    + +
  • + Scratch Foundation +
  • +
  • + ScratchEd +
  • +
    + +
  • + Scratch Day +
  • +
    +
    +

    + +

    +
    +
    +
    +

    Contact

    +

    + + Email Us + +

    +
    +
    + +
  • + + scratch twitter + +
  • +
  • + + scratch facebook + +
  • +
  • + + scratch foundation blog + +
  • +
    +
    +
    +
    + +
    + ); + } +}); + +module.exports = injectIntl(ConferenceFooter); diff --git a/src/components/navigation/conference/2018/navigation.jsx b/src/components/navigation/conference/2018/navigation.jsx new file mode 100644 index 000000000..97a820b0b --- /dev/null +++ b/src/components/navigation/conference/2018/navigation.jsx @@ -0,0 +1,29 @@ +var React = require('react'); + +var NavigationBox = require('../../base/navigation.jsx'); + +require('./navigation.scss'); + +var Navigation = React.createClass({ + type: 'Navigation', + render: function () { + return ( + + + + ); + } +}); + +module.exports = Navigation; diff --git a/src/components/navigation/conference/2018/navigation.scss b/src/components/navigation/conference/2018/navigation.scss new file mode 100644 index 000000000..3ea2c1799 --- /dev/null +++ b/src/components/navigation/conference/2018/navigation.scss @@ -0,0 +1,39 @@ +@import "../../../../colors"; +@import "../../../../frameless"; + +#navigation { + .ul.mod-2018 { + display: flex; + justify-content: space-between; + flex-flow: row nowrap; + align-items: center; + list-style-type: none; + } + + .li-left.mod-2018 { + margin-top: 0; + margin-right: 10px; + color: $type-white; + } + + .logo-a { + display: flex; + height: 100%; + align-items: center; + } + + .logo-a-image { + margin-right: 10px; + border-right: 2px solid $active-gray; + padding-right: 10px; + width: 80px; + } + + .logo-a-title { + text-decoration: none; + white-space: nowrap; + color: $type-white; + font-size: .85rem; + font-weight: bold; + } +} diff --git a/src/components/page/conference/2018/page.jsx b/src/components/page/conference/2018/page.jsx new file mode 100644 index 000000000..e8dd6e6f8 --- /dev/null +++ b/src/components/page/conference/2018/page.jsx @@ -0,0 +1,27 @@ +var React = require('react'); + +var Navigation = require('../../../navigation/conference/2018/navigation.jsx'); +var Footer = require('../../../footer/conference/2018/footer.jsx'); + +require('../page.scss'); + +var Page = React.createClass({ + type: 'Page', + render: function () { + return ( +
    + +
    + {this.props.children} +
    + +
    + ); + } +}); + +module.exports = Page; diff --git a/src/routes.json b/src/routes.json index 8a8bdcbb2..b68120d45 100644 --- a/src/routes.json +++ b/src/routes.json @@ -44,7 +44,15 @@ { "name": "conference-index", "pattern": "^/conference/?$", - "routeAlias": "/conference(?!/201[4-5])", + "routeAlias": "/conference(?!/201[4-7])", + "view": "conference/2018/index/index", + "title": "Scratch Conference", + "viewportWidth": "device-width" + }, + { + "name": "conference-index-2017", + "pattern": "^/conference/2017/?$", + "routeAlias": "/conference(?!/201[4-6])", "view": "conference/2017/index/index", "title": "Scratch Conference", "viewportWidth": "device-width" diff --git a/src/views/conference/2018/index/index.jsx b/src/views/conference/2018/index/index.jsx new file mode 100644 index 000000000..554bcaa9a --- /dev/null +++ b/src/views/conference/2018/index/index.jsx @@ -0,0 +1,123 @@ +var FormattedDate = require('react-intl').FormattedDate; +var FormattedMessage = require('react-intl').FormattedMessage; +var FormattedHTMLMessage = require('react-intl').FormattedHTMLMessage; +var React = require('react'); +var render = require('../../../../lib/render.jsx'); + +var FlexRow = require('../../../../components/flex-row/flex-row.jsx'); +var Page = require('../../../../components/page/conference/2018/page.jsx'); +var TitleBanner = require('../../../../components/title-banner/title-banner.jsx'); + +require('../../../../components/forms/button.scss'); +require('./index.scss'); + +var ConferenceSplash = React.createClass({ + type: 'ConferenceSplash', + + render: function () { + return ( +
    + +
    +

    +
    + +
    +

    +

    + +

    +
    +
    +
    +

    + +

    + + + + + + + + + + + + + +
    + Calendar Icon + + + {' - '} + + +
    + Map Icon +
    +
    +
    +

    + +

    +

    +

    + +

    +

    + +

    +

    + +

    +

    + +

    +

    + + + +
    +
    + +
    +

    +
    +
    +

    + +
    + +

    +
    +
    +

    + +

    +
    +
    +
    + ); + } +}); + +render(, document.getElementById('app')); diff --git a/src/views/conference/2018/index/index.scss b/src/views/conference/2018/index/index.scss new file mode 100644 index 000000000..23cfe7b73 --- /dev/null +++ b/src/views/conference/2018/index/index.scss @@ -0,0 +1,161 @@ +@import "../../../../colors"; +@import "../../../../frameless"; + +.title-banner.mod-conference.mod-2018 { + padding-top: 0; +} + +.title-banner-image.mod-2018 { + opacity: .75; + margin-bottom: 1.75rem; + background-image: url("/images/conference/index/2017/title-banner.jpg"); + background-position: center; + background-size: cover; + width: 100%; + height: 20rem; +} + +.conf2018-panel, +.title-banner-h3.mod-2018 { + margin: auto; + width: 48.75rem; +} + +.title-banner-h3.mod-2018 { + text-align: center; + color: $type-white; +} + +.conf2018-panel { + border-bottom: 1px solid $ui-border; +} + +.conf2018-panel.mod-last { + border-bottom: 0; +} + +.flex-row.conf2018-panel-title { + justify-content: flex-start; + align-items: center; +} + +.conf2018-panel-desc { + margin: 2rem 0; +} + +td { + padding: .75rem 1.25rem; + vertical-align: top; +} + +.conf2018-panel-row-icon-image { + margin-top: .125rem; + width: 1rem; + height: 1rem; +} + +.button.mod-2018-panel { + display: block; + margin: 2rem auto 0; + background-color: $ui-orange; + padding: 1rem 0; + width: 13.75rem; + text-align: center; + color: $type-white; +} + +@media only screen and (max-width: $mobile - 1) { + .index.mod-2018 { + text-align: left; + } + + .title-banner-image.mod-2018 { + height: 10rem; + } + + .title-banner-h1.mod-2018 { + line-height: 1.25em; + } + + .conf2018-panel, + .title-banner-h3.mod-2018 { + width: initial; + } + + .conf2018-panel { + margin: auto .5rem; + } + + .title-banner-h3.mod-2018 { + margin: 1rem .5rem .5rem; + font-size: 1.1rem; + } + + .flex-row.conf2018-panel-title { + flex-direction: row; + } + + .conf2018-panel-title-text { + max-width: 14rem; + } + + .conf2018-panel-row > td { + padding: .75rem .375rem .75rem 0; + } +} + +@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { + .index.mod-2018 { + text-align: left; + } + + .title-banner-image.mod-2018 { + height: 10rem; + } + + .conf2018-panel, + .title-banner-h3.mod-2018 { + margin: auto .5rem ; + width: initial; + } + + .title-banner-h3.mod-2018 { + font-size: 1.1rem; + } + + .flex-row.conf2018-panel-title { + flex-direction: row; + } + + .conf2018-panel-title-text { + max-width: 18.75rem; + } + + .button.mod-2018-panel { + width: 5.75rem; + } +} + +@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { + .index.mod-2018 { + text-align: left; + } + + .title-banner-image.mod-2018 { + height: 15rem; + } + + .conf2018-panel, + .title-banner-h3.mod-2018 { + margin: auto; + width: 38.75rem; + } + + .title-banner-h3.mod-2018 { + font-size: 1.1rem; + } + + .button.mod-2018-panel { + width: 8.75rem; + } +} diff --git a/src/views/conference/2018/index/l10n.json b/src/views/conference/2018/index/l10n.json new file mode 100644 index 000000000..fcf03baf8 --- /dev/null +++ b/src/views/conference/2018/index/l10n.json @@ -0,0 +1,28 @@ +{ + "conference-2018.title": "Scratch Conference 2018
    The Next Generation", + "conference-2018.dateDesc": "July 26-28, 2018 | Cambridge, MA, USA", + "conference-2018.dateDescMore": " (with opening reception the evening of July 25)", + "conference-2018.locationDetails": "MIT Media Lab, Cambridge, MA", + "conference-2018.seeBelow": "Learn more about conference dates and locations below.", + + "conference-2018.date": "When:", + "conference-2018.location": "Where:", + + "conference-2018.desc": "Join us for the Scratch@MIT conference, a playful gathering of educators, researchers, developers, and other members of the worldwide Scratch community.

    We're planning a very participatory conference, with an entire day of hands-on workshops and lots of opportunities for peer-to-peer discussion and collaboration.", + + "conference-2018.sessionDesc": "Interested in offering a session? We invite four types of proposals:", + "conference-2018.sessionItem1": "Poster/demonstration (90 minutes). Show off your project in an exhibition setting, alongside other presenters. You will be provided with display space for a poster and table space for a computer or handouts.", + "conference-2018.sessionItem2": "Hands-on workshop (90 minutes). Engage participants in hands-on activities, highlighting new ways of creating and collaborating with Scratch.", + "conference-2018.sessionItem3": "Interactive panel (60 minutes). Discuss a Scratch-related topic in a panel with three or more people. Your proposal should describe how you will engage the audience during the session.", + "conference-2018.sessionItem4": "Ignite talk (5 minutes). Share what you've been doing in a short, lively presentation.", + + "conference-2018.proposal": " Submit Your Proposal", + "conference-2018.proposalDeadline": "Deadline for proposals: February 5", + "conference-2018.proposalAccept": "Notification of acceptance: March 1", + + "conference-2018.registrationTitle": "Registration:", + "conference-2018.registrationEarly": "Early Bird Registration (March 1-May 1): $200", + "conference-2018.registrationStandard": "Standard Registration (after May 1): $300", + + "conference-2018.questions": "Questions? Contact the Scratch Team at conference@scratch.mit.edu" +} From 4ae8edd1934220d356f4d0236e19325d3a7ca2d7 Mon Sep 17 00:00:00 2001 From: Sheshank Shankar Date: Mon, 20 Nov 2017 06:13:11 -0800 Subject: [PATCH 14/25] Add Learning Resource designer (#1676) --- src/views/jobs/jobs.jsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/views/jobs/jobs.jsx b/src/views/jobs/jobs.jsx index 7fa923f88..9a00774f1 100644 --- a/src/views/jobs/jobs.jsx +++ b/src/views/jobs/jobs.jsx @@ -54,6 +54,14 @@ var Jobs = React.createClass({ MIT Media Lab, Cambridge, MA +
  • + + Learning Resource Designer + + + MIT Media Lab, Cambridge, MA + +
  • From 3e52bc7fc520079fde91bef0c860de19904e5b4a Mon Sep 17 00:00:00 2001 From: Sheshank Shankar Date: Tue, 21 Nov 2017 16:51:11 -0800 Subject: [PATCH 15/25] Make Conference logo button link to homepage. --- src/components/navigation/conference/2018/navigation.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/navigation/conference/2018/navigation.jsx b/src/components/navigation/conference/2018/navigation.jsx index 97a820b0b..eb4b39de8 100644 --- a/src/components/navigation/conference/2018/navigation.jsx +++ b/src/components/navigation/conference/2018/navigation.jsx @@ -11,7 +11,7 @@ var Navigation = React.createClass({
    • - + Scratch Logo Date: Tue, 28 Nov 2017 14:01:55 -0500 Subject: [PATCH 16/25] Change avatar to use `uploads` endpoint --- src/components/grid/grid.jsx | 53 ++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/src/components/grid/grid.jsx b/src/components/grid/grid.jsx index 9100cacbc..c7faea169 100644 --- a/src/components/grid/grid.jsx +++ b/src/components/grid/grid.jsx @@ -32,33 +32,40 @@ var Grid = React.createClass({ if (this.props.itemType == 'projects') { return ( - + ); } else { return ( - + ); } }.bind(this))} From 02da6f62a66b68253e11f54931b4b755b3e15c6f Mon Sep 17 00:00:00 2001 From: Matthew Taylor Date: Wed, 29 Nov 2017 08:03:48 -0500 Subject: [PATCH 17/25] fall back to default image if thumb not found This incorporates a fallback image into the thumbnail component for all of project, studio and avatar thumbnails using the `onerror` property of img elements (which is supported across all browsers). /cc @thisandagain @rschamp --- src/components/grid/grid.jsx | 1 + src/components/thumbnail/thumbnail.jsx | 92 ++++++++++++++++++++++---- 2 files changed, 80 insertions(+), 13 deletions(-) diff --git a/src/components/grid/grid.jsx b/src/components/grid/grid.jsx index c7faea169..f130e5bbd 100644 --- a/src/components/grid/grid.jsx +++ b/src/components/grid/grid.jsx @@ -64,6 +64,7 @@ var Grid = React.createClass({ href={href} title={item.title} src={item.image} + srcDefault={'https://uploads.scratch.mit.edu/galleries/thumbnails/default.png'} owner={item.owner} /> ); diff --git a/src/components/thumbnail/thumbnail.jsx b/src/components/thumbnail/thumbnail.jsx index 391c4343c..314061ba0 100644 --- a/src/components/thumbnail/thumbnail.jsx +++ b/src/components/thumbnail/thumbnail.jsx @@ -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({
      + title={this.props.remixes + ' remixes'} + > {this.props.remixes}
      ); @@ -68,19 +83,48 @@ var Thumbnail = React.createClass({
      + title={this.props.views + ' views'} + > {this.props.views}
      ); } - var imgElement,titleElement,avatarElement; + + var imgElement, titleElement, avatarElement; if (this.props.linkTitle) { - imgElement =
      - {this.props.alt} - ; - titleElement = - {this.props.title} - ; + if (this.state.srcFallback) { + imgElement = ( + + {this.props.alt} + + ); + } else { + imgElement = ( + + {this.props.alt} + + ); + } + titleElement = ( + + {this.props.title} + + ); } else { imgElement = ; titleElement = this.props.title; @@ -97,10 +141,32 @@ var Thumbnail = React.createClass({ } if (this.props.avatar && this.props.showAvatar) { - avatarElement = - - {this.props.creator} - ; + if (this.state.avatarFallback) { + avatarElement = ( + + {this.props.creator} + + ); + } else { + avatarElement = ( + + {this.props.creator} + + ); + } } return (
      From 8722a1d374d4f80b548e02d668b2ae14a4db2680 Mon Sep 17 00:00:00 2001 From: Kerr Date: Wed, 29 Nov 2017 22:12:24 +0000 Subject: [PATCH 18/25] Update jobs.jsx --- src/views/jobs/jobs.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/jobs/jobs.jsx b/src/views/jobs/jobs.jsx index 9a00774f1..ff1a6f681 100644 --- a/src/views/jobs/jobs.jsx +++ b/src/views/jobs/jobs.jsx @@ -31,8 +31,8 @@ var Jobs = React.createClass({

      • - - Junior Designer + + Designer MIT Media Lab, Cambridge, MA From 9efd8e65ebce716d8d7397d423b304e10556afaa Mon Sep 17 00:00:00 2001 From: Colby Gutierrez-Kraybill Date: Wed, 29 Nov 2017 10:39:28 -0500 Subject: [PATCH 19/25] Add specific /projects cache handling when user is not logged-in --- bin/lib/fastly-config-methods.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bin/lib/fastly-config-methods.js b/bin/lib/fastly-config-methods.js index d84756262..9ba7c66dc 100644 --- a/bin/lib/fastly-config-methods.js +++ b/bin/lib/fastly-config-methods.js @@ -119,7 +119,9 @@ var FastlyConfigMethods = { ' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For;\n' + ' }\n' + ' set req.grace = 60s;\n' + - ' return(pass);\n' + + ' if (!req.url ~ "^/projects/" && req.http.Cookie:scratchsessionid) {\n' + + ' return(pass);\n' + + ' }\n' + '}\n'; }, @@ -132,9 +134,11 @@ var FastlyConfigMethods = { setResponseTTL: function (condition) { return '' + 'if (' + condition + ') {\n' + - ' set beresp.ttl = 0s;\n' + - ' set beresp.grace = 0s;\n' + - ' return(pass);\n' + + ' if (!req.url ~ "^/projects/" && req.http.Cookie:scratchsessionid) {\n' + + ' set beresp.ttl = 0s;\n' + + ' set beresp.grace = 0s;\n' + + ' return(pass);\n' + + ' }\n'; '}\n'; } }; From 7a6be0306b1b811a8693d1dc5e6ff7a36c9c827b Mon Sep 17 00:00:00 2001 From: Colby Gutierrez-Kraybill Date: Wed, 29 Nov 2017 23:24:43 -0500 Subject: [PATCH 20/25] Cache ^/projects/ to users who are not logged-in --- bin/lib/fastly-config-methods.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bin/lib/fastly-config-methods.js b/bin/lib/fastly-config-methods.js index 9ba7c66dc..9bfe46486 100644 --- a/bin/lib/fastly-config-methods.js +++ b/bin/lib/fastly-config-methods.js @@ -119,7 +119,9 @@ var FastlyConfigMethods = { ' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For;\n' + ' }\n' + ' set req.grace = 60s;\n' + - ' if (!req.url ~ "^/projects/" && req.http.Cookie:scratchsessionid) {\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'; @@ -134,7 +136,9 @@ var FastlyConfigMethods = { setResponseTTL: function (condition) { return '' + 'if (' + condition + ') {\n' + - ' if (!req.url ~ "^/projects/" && req.http.Cookie:scratchsessionid) {\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' + From 279e25eb69d40f9c393cc3b61a4214203ad5e17b Mon Sep 17 00:00:00 2001 From: Colby Gutierrez-Kraybill Date: Thu, 30 Nov 2017 10:57:48 -0500 Subject: [PATCH 21/25] Simplify how the conditional is created for setting the S3 backend, caching related behavior for non-logged in users and maintaining language settings and passing to backend --- bin/configure-fastly.js | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/bin/configure-fastly.js b/bin/configure-fastly.js index 1065ba8e1..e83652e31 100644 --- a/bin/configure-fastly.js +++ b/bin/configure-fastly.js @@ -1,6 +1,7 @@ var async = require('async'); var defaults = require('lodash.defaults'); var fastlyConfig = require('./lib/fastly-config-methods'); +const languages = require('./languages.json'); var route_json = require('../src/routes.json'); @@ -46,18 +47,42 @@ async.auto({ 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 ); }], From 66edcb60a89efe528afeb06b8f9fef50bfc4e997 Mon Sep 17 00:00:00 2001 From: Colby Gutierrez-Kraybill Date: Thu, 30 Nov 2017 11:28:53 -0500 Subject: [PATCH 22/25] Remove unused vcl code generators, setBackend and setFrontend --- bin/lib/fastly-config-methods.js | 43 -------------------------------- 1 file changed, 43 deletions(-) diff --git a/bin/lib/fastly-config-methods.js b/bin/lib/fastly-config-methods.js index 9bfe46486..1969ecfee 100644 --- a/bin/lib/fastly-config-methods.js +++ b/bin/lib/fastly-config-methods.js @@ -84,49 +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' + - ' 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'; - }, - /** * Returns custom vcl configuration as a string that sets the varnish * Time to Live (TTL) for responses that come from s3. From 785667691e9dcdea85723aaf144a6576aff67c0d Mon Sep 17 00:00:00 2001 From: Colby Gutierrez-Kraybill Date: Thu, 30 Nov 2017 11:30:45 -0500 Subject: [PATCH 23/25] Fix up lint errors --- bin/configure-fastly.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/bin/configure-fastly.js b/bin/configure-fastly.js index e83652e31..4f698fedf 100644 --- a/bin/configure-fastly.js +++ b/bin/configure-fastly.js @@ -42,10 +42,6 @@ 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 recvCondition = '' + 'if (' + notPassStatement + ') {\n' + @@ -63,13 +59,13 @@ async.auto({ ' }\n' + ' set req.grace = 60s;\n' + ' if (req.http.Cookie:scratchlanguage) {\n' + - ' set req.http.Accept-Language = 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(':') + '", ' + + Object.keys(languages).join(':') + '", ' + '"en", ' + 'std.tolower(req.http.Accept-Language)' + - ');\n' + + ');\n' + ' }\n' + ' if (req.url ~ "^/projects/" && !req.http.Cookie:scratchsessionid) {\n' + ' set req.http.Cookie = req.http.Cookie:scratchlanguage;\n' + From 1e8bf2653c4bea16f14e6561e2678159fc172b82 Mon Sep 17 00:00:00 2001 From: Colby Gutierrez-Kraybill Date: Thu, 30 Nov 2017 11:38:14 -0500 Subject: [PATCH 24/25] Correct brace addition to VCL --- bin/lib/fastly-config-methods.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/lib/fastly-config-methods.js b/bin/lib/fastly-config-methods.js index 1969ecfee..8ef064715 100644 --- a/bin/lib/fastly-config-methods.js +++ b/bin/lib/fastly-config-methods.js @@ -99,7 +99,7 @@ var FastlyConfigMethods = { ' set beresp.ttl = 0s;\n' + ' set beresp.grace = 0s;\n' + ' return(pass);\n' + - ' }\n'; + ' }\n' + '}\n'; } }; From 3e8fdc9ce21f088a001dd20562cb0ed4ca7ee502 Mon Sep 17 00:00:00 2001 From: Colby Gutierrez-Kraybill Date: Thu, 30 Nov 2017 11:46:39 -0500 Subject: [PATCH 25/25] adjust tests to changes in fastly-config-methods.js --- test/unit/test_fastly_config_methods.js | 41 +++++-------------------- 1 file changed, 7 insertions(+), 34 deletions(-) diff --git a/test/unit/test_fastly_config_methods.js b/test/unit/test_fastly_config_methods.js index 229d07043..a2880006f 100644 --- a/test/unit/test_fastly_config_methods.js +++ b/test/unit/test_fastly_config_methods.js @@ -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' + - ' set beresp.ttl = 0s;\n' + - ' set beresp.grace = 0s;\n' + - ' return(pass);\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();