Merge branch 'develop' into hotfix/fix-adventure-game

This commit is contained in:
Ray Schamp 2018-12-03 13:28:27 -05:00 committed by GitHub
commit 7d816ac5e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
93 changed files with 3820 additions and 1416 deletions

View file

@ -43,6 +43,11 @@ env:
- PROJECT_HOST_VAR=PROJECT_HOST_$TRAVIS_BRANCH
- PROJECT_HOST=${!PROJECT_HOST_VAR}
- PROJECT_HOST=${PROJECT_HOST:-$PROJECT_HOST_STAGING}
- STATIC_HOST_master=https://cdn2.scratch.mit.edu
- STATIC_HOST_STAGING=https://cdn.scratch.ly
- STATIC_HOST_VAR=STATIC_HOST_$TRAVIS_BRANCH
- STATIC_HOST=${!STATIC_HOST_VAR}
- STATIC_HOST=${STATIC_HOST:-$STATIC_HOST_STAGING}
- PATH=$PATH:$PWD/test/integration/node_modules/chromedriver/bin
- AWS_ACCESS_KEY_ID=$EB_AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY=$EB_AWS_SECRET_ACCESS_KEY

View file

@ -172,3 +172,13 @@ type = KEYVALUEJSON
source_file = src/views/wedo2-legacy/l10n.json
source_lang = en
type = KEYVALUEJSON
[scratch-website.parents-l10njson]
source_file = src/views/parents/l10n.json
source_lang = en
type = KEYVALUEJSON
[scratch-website.scratch_14-l10njson]
source_file = src/views/scratch_1.4/l10n.json
source_lang = en
type = KEYVALUEJSON

View file

@ -25,6 +25,7 @@
"homepage": "https://github.com/llk/scratch-www#readme",
"dependencies": {
"bunyan": "1.7.1",
"clipboard-copy": "2.0.1",
"compression": "1.6.1",
"express": "4.16.1",
"express-http-proxy": "1.1.0",
@ -47,6 +48,7 @@
"babel-plugin-transform-object-rest-spread": "6.26.0",
"babel-preset-es2015": "6.22.0",
"babel-preset-react": "6.22.0",
"bowser": "1.9.4",
"cheerio": "1.0.0-rc.2",
"classnames": "2.2.5",
"cookie": "0.2.2",

View file

@ -5,9 +5,14 @@ $ui-blue-10percent: hsla(215, 100, 65, .1);
$ui-blue-25percent: hsla(215, 100, 65, .25);
$ui-orange: hsla(38, 100, 55, 1); // #FFAB19 Control Primary
$ui-orange-high-contrast: hsla(30, 100, 55, 1); // #FFAB19 Control Primary
$ui-orange-10percent: hsla(35, 90, 55, .1);
$ui-orange-25percent: hsla(35, 90, 55, .25);
$ui-red: hsla(20, 100%, 55%, 1); /* #FF661A */
$ui-red-25percent: hsla(20, 100%, 55%, .25);
$ui-green-35percent: rgba(126, 225, 195, .35);
$ui-light-gray: hsla(0, 0, 98, 1); //#FAFAFA
$ui-gray: hsla(0, 0, 95, 1); //#F2F2F2
$ui-dark-gray: hsla(0, 0, 70, 1); //#B3B3B3

View file

@ -158,17 +158,40 @@
align-items: flex-start;
}
.step-image.badge {
height: initial;
.downloads-container {
text-align: center;
}
.download-button {
display: flex;
align-items: center;
.store-badge {
display: block;
width: 150px;
height: 80px;
}
img {
margin-left: .5rem;
}
.horizontal-divider {
display: block;
margin: 20px;
}
.horizontal-divider:before,
.horizontal-divider:after {
display: inline-block;
position: relative;
background-color: $ui-dark-gray;
width: 50%;
height: 1px;
vertical-align: middle;
content: "";
}
.horizontal-divider:before {
right: .5em;
margin-left: -50%;
}
.horizontal-divider:after {
left: .5em;
margin-right: -50%;
}
}

View file

@ -24,23 +24,34 @@ const InstallScratchLink = ({
<span className="step-description">
<FormattedMessage id="installScratchLink.downloadAndInstall" />
</span>
<a
className="step-image badge"
href={`https://downloads.scratch.mit.edu/link/${
currentOS === OS_ENUM.WINDOWS ? 'windows' : 'mac'
}.zip`}
>
<button className="button download-button">
{currentOS === OS_ENUM.WINDOWS ?
<FormattedMessage id="installScratchLink.windowsDownload" /> :
<FormattedMessage id="installScratchLink.macosDownload" />
<div className="downloads-container">
<a
href={
currentOS === OS_ENUM.WINDOWS ?
'https://www.microsoft.com/store/productId/9N48XLLCZH0X' :
'https://itunes.apple.com/us/app/scratch-link/id1408863490'
}
target="_blank"
>
<img
alt=""
src="/svgs/extensions/download-white.svg"
className="store-badge"
src={`/images/scratchlink/${
currentOS === OS_ENUM.WINDOWS ? 'windows' : 'mac'
}-store-badge.svg`}
/>
</button>
</a>
</a>
<span className="horizontal-divider">
<FormattedMessage id="installScratchLink.or" />
</span>
<a
href={`https://downloads.scratch.mit.edu/link/${
currentOS === OS_ENUM.WINDOWS ? 'windows' : 'mac'
}.zip`}
>
<FormattedMessage id="installScratchLink.directDownload" />
</a>
</div>
</Step>
</div>

View file

@ -80,7 +80,7 @@ const ConferenceFooter = () => (
<a href="https://scratch.mit.edu">Scratch</a>
</li>
<li>
<a href="http://www.scratchjr.org/">ScratchJr</a>
<a href="https://www.scratchjr.org/">ScratchJr</a>
</li>
</FlexRow>
<FlexRow

View file

@ -23,7 +23,7 @@ const ConferenceFooter = props => (
<a href="https://scratch.mit.edu">Scratch</a>
</li>
<li>
<a href="http://www.scratchjr.org/">ScratchJr</a>
<a href="https://www.scratchjr.org/">ScratchJr</a>
</li>
</FlexRow>
<FlexRow

View file

@ -99,7 +99,7 @@ const ConferenceFooter = props => (
</li>
<li>
<a
href="http://www.scratchjr.org/"
href="https://www.scratchjr.org/"
rel="noopener noreferrer"
target="_blank"
>

View file

@ -186,7 +186,7 @@ const Footer = props => (
</a>
</dd>
<dd>
<a href="http://www.scratchjr.org/">
<a href="https://www.scratchjr.org/">
<FormattedMessage id="general.scratchJr" />
</a>
</dd>

View file

@ -4,6 +4,7 @@ const React = require('react');
const Thumbnail = require('../thumbnail/thumbnail.jsx');
const FlexRow = require('../flex-row/flex-row.jsx');
const thumbnailUrl = require('../../lib/user-thumbnail');
require('./grid.scss');
@ -15,7 +16,7 @@ const Grid = props => (
if (props.itemType === 'projects') {
return (
<Thumbnail
avatar={`https://cdn2.scratch.mit.edu/get_image/user/${item.author.id}_32x32.png`}
avatar={thumbnailUrl(item.author.id)}
creator={item.author.username}
favorites={item.stats.favorites}
href={href}

View file

@ -39,7 +39,7 @@ class Intro extends React.Component {
<div className="sprites">
<a
className="sprite sprite-1"
href="/projects/editor/?tip_bar=getStarted"
href="/projects/editor/?tutorial=getStarted"
>
<img
alt="Scratch Cat"

View file

@ -81,7 +81,7 @@ class Navigation extends React.Component {
window.location.href = `/search/projects?q=${encodeURIComponent(formData.q)}`;
}
render () {
const createLink = this.props.user ? '/projects/editor/' : '/projects/editor/?tip_bar=home';
const createLink = this.props.user ? '/projects/editor/' : '/projects/editor/?tutorial=getStarted';
return (
<NavigationBox
className={classNames({

View file

@ -0,0 +1,24 @@
const React = require('react');
const FormattedMessage = require('react-intl').FormattedMessage;
const FlexRow = require('../../components/flex-row/flex-row.jsx');
require('./not-available.scss');
const ProjectNotAvailable = () => (
<div className="not-available-outer">
<FlexRow className="inner">
<img
className="not-available-image"
src="/images/404-giga.png"
/>
<h1>
<FormattedMessage id="general.notAvailableHeadline" />
</h1>
<p>
<FormattedMessage id="general.notAvailableSubtitle" />
</p>
</FlexRow>
</div>
);
module.exports = ProjectNotAvailable;

View file

@ -0,0 +1,5 @@
@import "../../colors";
.not-available-image {
padding: 5rem 0 2rem;
}

View file

@ -4,6 +4,7 @@ const React = require('react');
const Thumbnail = require('../thumbnail/thumbnail.jsx');
const FlexRow = require('../flex-row/flex-row.jsx');
const thumbnailUrl = require('../../lib/user-thumbnail');
require('./thumbnailcolumn.scss');
@ -14,7 +15,7 @@ const ThumbnailColumn = props => (
if (props.itemType === 'preview') {
return (
<Thumbnail
avatar={`https://cdn2.scratch.mit.edu/get_image/user/${item.author.id}_32x32.png`}
avatar={thumbnailUrl(item.author.id)}
creator={item.author.username}
favorites={item.stats.favorites}
href={href}

View file

@ -19,11 +19,11 @@ const Welcome = props => (
>
<div className="welcome-col blue">
<h4>
<a href="/projects/editor/?tip_bar=getStarted">
<a href="/projects/editor/?tutorial=getStarted">
{props.messages['welcome.learn']}
</a>
</h4>
<a href="/projects/editor/?tip_bar=getStarted">
<a href="/projects/editor/?tutorial=getStarted">
<img
alt="Get Started"
src="/images/welcome-learn.png"

View file

@ -86,7 +86,10 @@
"general.wiki": "Scratch Wiki",
"general.copyLink": "Copy Link",
"general.report": "Report",
"general.notAvailableHeadline": "Whoops! Our server is Scratch'ing its head",
"general.notAvailableSubtitle": "We couldn't find the page you're looking for. Check to make sure you've typed the url correctly.",
"general.seeAllComments": "See all comments",
"general.all": "All",
"general.animations": "Animations",
"general.art": "Art",
@ -97,6 +100,10 @@
"general.tutorials": "Tutorials",
"general.teacherAccounts": "Teacher Accounts",
"general.unsupportedBrowser": "This browser is not supported",
"general.unsupportedBrowserDescription": "We're very sorry, but Scratch 3.0 does not support Internet Explorer, Vivaldi, Opera or Silk. We recommend trying a newer browser such as Google Chrome, Mozilla Firefox, or Microsoft Edge.",
"general.3faq": "To learn more, go to the {faqLink}.",
"footer.discuss": "Discussion Forums",
"footer.scratchFamily": "Scratch Family",
@ -115,8 +122,8 @@
"installScratchLink.installHeaderTitle": "Install Scratch Link",
"installScratchLink.downloadAndInstall": "Download and install Scratch Link.",
"installScratchLink.windowsDownload": "Download for Windows",
"installScratchLink.macosDownload": "Download for macOS",
"installScratchLink.or": "or",
"installScratchLink.directDownload": "Direct download",
"installScratchLink.startScratchLink": "Start Scratch Link and make sure it is running. It should appear in your toolbar.",
"parents.FaqAgeRangeA": "While Scratch is primarily designed for 8 to 16 year olds, it is also used by people of all ages, including younger children with their parents.",

View file

@ -12,24 +12,31 @@ const EXTENSION_INFO = {
l10nId: 'project.penExtensionChip',
icon: 'extension-pen.svg'
},
speak: {
name: 'Amazon Polly'
},
speech: {
l10nId: 'project.speechExtensionChip'
},
translate: {
l10nId: 'project.translateExtensionChip',
icon: 'extension-translate.svg'
},
videoSensing: {
l10nId: 'project.videoMotionChip',
icon: 'extension-videomotion.svg'
icon: 'extension-videosensing.svg'
},
text2speech: {
l10nId: 'project.text2SpeechChip',
icon: 'extension-text2speech.svg'
},
translate: {
l10nId: 'project.translateChip',
icon: 'extension-translate.svg'
},
wedo2: {
name: 'LEGO WeDo 2.0',
icon: 'extension-wedo2.svg',
hasStatus: true
},
ev3: {
name: 'LEGO MINDSTORMS EV3',
icon: 'extension-ev3.svg',
hasStatus: true
},
makeymakey: {
name: 'Makey Makey',
icon: 'extension-makeymakey.svg'
}
};

View file

@ -0,0 +1,15 @@
import bowser from 'bowser';
/**
* Helper function to determine if the browser is supported.
* @returns {boolean} False if the platform is definitely not supported.
*/
export default function () {
if (bowser.msie ||
bowser.vivaldi ||
bowser.opera ||
bowser.silk) {
return false;
}
return true;
}

18
src/lib/user-thumbnail.js Normal file
View file

@ -0,0 +1,18 @@
/**
* @user-thumbnail
* Utility functions to return thumnail-related strings
*/
/**
* Generate a thumbnail url for a particular userid, with width and height.
* @param {string} userId userId for the user whose thumbnail we want
* @param {number} width desired thumbnail width; defaults to 32
* @param {number} height desired thumbnail height; defaults to width.
* @returns {string} thumbnail url string
*/
const thumbnailUrl = (userId, width, height) => (
`${process.env.STATIC_HOST}/get_image/user/${userId || 'default'}_` +
`${width ? width : 32}x${height ? height : (width ? width : 32)}.png`
);
module.exports = thumbnailUrl;

View file

@ -25,6 +25,7 @@ module.exports.getInitialState = () => ({
report: module.exports.Status.NOT_FETCHED,
projectStudios: module.exports.Status.NOT_FETCHED,
curatedStudios: module.exports.Status.NOT_FETCHED,
visibility: module.exports.Status.NOT_FETCHED,
studioRequests: {}
},
projectInfo: {},
@ -38,7 +39,9 @@ module.exports.getInitialState = () => ({
projectStudios: [],
curatedStudios: [],
currentStudioIds: [],
moreCommentsToLoad: false
moreCommentsToLoad: false,
projectNotAvailable: false,
visibilityInfo: {}
});
module.exports.previewReducer = (state, action) => {
@ -51,7 +54,12 @@ module.exports.previewReducer = (state, action) => {
return module.exports.getInitialState();
case 'SET_PROJECT_INFO':
return Object.assign({}, state, {
projectInfo: action.info
projectInfo: action.info ? action.info : {},
projectNotAvailable: !action.info
});
case 'UPDATE_PROJECT_INFO':
return Object.assign({}, state, {
projectInfo: Object.assign({}, state.projectInfo, action.info)
});
case 'SET_REMIXES':
return Object.assign({}, state, {
@ -85,6 +93,11 @@ module.exports.previewReducer = (state, action) => {
item !== action.studioId
))
});
case 'RESET_COMMENTS':
return Object.assign({}, state, {
comments: [],
replies: {}
});
case 'SET_COMMENTS':
return Object.assign({}, state, {
comments: [...state.comments, ...action.items] // TODO: consider a different way of doing this?
@ -158,6 +171,10 @@ module.exports.previewReducer = (state, action) => {
return Object.assign({}, state, {
moreCommentsToLoad: action.moreCommentsToLoad
});
case 'SET_VISIBILITY_INFO':
return Object.assign({}, state, {
visibilityInfo: action.visibilityInfo
});
case 'ERROR':
log.error(action.error);
return state;
@ -180,6 +197,11 @@ module.exports.setProjectInfo = info => ({
info: info
});
module.exports.updateProjectInfo = info => ({
type: 'UPDATE_PROJECT_INFO',
info: info
});
module.exports.setOriginalInfo = info => ({
type: 'SET_ORIGINAL',
info: info
@ -301,6 +323,15 @@ module.exports.setMoreCommentsToLoad = moreCommentsToLoad => ({
moreCommentsToLoad: moreCommentsToLoad
});
module.exports.resetComments = () => ({
type: 'RESET_COMMENTS'
});
module.exports.setVisibilityInfo = visibilityInfo => ({
type: 'SET_VISIBILITY_INFO',
visibilityInfo: visibilityInfo
});
module.exports.getProjectInfo = (id, token) => (dispatch => {
const opts = {
uri: `/projects/${id}`
@ -309,19 +340,41 @@ module.exports.getProjectInfo = (id, token) => (dispatch => {
Object.assign(opts, {authentication: token});
}
dispatch(module.exports.setFetchStatus('project', module.exports.Status.FETCHING));
api(opts, (err, body) => {
api(opts, (err, body, response) => {
if (err) {
dispatch(module.exports.setFetchStatus('project', module.exports.Status.ERROR));
dispatch(module.exports.setError(err));
return;
}
if (typeof body === 'undefined') {
if (typeof body === 'undefined' || response.statusCode === 404) {
dispatch(module.exports.setFetchStatus('project', module.exports.Status.ERROR));
dispatch(module.exports.setError('No project info'));
dispatch(module.exports.setProjectInfo(null));
return;
}
dispatch(module.exports.setFetchStatus('project', module.exports.Status.FETCHED));
dispatch(module.exports.setProjectInfo(body));
// If the project is not public, make a follow-up request for why
if (!body.public) {
dispatch(module.exports.getVisibilityInfo(id, body.author.username, token));
}
});
});
module.exports.getVisibilityInfo = (id, ownerUsername, token) => (dispatch => {
dispatch(module.exports.setFetchStatus('visibility', module.exports.Status.FETCHING));
api({
uri: `/users/${ownerUsername}/projects/${id}/visibility`,
authentication: token
}, (err, body, response) => {
if (err || !body || response.statusCode !== 200) {
dispatch(module.exports.setFetchStatus('visibility', module.exports.Status.ERROR));
dispatch(module.exports.setError('No visibility info available'));
return;
}
dispatch(module.exports.setFetchStatus('visibility', module.exports.Status.FETCHED));
dispatch(module.exports.setVisibilityInfo(body));
});
});
@ -767,6 +820,25 @@ module.exports.restoreComment = (projectId, commentId, topLevelCommentId, token)
});
});
module.exports.shareProject = (projectId, token) => (dispatch => {
dispatch(module.exports.setFetchStatus('project', module.exports.Status.FETCHING));
api({
uri: `/proxy/projects/${projectId}/share`,
authentication: token,
withCredentials: true,
method: 'PUT',
useCsrf: true
}, (err, body, res) => {
if (err || res.statusCode !== 200) {
dispatch(module.exports.setFetchStatus('project', module.exports.Status.ERROR));
dispatch(module.exports.setError(err));
return;
}
dispatch(module.exports.setFetchStatus('project', module.exports.Status.FETCHED));
dispatch(module.exports.updateProjectInfo(body));
});
});
module.exports.reportProject = (id, jsonData, token) => (dispatch => {
dispatch(module.exports.setFetchStatus('report', module.exports.Status.FETCHING));
// scratchr2 will fail if no thumbnail base64 string provided. We don't yet have
@ -791,3 +863,24 @@ module.exports.reportProject = (id, jsonData, token) => (dispatch => {
dispatch(module.exports.setFetchStatus('report', module.exports.Status.FETCHED));
});
});
module.exports.updateProjectThumbnail = (id, blob) => (dispatch => {
dispatch(module.exports.setFetchStatus('project-thumbnail', module.exports.Status.FETCHING));
api({
uri: `/internalapi/project/thumbnail/${id}/set/`,
method: 'POST',
headers: {
'Content-Type': 'image/png'
},
withCredentials: true,
useCsrf: true,
body: blob,
host: '' // Not handled by the API, use existing infrastructure
}, (err, body, res) => {
if (err || res.statusCode !== 200) {
dispatch(module.exports.setFetchStatus('project-thumbnail', module.exports.Status.ERROR));
return;
}
dispatch(module.exports.setFetchStatus('project-thumbnail', module.exports.Status.FETCHED));
});
});

View file

@ -198,11 +198,11 @@
"title": "Microworlds"
},
{
"name": "preview",
"pattern": "^/preview(/editor|(/\\d+(/editor|/fullscreen)?)?)?/?$",
"routeAlias": "/preview/?$",
"name": "projects",
"pattern": "^/projects(/editor|(/\\d+(/editor|/fullscreen)?)?)?/?(\\?.*)?$",
"routeAlias": "/projects/?$",
"view": "preview/preview",
"title": "Scratch 3.0 Preview"
"title": "Scratch Project"
},
{
"name": "3faq",
@ -211,6 +211,13 @@
"view": "preview-faq/preview-faq",
"title": "Scratch 3.0 FAQ"
},
{
"name": "parents",
"pattern": "^/parents/?$",
"routeAlias": "/parents/",
"view": "parents/parents",
"title": "For Parents"
},
{
"name": "preview-faq-redirect",
"pattern": "^/preview-faq/?$",
@ -231,6 +238,13 @@
"view": "research/research",
"title": "Research"
},
{
"name": "scratch_1.4",
"pattern": "^/scratch_1.4/?$",
"routeAlias": "/scratch_1.4",
"view": "scratch_1.4/scratch_1.4",
"title": "Scratch 1.4"
},
{
"name": "search",
"pattern": "^/search/:projects/?$",
@ -302,7 +316,7 @@
"title": "LEGO WeDo 2.0"
},
{
"name": "wedo-legacy",
"name": "wedo2-legacy",
"pattern": "^/wedo-legacy/?$",
"routeAlias": "/wedo-legacy/?$",
"view": "wedo2-legacy/wedo2",
@ -410,5 +424,135 @@
"pattern": "^/go/?(\\?.*)?$",
"routeAlias": "/go/?\\??",
"redirect": "/tips"
},
{
"name": "create-tutorial-redirect",
"pattern": "^/create/?$",
"redirect": "/projects/editor/?tutorial=getStarted"
},
{
"name": "name-tutorial-redirect",
"pattern": "^/name/?$",
"redirect": "/projects/editor/?tutorial=name"
},
{
"name": "music-tutorial-redirect",
"pattern": "^/music/?$",
"redirect": "/projects/editor/?tutorial=music"
},
{
"name": "story-tutorial-redirect",
"pattern": "^/story/?$",
"redirect": "/projects/editor/?tutorial=story"
},
{
"name": "pong-tutorial-redirect",
"pattern": "^/pong/?$",
"redirect": "/projects/editor/?tutorial=pong"
},
{
"name": "clicker-tutorial-redirect",
"pattern": "^/clicker/?$",
"redirect": "/projects/editor/?tutorial=clicker-game"
},
{
"name": "chase-tutorial-redirect",
"pattern": "^/chase/?$",
"redirect": "/projects/editor/?tutorial=chase-game"
},
{
"name": "jazz-tutorial-redirect",
"pattern": "^/jazz/?$",
"redirect": "/projects/editor/?tutorial=music"
},
{
"name": "catch-tutorial-redirect",
"pattern": "^/catch/?$",
"redirect": "/tips"
},
{
"name": "dance-tutorial-redirect",
"pattern": "^/dance/?$",
"redirect": "/tips"
},
{
"name": "fly-tutorial-redirect",
"pattern": "^/fly/?$",
"redirect": "/tips"
},
{
"name": "pet-tutorial-redirect",
"pattern": "^/pet/?$",
"redirect": "/tips"
},
{
"name": "racegame-tutorial-redirect",
"pattern": "^/racegame/?$",
"redirect": "/tips"
},
{
"name": "hide-tutorial-redirect",
"pattern": "^/hide/?$",
"redirect": "/tips"
},
{
"name": "fashion-tutorial-redirect",
"pattern": "^/fashion/?$",
"redirect": "/tips"
},
{
"name": "dressup-tutorial-redirect",
"pattern": "^/dressup/?$",
"redirect": "/tips"
},
{
"name": "card-tutorial-redirect",
"pattern": "^/card/?$",
"redirect": "/tips"
},
{
"name": "valentines-tutorial-redirect",
"pattern": "^/valentines/?$",
"redirect": "/tips"
},
{
"name": "bearstack-tutorial-redirect",
"pattern": "^/bearstack/?$",
"redirect": "/tips"
},
{
"name": "favorite-tutorial-redirect",
"pattern": "^/favorite/?$",
"redirect": "/tips"
},
{
"name": "hoops-tutorial-redirect",
"pattern": "^/hoops/?$",
"redirect": "/tips"
},
{
"name": "soccer-tutorial-redirect",
"pattern": "^/soccer/?$",
"redirect": "/tips"
},
{
"name": "codeweek-tutorial-redirect",
"pattern": "^/codeweekeu/?$",
"redirect": "/tips"
},
{
"name": "madewithcode-tutorial-redirects",
"pattern": "^/madewithcode-(name|card)/?$",
"redirect": "/tips"
},
{
"name": "odetocode-tutorial-redirect",
"pattern": "^/odetocode/?$",
"redirect": "/tips"
},
{
"name": "makey-tutorial-redirects",
"pattern": "^/makey(piano|drum)?/?$",
"redirect": "/tips"
}
]

View file

@ -1,459 +1,191 @@
const React = require('react');
const render = require('../../lib/render.jsx');
const FormattedHTMLMessage = require('react-intl').FormattedHTMLMessage;
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const Avatar = require('../../components/avatar/avatar.jsx');
const Page = require('../../components/page/www/page.jsx');
const People = require('./people.json');
const Supporters = require('./supporters.json');
const TitleBanner = require('../../components/title-banner/title-banner.jsx');
require('./credits.scss');
const Credits = () => (
<div className="inner credits">
<h1><FormattedMessage id="credits.title" /></h1>
<h2>MIT Scratch Team</h2>
<p><FormattedMessage id="credits.developers" /></p>
<div className="credits">
<TitleBanner className="masthead mod-blue-bg">
<h1 className="title-banner-h1">
<FormattedMessage id="credits.title" />
</h1>
</TitleBanner>
<div className="content">
<div className="people">
<div className="mid-header">
<h2>MIT Scratch Team</h2>
<p>
<FormattedMessage id="credits.developers" />
</p>
</div>
<ul className="avatar-grid">
{People.map((person, index) => (
<li
className="avatar-item"
key={`person-${index}`}
>
<div>
<a href={`https://scratch.mit.edu/users/${person.userName}/`}>
<Avatar
alt=""
src={`https://cdn.scratch.mit.edu/get_image/user/${person.userId || 'default'}_80x80.png`}
/>
</a>
</div>
<span className="avatar-text">
{person.name}
</span>
</li>
))}
</ul>
</div>
<div className="supporters">
<div className="mid-header">
<h2>
<FormattedMessage id="credits.currentSponsors" />
</h2>
<p>
<FormattedMessage id="credits.currentFinancialSupport" />
</p>
</div>
<div className="logo-grid">
{Supporters.map((supporter, index) => (
<span
className="logo"
key={`logo-${index}`}
>
<a href={supporter.logoDestination}>
{supporter.logoSrc ? (
<img
alt=""
src={supporter.logoSrc}
width={supporter.width}
/>
) :
<div className="text-logo">
{supporter.textLogo}
</div>
}
</a>
</span>
))}
</div>
</div>
<div className="acknowledge-content">
<h2>
<FormattedMessage id="credits.translationsTitle" />
</h2>
<p>
<FormattedMessage
id="credits.acknowledgementsTranslators"
values={{
translatorsLink: (
<a href="http://wiki.scratch.mit.edu/wiki/Translators">
<FormattedMessage id="credits.acknowledgementsTranslatorsLinkText" />
</a>
)
}}
/>
</p>
<h2>
<FormattedMessage id="credits.illustrationsTitle" />
</h2>
<p>
<FormattedMessage id="credits.acknowledgementsIllustrations" />
</p>
<p>
Natalie Rosalinda Hall, Wren McDonald, Andrew Rae, Daria Skrybchenko,
Robert Hunter, Alex Eben Meyer, Ding Ding Hu, Owen Davey.
</p>
<h2>
<FormattedMessage id="credits.pastContributors" />
</h2>
<p>
<FormattedMessage id="credits.pastContributorsThanks" />
</p>
<p>
<FormattedMessage id="credits.otherContributors" />
{' '}
Ben Berg, Amos Blanton, Karen Brennan, Juanita Buitrago, Leo Burd,
Gaia Carini, Kasia Chmielinski, Michelle Chung, Shane Clements,
Hannah Cole, Sayamindu Dasgupta, Margarita Dekoli, Evelyn Eastmond,
Dave Feinberg, Chris Graves, Megan Haddadi, Connor Hudson,
Christina Huang, Tony Hwang, Abdulrahman Idlbi, Randy Jou, Lily Kim,
Tauntaun Kim, Saskia Leggett, Tim Mickel, Amon Millner, Ricarose Roque,
Andrea Saxman, Jay Silver, Tammy Stern, Lis Sylvan, Hanako Tjia, Claudia
Urrea, Oren Zuckerman.
</p>
<p>
<FormattedMessage id="credits.partnersBody" />
</p>
<h2>
<FormattedMessage id="credits.researchersTitle" />
</h2>
<p>
<FormattedMessage
id="credits.researchersBody"
values={{
scratchResearchLink: (
<a href="https://scratch.mit.edu/info/research/">
<FormattedMessage id="credits.researchLinkText" />
</a>
)
}}
/>
</p>
<p>
<FormattedMessage
id="credits.researchersContributors"
values={{
nsfLink: (
<a href="http://www.nsf.gov/awardsearch/showAward?AWD_ID=0325828">
<FormattedMessage id="credits.researchNSFLinkText" />
</a>
),
scratchEdLink: (
<a href="http://scratched.gse.harvard.edu/">
<FormattedMessage id="credits.researchScratchEdLinkText" />
</a>
)
}}
/>
</p>
<h2>
<FormattedMessage id="credits.acknowledgementsTitle" />
</h2>
<p>
<FormattedMessage id="credits.acknowledgementsContributors" />
</p>
<p>
Susan Abend, Robbie Berg, Lauren Bessen, Keith Braadfladt,
Susan Carillo, Will Denton, Nathan Dinsmore, Catherine Feldman,
Jodi Finch, Ioana Fineberg, JT Galla, Rachel Garber, Cassy Gibbs,
Brian Harvey, Roland Hebert, Tracy Ho, Benjamin Howe, Kapaya Katongo,
Evan Karatzas, Christine Kim, Joren Lauwers, Mike Lee, Jeff Lieberman,
Mark Loughridge, Kelly Liu, Anthony Lu, Danny Lutz, David Malan
Wayne Marshall, John McIntosh, Paul Medlock-Walton, Dongfang (Tian) Mi,
Ximena Miranda, Jens Moenig, Evan Moore, Geetha Narayanan, Kate Nazemi,
Liddy Nevile, Wing Ngan, Derek O&apos;Connell, Tim Radvan, Karen Randall,
Ian Reynolds, Miriam Ruiz, Chinua Shaw, Ed Shems, Cynthia Solomon,
Daniel Strimpel, Kilmer Sweazy, John Henry Thompson, Ubong Ukoh,
Vladimir Vuksan, Han Xu.
</p>
<p>
<FormattedMessage id="credits.acknowledgementsInfluencers" />
</p>
<p>
<FormattedMessage id="credits.acknowledgementsCommunity" />
</p>
</div>
</div>
</div>);
<ul>
<li>
<img
alt="Christan Avatar"
src="//cdn.scratch.mit.edu/get_image/user/2755634_170x170.png"
/>
<span className="name">Christan Balch</span>
</li>
<li>
<img
alt="Carl Avatar"
src="//cdn.scratch.mit.edu/get_image/user/3581881_170x170.png"
/>
<span className="name">Carl Bowman</span>
</li>
<li>
<img
alt="Karishma Avatar"
src="//cdn2.scratch.mit.edu/get_image/user/27383273_60x60.png"
/>
<span className="name">Karishma Chadha</span>
</li>
<li>
<img
alt="Champika Avatar"
src="//cdn.scratch.mit.edu/get_image/user/900283_170x170.png"
/>
<span className="name">Champika Fernando</span>
</li>
<li>
<img
alt="Mark Avatar"
src="//cdn.scratch.mit.edu/get_image/user/24137617_170x170.png"
/>
<span className="name">Mark Ferrell</span>
</li>
<li>
<img
alt="Chris Avatar"
src="//cdn.scratch.mit.edu/get_image/user/1494_170x170.png"
/>
<span className="name">Chris Garrity</span>
</li>
<li>
<img
alt="Colby Avatar"
src="//cdn.scratch.mit.edu/get_image/user/10866958_170x170.png"
/>
<span className="name">Colby Gutierrez-Kraybill</span>
</li>
<li>
<img
alt="Paul Avatar"
src="//cdn.scratch.mit.edu/get_image/user/21986973_170x170.png"
/>
<span className="name">Paul Kaplan</span>
</li>
<li>
<img
alt="DD Avatar"
src="//cdn.scratch.mit.edu/get_image/user/527836_170x170.png"
/>
<span className="name">DD Liu</span>
</li>
<li>
<img
alt="Katelyn Avatar"
src="//cdn.scratch.mit.edu/get_image/user/34607790_170x170.png"
/>
<span className="name">Katelyn Mann</span>
</li>
<li>
<img
alt="Shruti Avatar"
src="//cdn.scratch.mit.edu/get_image/user/3714374_170x170.png"
/>
<span className="name">Shruti Mohnot</span>
</li>
<li>
<img
alt="Sarah Avatar"
src="//cdn.scratch.mit.edu/get_image/user/246290_170x170.png"
/>
<span className="name">Sarah Otts</span>
</li>
<li>
<img
alt="Carmelo Avatar"
src="//cdn.scratch.mit.edu/get_image/user/2286560_170x170.png"
/>
<span className="name">Carmelo Presicce</span>
</li>
<li>
<img
alt="Tina Avatar"
src="//cdn2.scratch.mit.edu/get_image/user/25500116_170x170.png"
/>
<span className="name">Tina Quach</span>
</li>
<li>
<img
alt="Mitchel Avatar"
src="//cdn.scratch.mit.edu/get_image/user/167_170x170.png"
/>
<span className="name">Mitchel Resnick</span>
</li>
<li>
<img
alt="ericr Avatar"
src="//cdn.scratch.mit.edu/get_image/user/159_170x170.png"
/>
<span className="name">Eric Rosenbaum</span>
</li>
<li>
<img
alt="Natalie Avatar"
src="//cdn.scratch.mit.edu/get_image/user/169_170x170.png"
/>
<span className="name">Natalie Rusk</span>
</li>
<li>
<img
alt="Ray Avatar"
src="//cdn.scratch.mit.edu/get_image/user/2584924_170x170.png"
/>
<span className="name">Ray Schamp</span>
</li>
<li>
<img
alt="Eric Avatar"
src="//cdn.scratch.mit.edu/get_image/user/3484484_170x170.png"
/>
<span className="name">Eric Schilling</span>
</li>
<li>
<img
alt="Andrew Avatar"
src="//cdn.scratch.mit.edu/get_image/user/1709047_170x170.png"
/>
<span className="name">Andrew Sliwinski</span>
</li>
<li>
<img
alt="Tracy Avatar"
src="//cdn.scratch.mit.edu/get_image/user/18417774_170x170.png"
/>
<span className="name">Tracy Tang</span>
</li>
<li>
<img
alt="Bryce Avatar"
src="//cdn.scratch.mit.edu/get_image/user/2029640_170x170.png"
/>
<span className="name">Bryce Taylor</span>
</li>
<li>
<img
alt="Matthew Avatar"
src="//cdn.scratch.mit.edu/get_image/user/4373707_170x170.png"
/>
<span className="name">Matthew Taylor</span>
</li>
<li>
<img
alt="Moran Avatar"
src="//cdn.scratch.mit.edu/get_image/user/2678986_170x170.png"
/>
<span className="name">Moran Tsur</span>
</li>
<li>
<img
alt="Chris Avatar"
src="//cdn.scratch.mit.edu/get_image/user/3532363_170x170.png"
/>
<span className="name">Chris Willis-Ford</span>
</li>
<li>
<img
alt="Kathy Avatar"
src="//cdn.scratch.mit.edu/get_image/user/26779669_170x170.png"
/>
<span className="name">Kathy Wu</span>
</li>
<li>
<img
alt="Julia Avatar"
src="//cdn.scratch.mit.edu/get_image/user/2796185_170x170.png"
/>
<span className="name">Julia Zimmerman</span>
</li>
</ul>
<p><FormattedMessage id="credits.moderators" /></p>
<ul>
<li>
<img
alt="Jolie Avatar"
src="//cdn.scratch.mit.edu/get_image/user/2496866_170x170.png"
/>
<span className="name">Jolie Castellucci</span>
</li>
<li>
<img
alt="Ellen Avatar"
src="//cdn.scratch.mit.edu/get_image/user/24164779_170x170.png"
/>
<span className="name">Ellen Daoust</span>
</li>
<li>
<img
alt="Linda Avatar"
src="//cdn.scratch.mit.edu/get_image/user/1048810_170x170.png"
/>
<span className="name">Linda Fernsel</span>
</li>
<li>
<img
alt="Mark Avatar"
src="//cdn.scratch.mit.edu/get_image/user/49156_170x170.png"
/>
<span className="name">Mark Goff</span>
</li>
<li>
<img
alt="Joel Avatar"
src="//cdn2.scratch.mit.edu/get_image/user/26249744_60x60.png"
/>
<span className="name">Joel Gritter</span>
</li>
<li>
<img
alt="Carolina Avatar"
src="//cdn2.scratch.mit.edu/get_image/user/5311910_60x60.png"
/>
<span className="name">Carolina Kaufman</span>
</li>
<li>
<img
alt="Dalton Avatar"
src="//cdn.scratch.mit.edu/get_image/user/373646_170x170.png"
/>
<span className="name">Dalton Miner</span>
</li>
<li>
<img
alt="Franchette Avatar"
src="//cdn.scratch.mit.edu/get_image/user/159139_170x170.png"
/>
<span className="name">Franchette Viloria</span>
</li>
<li>
<img
alt="Annie Avatar"
src="//cdn.scratch.mit.edu/get_image/user/4747093_170x170.png"
/>
<span className="name">Annie Whitehouse</span>
</li>
</ul>
<h2><FormattedMessage id="credits.previousTitle" /></h2>
<p>
<FormattedMessage id="credits.previousBody" />
&nbsp;
Ben Berg,
Amos Blanton,
Karen Brennan,
Juanita Buitrago,
Leo Burd,
Gaia Carini,
Kasia Chmielinski,
Michelle Chung,
Shane Clements,
Hannah Cole,
Sayamindu Dasgupta,
Margarita Dekoli,
Evelyn Eastmond,
Dave Feinberg,
Chris Graves,
Megan Haddadi,
Connor Hudson,
Christina Huang,
Tony Hwang,
Abdulrahman Idlbi,
Randy Jou,
Lily Kim,
Tauntaun Kim,
Saskia Leggett,
Tim Mickel,
Amon Millner,
Ricarose Roque,
Andrea Saxman,
Jay Silver,
Tammy Stern,
Lis Sylvan,
Hanako Tjia,
Claudia Urrea,
Oren Zuckerman
</p>
<h2>
<FormattedMessage id="credits.partnersTitle" />
</h2>
<p>
<FormattedMessage id="credits.partnersBody" />
</p>
<h2>
<FormattedMessage id="credits.researchersTitle" />
</h2>
<p>
<FormattedHTMLMessage id="credits.researchersBody" />
</p>
<h2>
<FormattedMessage id="credits.acknowledgementsTitle" />
</h2>
<p>
<FormattedHTMLMessage id="credits.acknowledgementsContributors" />
&nbsp;
Susan Abend, Robbie Berg, Lauren Bessen, Keith Braadfladt, Susan Carillo,
Will Denton, Nathan Dinsmore, Catherine Feldman, Jodi Finch, Ioana Fineberg,
JT Galla, Rachel Garber, Chris Garrity, Cassy Gibbs, Brian Harvey,
Roland Hebert, Tracy Ho, Benjamin Howe, Kapaya Katongo, Evan Karatzas,
Christine Kim, Joren Lauwers, Mike Lee, Jeff Lieberman, Mark Loughridge,
Kelly Liu, Anthony Lu, Danny Lutz, David Malan, Wayne Marshall,
John McIntosh, Paul Medlock-Walton, Dongfang (Tian) Mi, Ximena Miranda,
Jens Moenig, Evan Moore, Geetha Narayanan, Kate Nazemi, Liddy Nevile,
Wing Ngan, Derek O&#39;Connell, Tim Radvan, Karen Randall, Ian Reynolds,
Miriam Ruiz, Chinua Shaw, Ed Shems, Cynthia Solomon, Daniel Strimpel,
Kilmer Sweazy, John Henry Thompson, Ubong Ukoh, Vladimir Vuksan, Han Xu.
&nbsp;
<FormattedHTMLMessage id="credits.acknowledgementsTranslators" />
</p>
<p>
<FormattedMessage id="credits.acknowledgementsCommunity" />
</p>
<p>
<FormattedMessage id="credits.acknowledgementsInfluencers" />
</p>
<h2>
<FormattedMessage id="credits.supportersTitle" />
</h2>
<p>
<FormattedMessage id="credits.supportersFinancialHeader" />
</p>
<p>
<a href="http://www.nsf.gov/">National Science Foundation</a>,
<a href="http://www.scratchfoundation.org/"> Scratch Foundation</a>,
<a href="http:/www.siegelendowment.org"> Siegel Family Endowment</a>,
<a href="http://www.google.org/"> Google</a>,
<a href="http://www.legofoundation.com/"> LEGO Foundation</a>,
<a href="http://www.intel.com/"> Intel</a>,
<a href="http://www.turner.com/company/"> Cartoon Network</a>,
<a href="http://www.fundacaolemann.org.br/lemann-foundation/"> Lemann Foundation</a>,
<a href="https://www.macfound.org/"> MacArthur Foundation</a>.
</p>
<p><FormattedMessage id="credits.supportersServicesHeader" /></p>
<p>
<a href="http://www.advancedinstaller.com/"> Advanced Installer</a>,
<a href="http://aws.amazon.com/"> Amazon Web Services</a>,
<a href="https://codetree.com/"> Codetree</a>,
<a href="https://www.fastly.com/"> Fastly</a>,
<a href="https://www.geckoboard.com"> Geckoboard</a>,
<a href="https://github.com/"> Github</a>,
<a href="https://www.inversoft.com/"> Inversoft</a>,
<a href="http://mailchimp.com/"> MailChimp</a>,
<a href="http://mandrill.com/"> Mandrill</a>,
<a href="http://newrelic.com/"> New Relic</a>,
<a href="https://www.pagerduty.com/"> PagerDuty</a>,
<a href="https://www.pingdom.com/"> Pingdom</a>,
<a href="https://www.rallydev.com/"> Rally</a>,
<a href="https://saucelabs.com/"> SauceLabs</a>,
<a href="https://screenhero.com/"> Screenhero</a>,
<a href="https://getsentry.com/welcome/"> Sentry</a>,
<a href="http://www.git-tower.com/"> Tower</a>,
<a href="https://www.transifex.com/"> Transifex</a>,
<a href="https://travis-ci.org/"> Travis-CI</a>.
</p>
<p><FormattedMessage id="credits.supportersOpenHeader" /></p>
<p>
<a href="https://www.djangoproject.com/"> Django</a>,
<a href="http://djangobb.org/"> DjangoBB</a>,
<a href="https://www.docker.com/"> Docker</a>,
<a href="https://www.elastic.co/"> Elasticsearch</a>,
<a href="http://ganglia.sourceforge.net/"> Ganglia</a>,
<a href="http://gunicorn.org"> Gunicorn</a>,
<a href="https://jenkins-ci.org/"> Jenkins</a>,
<a href="http://www.linux.org/"> Linux</a>,
<a href="http://memcached.org/"> Memcached</a>,
<a href="https://www.mediawiki.org/wiki/MediaWiki"> MediaWiki</a>,
<a href="http://www.mysql.com/"> MySQL</a>,
<a href="https://www.nagios.org/"> Nagios</a>,
<a href="https://www.nginx.com/resources/wiki/"> Nginx</a>,
<a href="https://nodejs.org/en/"> Node.js</a>,
<a href="http://www.postgresql.org/"> PostgreSQL</a>,
<a href="https://www.python.org/"> Python</a>,
<a href="http://redis.io/"> Redis</a>,
<a href="http://saltstack.com/"> SaltStack</a>,
<a href="https://github.com/etsy/statsd/"> StatsD</a>,
<a href="http://www.ubuntu.com/"> Ubuntu</a>,
<a href="https://www.varnish-cache.org/"> Varnish</a>.
</p>
</div>
);
render(<Page><Credits /></Page>, document.getElementById('app'));
const WrappedCredits = injectIntl(Credits);
render(<Page><WrappedCredits /></Page>, document.getElementById('app'));

View file

@ -1,39 +1,172 @@
@import "../../colors";
.credits {
p {
line-height: 1.5rem;
#view {
padding: 0;
text-align: left;
}
.credits {
.avatar-grid {
display: flex;
margin: 0 auto;
padding: 64px 0;
max-width: 864px;
list-style: none;
flex-wrap: wrap;
flex-flow: row wrap;
justify-content: center;
}
.content {
padding-top: 40px;
}
.avatar-item {
display: inline-block;
margin: 0;
padding-bottom: 32px;
text-align: center;
line-height: 1.25rem;
img {
$img-border: rgba(0, 0, 0, .05);
border: 2px solid $img-border;
border-radius: 8px;
background-color: $ui-white;
width: 80px;
height: 80px;
}
.avatar-text {
display: inline-block;
width: 144px;
font-size: .875rem;
word-wrap: break-word;
a {
white-space: normal;
word-wrap: break-word; /* Overrides: https://github.com/LLK/scratch-www/blob/develop/src/main.scss#L43-L47 */
}
}
ul {
display: flex;
margin: 0;
padding: 0;
list-style: none;
flex-wrap: wrap;
.acknowledge-content {
margin: 0 176px;
padding: 2.5rem 0 5.75rem 0;
max-width: 520px;
p {
margin: 0;
padding-bottom: 1.875rem;
line-height: 1.5rem;
}
h2 {
padding: 3rem 0 1.5rem;
line-height: 1.2em;
font-size: 2rem;
}
}
li {
display: inline-block;
margin: 10px 0;
width: 188px;
text-align: center;
.text-logo {
max-width: 140px;
text-align: center;
font-size: 16px;
font-weight: bold;
}
img {
margin: 10px 23px;
border: 2px;
border-style: solid;
border-radius: 50%;
border-color: $ui-dark-gray;
background-color: $ui-white;
padding: 20px;
width: 85px;
height: 85px;
.mid-header {
display: flex;
margin: 0 88px;
flex-direction: column;
align-items: center;
h2 {
line-height: 1.2em;
}
}
.logo-grid {
display: flex;
margin: 0 auto;
max-width: 864px;
flex-wrap: wrap;
justify-content: space-around;
align-items: center;
}
.supporters {
$bg-grey: #f7f6f8;
background-color: $bg-grey;
padding: 5.25rem 0;
}
.logo {
padding: 84px 24px 0;
}
.title-banner {
display: flex;
padding: 0;
height: 152px;
align-items: center;
}
.title-banner-h1 {
font-size: 2rem;
}
}
@media only screen and (min-width: 477px) and (max-width: 768px) {
.credits {
.acknowledge-content {
margin: 0 88px;
}
}
}
@media only screen and (min-width: 365px) and (max-width: 476px) {
.credits {
.avatar-item {
padding-bottom: 16px;
.avatar-text {
width: 112px;
}
}
.acknowledge-content,
.mid-header {
margin: 0 44px;
}
}
}
@media only screen and (max-width: 364px) {
.credits {
.avatar-item {
padding-bottom: 16px;
img {
width: 56px;
height: 56px;
}
.avatar-text {
width: 96px;
}
}
.logo-grid {
flex-direction: column;
}
.acknowledge-content {
margin: 0 12px;
}
.logo {
padding: 36px;
}
.avatar-grid {
margin: 0 12px;
}
}
}

View file

@ -1,20 +1,36 @@
{
"credits.title": "Scratch Credits and Contributors",
"credits.developers": "Scratch is designed and developed by the Lifelong Kindergarten Group at MIT Media Lab:",
"credits.title": "Scratch Credits & Contributors",
"credits.developers": "Scratch is designed, developed, and moderated by the Lifelong Kindergarten Group at MIT Media Lab:",
"credits.moderators": "The team of Scratch moderators manages, supports, and improves the Scratch online community:",
"credits.previousTitle": "Previous MIT Scratch Team Members",
"credits.previousBody": "Many important contributions have been made by previous Scratch Team members, including John Maloney (who led software development for the first decade of Scratch) and Andrés Monroy-Hernández (who led the development of the first Scratch community website). Other contributors include:",
"credits.partnersTitle": "Design and Development Partners",
"credits.researchersIntro": "Research on Scratch is being conducted by members of the MIT Scratch Team and researchers at other universities, including:",
"credits.partnersBody": "Paula Bontá and Brian Silverman, Playful Invention Company (who started contributing to the design of Scratch even before it was called Scratch).",
"credits.researchersTitle": "Scratch Researchers",
"credits.researchersBody": "<a href=\"https://scratch.mit.edu/info/research/\">Research on Scratch</a> is being conducted by members of the MIT Scratch Team and researchers at other universities, including Yasmin Kafai (who collaborated on the <a href=\"http://www.nsf.gov/awardsearch/showAward?AWD_ID=0325828\">initial NSF Scratch grant</a>) at the University of Pennsylvania Graduate School of Education, Karen Brennan (who leads the <a href=\"http://scratched.gse.harvard.edu/\">ScratchEd project</a>) at the Harvard Graduate School of Education, Benjamin Mako Hill at the University of Washington, Andrés Monroy Hernández at Microsoft Research, Mimi Ito and Crystle Martin at the University of California, Irvine, Quinn Burke at College of Charleston, Deborah Fields at Utah State University, and Kylie Peppler at Indiana University.",
"credits.researchersBody" : "{scratchResearchLink} is being conducted by members of the MIT Scratch Team and researchers at other universities, including:",
"credits.researchLinkText": "Research on Scratch",
"credits.researchersContributors" : "Yasmin Kafai (who collaborated on the {nsfLink}) at the University of Pennsylvania Graduate School of Education, Karen Brennan (who leads the {scratchEdLink}) at the Harvard Graduate School of Education, Benjamin Mako Hill at the University of Washington, Andrés Monroy Hernández at Microsoft Research, Mimi Ito and Crystle Martin at the University of California, Irvine, Quinn Burke at College of Charleston, Deborah Fields at Utah State University, and Kylie Peppler at Indiana University.",
"credits.researchNSFLinkText" : "initial NSF Scratch grant",
"credits.researchScratchEdLinkText" : "ScratchEd project",
"credits.acknowledgementsTitle": "Acknowledgements",
"credits.acknowledgementsContributors": "The following people have also contributed to the development and support of Scratch over the years:",
"credits.acknowledgementsTranslators": "With the help of <a href=\"http://wiki.scratch.mit.edu/wiki/Translators\">Scratch Translators</a> around the world, Scratch is available in many languages.",
"credits.acknowledgementsTranslators": "With the help of {translatorsLink} around the world, Scratch is available in many languages.",
"credits.acknowledgementsTranslatorsLinkText": "Scratch Translators",
"credits.acknowledgementsCommunity": "We greatly appreciate all of the contributions by members of the worldwide Scratch community, who have shaped the direction of Scratch by sharing their projects, comments, and ideas.",
"credits.acknowledgementsInfluencers": "The ideas of Seymour Papert and Alan Kay have deeply inspired and influenced our work on Scratch.",
"credits.supportersTitle": "Supporting Organizations",
"credits.supportersFinancialHeader": "The following organizations have provided major financial support for Scratch:",
"credits.supportersServicesHeader": "The following organizations donate their services to help keep the Scratch project running:",
"credits.supportersOpenHeader": "Scratch would not be possible without free and open source software, including:"
"credits.supportersOpenHeader": "Scratch would not be possible without free and open source software, including:",
"credits.currentSponsors": "Current Sponsors",
"credits.currentFinancialSupport": "The following organizations are providing major financial support for Scratch:",
"credits.translationsTitle": "Translations",
"credits.illustrationsTitle": "Illustrations",
"credits.acknowledgementsIllustrations": "Many thanks to the following artists for their contributions to the Scratch 3.0 sprite library:",
"credits.soundsTitle": "Sounds",
"credits.pastContributors": "Past Contributors",
"credits.pastContributorsThanks" : "Many important contributions have been made by previous Scratch Team members, including John Maloney (who led software development for the first decade of Scratch) and Andrés Monroy-Hernández (who led the development of the first Scratch community website).",
"credits.otherContributors": "Other contributors include:",
"credits.acknowledgementsSound": "Thanks to the following freesound.org artists:"
}

View file

@ -0,0 +1,242 @@
[
{
"userName": "labdalla",
"userId": 35687410,
"name": "Lena Abdalla"
},
{
"userName": "shaanmasala",
"userId": "29995562",
"name": "Yusuf Ahmad"
},
{
"userName": "ceebee",
"userId": 2755634,
"name": "christan balch"
},
{
"userName": "Zinnea",
"userId": 35911243,
"name": "Zo\u00eb Bentley"
},
{
"userName": "designerd",
"userId": 3581881,
"name": "Carl Bowman"
},
{
"userName": "leoburd",
"userId": 385,
"name": "Leo Burd"
},
{
"userName": "FredDog",
"userId": 2496866,
"name": "Jolie Castellucci"
},
{
"userName": "kittyloaf",
"userId": 27383273,
"name": "Karishma Chadha"
},
{
"userName": "SunnyDay4aBlueJay",
"userId": 24164779,
"name": "Ellen Daoust"
},
{
"userName": "shruti",
"userId": 3714374,
"name": "Shruti Dhariwal"
},
{
"userName": "Champ99",
"userId": 900283,
"name": "Champika Fernando"
},
{
"userName": "LiFaytheGoblin",
"userId": 1048810,
"name": "Linda Fernsel"
},
{
"userName": "dietbacon",
"userId": 24137617,
"name": "Mark Ferrell"
},
{
"userName": "rmiel",
"userId": 34557192,
"name": "Elizabeth Foster"
},
{
"userName": "lilyland",
"userId": 17184580,
"name": "Lily Gabaree"
},
{
"userName": "chrisg",
"userId": 1494,
"name": "Chris Garrity"
},
{
"userName": "Paddle2See",
"userId": 49156,
"name": "Mark Goff"
},
{
"userName": "GulpTea",
"userId": 26249744,
"name": "Joel Gritter"
},
{
"userName": "codubee",
"userId": 10866958,
"name": "Colby Gutierrez-Kraybill"
},
{
"userName": "khanning",
"userId": 1553886,
"name": "Kreg Hanning"
},
{
"userName": "pizzafordessert",
"userId": 22183577,
"name": "Sean Hickey"
},
{
"userName": "theladynico",
"userId": 35550237,
"name": "Nicole Hughes"
},
{
"userName": "sgcc_",
"userId": 21986973,
"name": "Paul Kaplan"
},
{
"userName": "dsquare",
"userId": 527836,
"name": "DD Liu"
},
{
"userName": "dinopickles",
"userId": 34607790,
"name": "Katelyn Mann"
},
{
"userName": "harakou",
"userId": 373646,
"name": "Dalton Miner"
},
{
"userName": "mwikali",
"userId": 24838781,
"name": "Marian Muthui"
},
{
"userName": "me_win",
"userId": 7664502,
"name": "My Nguyen"
},
{
"userName": "lob12",
"userId": 2860339,
"name": "Lisa O'Brien"
},
{
"userName": "",
"userId": "",
"name": "Abisola Okuk"
},
{
"userName": "KayOh",
"userId": "3018280",
"name": "Kristin Osiecki"
},
{
"userName": "scmb1",
"userId": 246290,
"name": "Sarah Otts"
},
{
"userName": "tarmelop",
"userId": 2286560,
"name": "Carmelo Presicce"
},
{
"userName": "quacht",
"userId": 25500116,
"name": "Tina Quach"
},
{
"userName": "mres",
"userId": 167,
"name": "Mitchel Resnick"
},
{
"userName": "ericr",
"userId": 159,
"name": "Eric Rosenbaum"
},
{
"userName": "natalie",
"userId": 169,
"name": "Natalie Rusk"
},
{
"userName": "raimondious",
"userId": 2584924,
"name": "Ray Schamp"
},
{
"userName": "speakvisually",
"userId": 3484484,
"name": "Eric Schilling"
},
{
"userName": "thisandagain",
"userId": 1709047,
"name": "Andrew Sliwinski"
},
{
"userName": "BrycedTea",
"userId": 2029640,
"name": "Bryce Taylor"
},
{
"userName": "jaleesa",
"userId": 2374106,
"name": "Jaleesa Trapp"
},
{
"userName": "cheddargirl",
"userId": 159139,
"name": "Franchette Viloria"
},
{
"userName": "wheelsonfire",
"userId": 10001044,
"name": "Ben Wheeler"
},
{
"userName": "achouse",
"userId": 4747093,
"name": "Annie Whitehouse"
},
{
"userName": "cwillisf",
"userId": 3532363,
"name": "Chris Willis-Ford"
},
{
"userName": "pondermake",
"userId": 26779669,
"name": "Kathy Wu"
},
{
"userName": "stymphalianbirb",
"userId": 2796185,
"name": "Julia Zimmerman"
}
]

View file

@ -0,0 +1,61 @@
[
{
"logoSrc": "../images/credits/siegelfamily.png",
"logoDestination": "https://www.siegelendowment.org",
"width": "146px"
},
{
"logoSrc": "../images/credits/smilegate.png",
"logoDestination": "https://www.smilegate.com/",
"width": "162px"
},
{
"logoSrc": "../svgs/credits/google.svg",
"logoDestination": "https://google.com/",
"width": "124px"
},
{
"textLogo": "Little Bluebridge Foundation",
"logoDestination": ""
},
{
"logoSrc": "../images/credits/legofoundation.png",
"logoDestination": "https://www.legofoundation.com",
"width": "240px"
},
{
"logoSrc": "../images/credits/lemann.png",
"logoDestination": "https://fundacaolemann.org.br/en/co-invest",
"width": "156px"
},
{
"logoSrc": "../svgs/credits/fastly.svg",
"logoDestination": "https://www.fastly.com/",
"width": "108px"
},
{
"logoSrc": "../images/credits/taledu.png",
"logoDestination": "https://en.100tal.com/",
"width": "188px"
},
{
"logoSrc": "../images/credits/lego-education.png",
"logoDestination": "https://education.lego.com",
"width": "194px"
},
{
"logoSrc": "../svgs/credits/cartoonnetwork.svg",
"logoDestination": "https://www.cartoonnetwork.com/",
"width": "78px"
},
{
"logoSrc": "../images/credits/brainpop.png",
"logoDestination": "https://www.brainpop.com/",
"width": "78px"
},
{
"logoSrc": "../svgs/credits/amazonws.svg",
"logoDestination": "https://aws.amazon.com/",
"width": "82px"
}
]

View file

@ -11,7 +11,7 @@
"faq.aboutScratchTitle":"What is Scratch, and what can I do with it?",
"faq.aboutScratchBody":"Scratch is a programming language and online community where you can create your own interactive stories, games, and animations -- and share your creations with others around the world. In the process of designing and programming Scratch projects, young people learn to think creatively, reason systematically, and work collaboratively. To learn more about Scratch, check out the <a href=\"/about\">About Scratch</a> page.",
"faq.makeGameTitle":"How do I make a game or animation with Scratch?",
"faq.makeGameBody":"Check out the <a href=\"/tips\">tips page</a> to see lots of ways to get started with Scratch. Or just <a href=\"/projects/editor/?tip_bar=getStarted\">dive in</a> to the project editor.",
"faq.makeGameBody":"Check out the <a href=\"/tips\">tips page</a> to see lots of ways to get started with Scratch. Or just <a href=\"/projects/editor/?tutorial=getStarted\">dive in</a> to the project editor.",
"faq.requirementsTitle":"What are the system requirements for Scratch?",
"faq.requirementsBody":"To run Scratch 2.0, you need to be using (1) a Windows, ChromeOS, Mac, or Linux computer; (2) a version of <a href=\"https://get.adobe.com/flashplayer/\">Adobe Flash Player</a> released on or after June 15, 2016; (3) a relatively recent web browser: one of the latest two versions of <a href=\"http://google.com/chrome/\">Chrome</a> (Windows, ChromeOS, Mac or Linux), <a href=\"http://www.mozilla.org/firefox/new/\">Firefox</a> (Windows or Mac only), <a href=\"https://support.apple.com/downloads/safari\">Safari</a> (Mac only), <a href=\"https://www.microsoft.com/windows/microsoft-edge\">Edge</a> (Windows only), or <a href=\"https://www.microsoft.com/download/internet-explorer.aspx\">Internet Explorer 11</a> (Windows only). If your computer doesnt meet these requirements, you can try downloading and installing <a href=\"/scratch_1.4\">Scratch 1.4</a>, which you can still use to share projects to the Scratch 2.0 website.",
"faq.offlineTitle":"Do you have a downloadable version so I can create and view projects offline?",

View file

@ -26,6 +26,8 @@ const Jobs = () => (
<div className="bottom">
<div className="inner">
<h3><FormattedMessage id="jobs.openings" /></h3>
<FormattedMessage id="jobs.nojobs" />
{/*
<ul>
<li>
<a href="https://www.media.mit.edu/about/job-opportunities/full-stack-engineer-lifelong-kindergarten/">
@ -36,6 +38,7 @@ const Jobs = () => (
</span>
</li>
</ul>
*/}
</div>
</div>
</div>

View file

@ -3,5 +3,6 @@
"jobs.joinScratchTeam": "Join the Scratch Team!",
"jobs.openings": "Current Job Openings",
"jobs.titleQuestion": "Want to work on an innovative project that is transforming the ways young people create, share, and learn?",
"jobs.workEnvironment":"Were seeking curious and motivated people to join our Scratch Team at the MIT Media Lab. We're a diverse group of educators, designers, and engineers, who work together in a playful, creative environment full of LEGO bricks, craft materials, and maker tools. We strongly value diversity, collaboration, and respect in the workplace."
"jobs.workEnvironment":"Were seeking curious and motivated people to join our Scratch Team at the MIT Media Lab. We're a diverse group of educators, designers, and engineers, who work together in a playful, creative environment full of LEGO bricks, craft materials, and maker tools. We strongly value diversity, collaboration, and respect in the workplace.",
"jobs.nojobs":"We don't have any open positions at this time. Please check back soon for opportunities!"
}

View file

@ -10,6 +10,7 @@ const React = require('react');
const Comment = require('../../../components/comment/comment.jsx');
const FlexRow = require('../../../components/flex-row/flex-row.jsx');
const SocialMessage = require('../../../components/social-message/social-message.jsx');
const thumbnailUrl = require('../../../lib/user-thumbnail');
class CommentMessage extends React.Component {
constructor (props) {
@ -164,7 +165,7 @@ class CommentMessage extends React.Component {
<img
alt={`${this.props.actorUsername}'s avatar`}
className="comment-message-info-img"
src={`https://cdn2.scratch.mit.edu/get_image/user/${this.props.actorId}_32x32.png`}
src={thumbnailUrl(this.props.actorId)}
/>
</a>
<Comment

View file

@ -24,7 +24,7 @@ const UserJoinMessage = props => (
</a>
),
makeProjectLink: (
<a href="/projects/editor/?tip_bar=getStarted">
<a href="/projects/editor/?tutorial=getStarted">
{props.intl.formatMessage({id: 'messages.userJoinMakeProject'})}
</a>
)

View file

@ -119,7 +119,7 @@ class MicroBit extends ExtensionLanding {
<a
download
className="download"
href="https://downloads.scratch.mit.edu/microbit/scratch-microbit-1.0.hex.zip"
href="https://downloads.scratch.mit.edu/microbit/scratch-microbit-1.1.0.hex.zip"
>
<FormattedMessage id="microbit.downloadHex" />
</a>

View file

@ -8,7 +8,7 @@
],
"videos":[
{
"image":"https://i.vimeocdn.com/video/528845372.webp?mw=900&amp;mh=583&amp;q=70",
"image":"https://i.vimeocdn.com/video/528845372.png?mw=900&amp;mh=583&amp;q=70",
"link":"//player.vimeo.com/video/134864477?title=0&byline=0&portrait=0"
}
],

View file

@ -8,7 +8,7 @@
],
"videos":[
{
"image":"https://i.vimeocdn.com/video/521248373.webp?mw=900&mh=583&q=70",
"image":"https://i.vimeocdn.com/video/521248373.png?mw=900&mh=583&q=70",
"link":"//player.vimeo.com/video/124055657?title=0&byline=0&portrait=0"
}
],

View file

@ -0,0 +1,39 @@
{
"parents.title": "For Parents",
"parents.intro": "Scratch is a programming language and an online community where children\n can program and share interactive media such as stories, games, and \nanimation with people from all over the world. As children create with \nScratch, they learn to think creatively, work collaboratively, and \nreason systematically. Scratch is designed and maintained by the \nLifelong Kindergarten group at the MIT Media Lab. ",
"parents.overview": "How it works",
"parents.faq": "FAQ",
"parents.overviewTitle": "How does Scratch work for children?",
"parents.overviewLearningTitle": "Learning",
"parents.overviewLearningBody": "For a one-page overview of what young people learn with Scratch, see {learningWithScratch}.\nRead an article on the {creativeLearningApproach}.",
"parents.learningWithScratchLinkText": "Learning with Scratch",
"parents.creativeLearningApproachLinkText": "Creative Learning Approach",
"parents.overviewCommunityTitle": "Community",
"parents.overviewCommunityBody": "We ask all participants on the site to follow the {communityGuidelines}.\nWe do not make private account information available to anyone. For more information, please see the {privacyPolicy}.",
"parents.communityGuidelinesLinkText": "Community Guidelines",
"parents.privacyPolicyLinkText": "Privacy Policy",
"parents.faqMoreAndAsk": "To find out more about Scratch, please see {faqPage}.\nYou can also ask questions in the {discussionForums}.\nIf you need to contact our staff team directly, click {contactUs} at the bottom of any page.",
"parents.faqLinkText": "Frequently Asked Questions",
"parents.faqDiscussionForumsLinkText": "Discussion Forums",
"parents.faqContactUsLinkText": "Contact Us",
"parents.faqAgeRangeTitle": "What is the age range for Scratch?",
"parents.faqAgeRangeBody": "While Scratch is primarily designed for 8 to 16 year olds, it is also used by people of all ages, including younger children with their parents. ",
"parents.faqResourcesTitle": "What resources are available for learning Scratch?",
"parents.faqResourcesBody": "If youre just getting started, theres a {stepByStepGuide} available inside Scratch, or you can download the {gettingStartedGuide}. The {scratchCards} provide a fun way to learn more. For an overview of Scratch resources, see {tips}.",
"parents.faqStepByStepGuideLinkText": "step-by-step guide",
"parents.faqGettingStartedGuideLinkText": "Getting Started guide (PDF)",
"parents.faqScratchCardsLinkText": "Scratch Cards",
"parents.faqTipsLinkText": "Tips",
"parents.faqCommunityTitle": "What is the Scratch online community?",
"parents.faqCommunityBody": "When participating in the Scratch online community, members can explore and experiment in an open learning community with other Scratch members from all backgrounds, ages, and interests. Members can share their work, get feedback, and learn from each other.",
"parents.faqGuidelinesTitle": "What are the guidelines for the Scratch online community?",
"parents.faqGuidelinesBody": "The MIT Scratch Team works with the community to maintain a friendly and respectful environment for people of all ages, races, ethnicities, religions, sexual orientations, and gender identities. You can help your child learn how to participate by reviewing the {communityGuidelines} together. Members are asked to comment constructively and to help keep the website friendly by reporting any content that does not follow the community guidelines. The Scratch Team works each day to manage activity on the site and respond to reports, with the help of tools such as the {CleanSpeak} profanity filter.",
"parents.faqCommunityGuidelinesLinkText": "community guidelines",
"parents.faqPrivacyPolicyTitle": "What is your privacy policy?",
"parents.faqPrivacyPolicyBody": "To protect children's online privacy, we limit what we collect during the signup process, and what we make public on the website. We don't sell or rent account information to anyone. You can find out more about our privacy policy on our {faqPage} page.",
"parents.faqFAQLinkText": "frequently asked questions page",
"parents.faqOfflineTitle": "Is there a way to use Scratch without participating online?",
"parents.faqOfflineBody": "Yes, the Scratch offline editor lets you create projects without joining or accessing the online community. Visit the {offline2} download page for instructions on how to install it on your computer. (If your computer does not support the latest version, try the {offline14}.) ",
"parents.faqOffline2LinkText": "Scratch 2.0 offline editor",
"parents.faqOffline14LinkText": "Scratch 1.4 offline editor"
}

View file

@ -0,0 +1,259 @@
const FormattedMessage = require('react-intl').FormattedMessage;
const React = require('react');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
const TitleBanner = require('../../components/title-banner/title-banner.jsx');
const Page = require('../../components/page/www/page.jsx');
const render = require('../../lib/render.jsx');
require('./parents.scss');
const Landing = () => (
<div className="parents">
<TitleBanner className="masthead">
<div className="inner">
<h1 className="title-banner-h1">
<FormattedMessage id="parents.title" />
</h1>
<FlexRow className="masthead-info">
<p className="title-banner-p intro">
<FormattedMessage id="parents.intro" />
</p>
<div className="ted-talk">
<iframe
allowFullScreen
frameBorder="0"
src="https://www.youtube.com/embed/3dciD9FO9mc"
/>
</div>
</FlexRow>
</div>
<div className="band">
<SubNavigation className="inner">
<a href="#overview">
<li>
<FormattedMessage id="parents.overview" />
</li>
</a>
<a href="#faq">
<li>
<FormattedMessage id="parents.faq" />
</li>
</a>
</SubNavigation>
</div>
</TitleBanner>
<div className="inner">
<section id="overview">
<span className="nav-spacer" />
<h2>
<FormattedMessage id="parents.overviewTitle" />
</h2>
<FlexRow className="general-usage">
<div>
<h3><FormattedMessage id="parents.overviewLearningTitle" /></h3>
<p>
<FormattedMessage
id="parents.overviewLearningBody"
values={{
learningWithScratch: (
<a href="http://llk.media.mit.edu/scratch/Learning-with-Scratch.pdf">
<FormattedMessage
id="parents.learningWithScratchLinkText"
/>
</a>
),
creativeLearningApproach: (
<a href="http://www.edutopia.org/kindergarten-creativity-collaboration-lifelong-learning">
<FormattedMessage
id="parents.creativeLearningApproachLinkText"
/>
</a>
)
}}
/>
</p>
</div>
<div>
<h3><FormattedMessage id="parents.overviewCommunityTitle" /></h3>
<p>
<FormattedMessage
id="parents.overviewCommunityBody"
values={{
communityGuidelines: (
<a href="/community_guidelines">
<FormattedMessage
id="parents.communityGuidelinesLinkText"
/>
</a>
),
privacyPolicy: (
<a href="/privacy_policy">
<FormattedMessage
id="parents.privacyPolicyLinkText"
/>
</a>
)
}}
/>
</p>
</div>
</FlexRow>
</section>
</div>
<TitleBanner className="faq-banner">
<div className="inner">
<section id="faq">
<span className="nav-spacer" />
<h2><FormattedMessage id="parents.faq" /></h2>
<p>
<FormattedMessage
id="parents.faqMoreAndAsk"
values={{
faqPage: (
<a href="/info/faq">
<FormattedMessage
id="parents.faqLinkText"
/>
</a>
),
discussionForums: (
<a href="/discuss">
<FormattedMessage
id="parents.faqDiscussionForumsLinkText"
/>
</a>
),
contactUs: (
<a href="/contact-us">
<FormattedMessage
id="parents.faqContactUsLinkText"
/>
</a>
)
}}
/>
</p>
<FlexRow className="three-col-row">
<div className="faq column">
<h3><FormattedMessage id="parents.faqAgeRangeTitle" /></h3>
<p>
<FormattedMessage id="parents.faqAgeRangeBody" />
</p>
</div>
<div className="faq column">
<h3><FormattedMessage id="parents.faqResourcesTitle" /></h3>
<p>
<FormattedMessage
id="parents.faqResourcesBody"
values={{
stepByStepGuide: (
<a href="/projects/editor/?tip_bar=getStarted">
<FormattedMessage
id="parents.faqStepByStepGuideLinkText"
/>
</a>
),
gettingStartedGuide: (
<a href="https://cdn.scratch.mit.edu/scratchr2/static/__edf64cc2d5d5da51528c169e65053195__//pdfs/help/Getting-Started-Guide-Scratch2.pdf">
<FormattedMessage
id="parents.faqGettingStartedGuideLinkText"
/>
</a>
),
scratchCards: (
<a href="/info/cards">
<FormattedMessage
id="parents.faqScratchCardsLinkText"
/>
</a>
),
tips: (
<a href="/tips">
<FormattedMessage
id="parents.faqTipsLinkText"
/>
</a>
)
}}
/>
</p>
</div>
<div className="faq column">
<h3><FormattedMessage id="parents.faqCommunityTitle" /></h3>
<p>
<FormattedMessage id="parents.faqCommunityBody" />
</p>
</div>
<div className="faq column">
<h3><FormattedMessage id="parents.faqGuidelinesTitle" /></h3>
<p>
<FormattedMessage
id="parents.faqGuidelinesBody"
values={{
communityGuidelines: (
<a href="/community_guidelines">
<FormattedMessage
id="parents.faqCommunityGuidelinesLinkText"
/>
</a>
),
CleanSpeak: (
<a href="http://www.inversoft.com/features/profanity-filter/">CleanSpeak</a>
)
}}
/>
</p>
</div>
<div className="faq column">
<h3><FormattedMessage id="parents.faqPrivacyPolicyTitle" /></h3>
<p>
<FormattedMessage
id="parents.faqPrivacyPolicyBody"
values={{
faqPage: (
<a href="/info/faq">
<FormattedMessage
id="parents.faqFAQLinkText"
/>
</a>
)
}}
/>
</p>
</div>
<div className="faq column">
<h3><FormattedMessage id="parents.faqOfflineTitle" /></h3>
<p>
<FormattedMessage
id="parents.faqOfflineBody"
values={{
offline2: (
<a href="/scratch2download">
<FormattedMessage
id="parents.faqOffline2LinkText"
/>
</a>
),
offline14: (
<a href="/scratch_1.4">
<FormattedMessage
id="parents.faqOffline14LinkText"
/>
</a>
)
}}
/>
</p>
</div>
</FlexRow>
</section>
</div>
</TitleBanner>
</div>
);
render(<Page><Landing /></Page>, document.getElementById('app'));

View file

@ -0,0 +1,269 @@
@import "../../colors";
@import "../../frameless";
$parents-spot: $ui-blue-dark;
$story-width: $cols3;
#view {
padding: 0;
}
.parents {
.intro {
margin: 0;
}
b {
font-weight: bold;
}
a {
white-space: normal;
}
.title-banner {
&.masthead {
background-color: $parents-spot;
padding-bottom: 0;
h1 {
margin: 0;
color: $ui-white;
}
.masthead-info {
display: flex;
align-items: center;
justify-content: space-between;
p {
margin: 0;
max-width: $cols6;
text-align: left;
color: $ui-white;
a {
border-bottom: 1px solid $ui-white;
color: $ui-white;
}
}
}
.ted-talk {
position: relative;
margin-bottom: $gutter;
border: 2px solid $ui-border;
border-radius: 10px;
width: $cols4;
height: $cols4 * .5625;
overflow: hidden;
iframe {
border: 0;
width: inherit;
height: inherit;
}
}
.band {
$band-color: hsla(360, 100, 100, .15);
margin-top: 2rem;
background-color: $band-color;
padding: 1rem 0;
}
.sub-nav {
text-align: left;
li {
margin: 0 .5rem 0 0;
}
}
}
&.faq-banner {
margin-bottom: 0;
background-color: $ui-gray;
}
}
.general-usage {
justify-content: space-between;
p {
max-width: $cols5;
}
}
section {
margin-bottom: 3rem;
}
#overview,
#faq {
.nav-spacer {
display: block;
visibility: hidden;
margin-top: -50px; // height of nav bar
height: 50px;
}
}
}
//4 columns
@media only screen and (max-width: $mobile - 1) {
.title-banner {
&.masthead {
padding-bottom: 2rem;
}
.band {
display: none;
}
}
.flex-row {
&.sidebar-row {
.body-copy {
width: 100%;
}
}
}
.overview {
text-align: center;
}
.flex-row {
align-items: center;
}
.general-usage {
align-items: center;
}
}
//6 columns
@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) {
.flex-row {
&.sidebar-row {
.body-copy {
width: 100%;
}
.sidebar {
margin: 0 auto;
}
}
}
.overview {
text-align: center;
}
.flex-row {
align-items: center;
}
.general-usage {
align-items: center;
}
}
//8 columns
@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) {
.masthead {
h1 {
text-align: center;
}
.subnav {
justify-content: center;
}
}
.masthead-info {
p {
width: $cols4;
}
iframe {
width: $cols4;
box-sizing: border-box;
}
}
#view {
text-align: left;
}
.flex-row {
&.sidebar-row {
.body-copy {
width: 100%;
}
.sidebar {
margin: 0 auto;
}
}
}
#overview {
h2 {
text-align: center;
}
.intro {
margin-bottom: 1rem;
text-align: center;
}
.general-usage {
margin-bottom: 2rem;
p {
margin: .25em 0;
max-width: $cols8;
text-align: center;
}
}
}
section {
p {
width: 100%;
}
}
#left {
width: $cols4;
}
}
// 12 columns
@media only screen and (min-width: $desktop) {
.masthead {
h1 {
text-align: left;
}
.sub-nav {
justify-content: flex-start;
}
}
.general-usage {
align-items: flex-start;
}
}

View file

@ -0,0 +1,34 @@
const PropTypes = require('prop-types');
const React = require('react');
const classNames = require('classnames');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const Button = require('../../components/forms/button.jsx');
require('./banner.scss');
const Banner = ({className, message, actionMessage, onAction}) => (
<div className={classNames('banner-outer', className)}>
<FlexRow className="inner banner-inner">
<span className="banner-text">
{message}
</span>
{actionMessage && onAction && (
<Button
className="banner-button"
onClick={onAction}
>
{actionMessage}
</Button>
)}
</FlexRow>
</div>
);
Banner.propTypes = {
actionMessage: PropTypes.node,
className: PropTypes.string,
message: PropTypes.node.isRequired,
onAction: PropTypes.func
};
module.exports = Banner;

View file

@ -0,0 +1,53 @@
@import "../../colors";
$navigation-height: 50px;
.banner-outer {
display: flex;
background-color: $ui-orange-25percent;
width: 100%;
min-height: 4rem;
overflow: hidden;
color: $ui-orange-high-contrast;
font-size: .875rem;
font-weight: bold;
align-self: center;
}
.banner-outer.banner-danger {
background-color: $ui-red-25percent;
color: $ui-red;
}
.banner-outer.banner-success {
background-color: $ui-green-35percent;
color: $ui-mint-green;
}
.banner-inner {
align-items: center;
justify-content: space-between;
flex-wrap: nowrap;
}
.banner-text {
padding: .5rem 0;
}
.banner-button {
margin-top: .75rem;
margin-bottom: .75rem;
border-radius: .25rem;
background-color: $ui-orange;
padding-top: .6875rem;
padding-bottom: .75rem;
min-width: 6rem;
max-width: 16rem;
min-height: 2.5rem;
max-height: 6rem;
font-size: .875rem;
}
.banner-danger .banner-button {
background-color: $ui-red;
}

View file

@ -158,7 +158,7 @@ class Comment extends React.Component {
className="comment-report"
onClick={this.handleReport}
>
<FormattedMessage id="comments.report" />
<FormattedMessage id="general.report" />
</span>
)}
</React.Fragment>

View file

@ -289,3 +289,7 @@
margin-right: -50%;
}
}
/* Hide the action list buttons (delete/report/restore) until hover over */
.comment .action-list { opacity: 0; }
.comment:hover .action-list { opacity: 1; }

View file

@ -5,14 +5,16 @@
"project.titleMaxLength": "Title is too long",
"project.musicExtensionChip": "Music",
"project.penExtensionChip": "Pen",
"project.speechExtensionChip": "Google Speech",
"project.translateExtensionChip": "Google Translate",
"project.text2SpeechChip": "Text to Speech",
"project.translateChip": "Translate",
"project.videoMotionChip": "Video Motion",
"project.needsConnection": "Needs Connection",
"project.comments.header": "Comments",
"project.comments.turnOff": "Turn off commenting",
"project.comments.turnedOff": "Sorry, comment posting has been turned off for this project.",
"project.share.notShared": "This project is not shared — so only you can see it. Click share to let everyone see it!",
"project.share.sharedLong": "Congratulations on sharing your project! Other people can now try it out, give comments, and remix it.",
"project.share.sharedShort": "Your project is now shared.",
"project.share.shareButton": "Share",
"project.seeInsideButton": "See inside",
"project.remixButton": "Remix",
@ -20,5 +22,9 @@
"project.inviteToRemix": "Invite user to remix",
"project.instructionsLabel": "Instructions",
"project.notesAndCreditsLabel": "Notes and Credits",
"project.credit": "Thanks to {userLink} for the original project {projectLink}."
"project.credit": "Thanks to {userLink} for the original project {projectLink}.",
"project.deletedBanner": "Note: This project is in the trash folder",
"project.moderationInfoLabel": "Moderation Info",
"project.numScripts": "{number} scripts",
"project.numSprites": "{number} sprites"
}

View file

@ -0,0 +1,58 @@
const PropTypes = require('prop-types');
const React = require('react');
const FormattedDate = require('react-intl').FormattedDate;
const FormattedMessage = require('react-intl').FormattedMessage;
const FormattedTime = require('react-intl').FormattedTime;
const FlexRow = require('../../components/flex-row/flex-row.jsx');
require('./mod-info.scss');
const ModInfo = props => (
<FlexRow className="mod-info">
{/* eslint-disable react/jsx-sort-props */}
{props.revisedDate &&
<div className="mod-date">
<FormattedDate
value={Date.parse(props.revisedDate)}
day="2-digit"
month="short"
year="numeric"
/>
{' - '}
<FormattedTime
value={Date.parse(props.revisedDate)}
hour="numeric"
minute="numeric"
timeZoneName="short"
/>
</div>
}
{/* eslint-enable react/jsx-sort-props */}
<div className="mod-sprites">
<FormattedMessage
id="project.numSprites"
values={{
number: props.sprites
}}
/>
</div>
<div className="mod-scripts">
<FormattedMessage
id="project.numScripts"
values={{
number: props.scripts
}}
/>
</div>
</FlexRow>
);
ModInfo.propTypes = {
revisedDate: PropTypes.string,
scripts: PropTypes.number,
sprites: PropTypes.number
};
module.exports = ModInfo;

View file

@ -0,0 +1,64 @@
@import "../../frameless";
.mod-info {
line-height: 2rem;
justify-content: flex-start;
@media #{$medium-and-smaller} {
margin: 0;
width: 100%;
justify-content: center;
flex-direction: row;
}
& > div {
@media #{$medium-and-smaller} {
padding: 0 1rem;
}
}
}
.mod-date,
.mod-sprites,
.mod-scripts {
display: inline;
padding-right: 2rem;
font-size: .875rem;
&:before {
display: inline-block;
margin-right: .5rem;
background-repeat: no-repeat;
background-position: center center;
background-size: contain;
width: 1.5rem;
height: 1.5rem;
vertical-align: -.35rem;
content: "";
}
}
.mod-date {
&:before {
opacity: .5;
background-image: url("/svgs/project/last-revised.svg");
}
}
.mod-sprites {
&:before {
opacity: .5;
background-image: url("/svgs/project/sprite-count.svg");
}
}
.mod-scripts {
&:before {
opacity: .5;
background-image: url("/svgs/project/block-count.svg");
}
}

View file

@ -15,7 +15,8 @@ const decorateText = require('../../lib/decorate-text.jsx');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const Button = require('../../components/forms/button.jsx');
const Avatar = require('../../components/avatar/avatar.jsx');
const ShareBanner = require('./share-banner.jsx');
const Banner = require('./banner.jsx');
const ModInfo = require('./mod-info.jsx');
const RemixCredit = require('./remix-credit.jsx');
const RemixList = require('./remix-list.jsx');
const Stats = require('./stats.jsx');
@ -25,6 +26,7 @@ const InplaceInput = require('../../components/forms/inplace-input.jsx');
const TopLevelComment = require('./comment/top-level-comment.jsx');
const ComposeComment = require('./comment/compose-comment.jsx');
const ExtensionChip = require('./extension-chip.jsx');
const thumbnailUrl = require('../../lib/user-thumbnail');
const projectShape = require('./projectshape.jsx').projectShape;
require('./preview.scss');
@ -42,13 +44,17 @@ const onKeyPress = e => {
};
const PreviewPresentation = ({
addToStudioOpen,
assetHost,
backpackOptions,
backpackHost,
canAddToStudio,
canDeleteComments,
canRemix,
canReport,
canRestoreComments,
canSave,
canShare,
canUseBackpack,
cloudHost,
comments,
editable,
@ -58,55 +64,111 @@ const PreviewPresentation = ({
intl,
isFullScreen,
isLoggedIn,
isNewScratcher,
isShared,
loved,
justShared,
loveCount,
loved,
modInfo,
moreCommentsToLoad,
originalInfo,
parentInfo,
projectHost,
projectId,
projectInfo,
remixes,
reportOpen,
replies,
addToStudioOpen,
projectStudios,
singleCommentId,
userOwnsProject,
onAddComment,
onAddToStudioClicked,
onAddToStudioClosed,
onCopyProjectLink,
onDeleteComment,
onFavoriteClicked,
onLoadMore,
onLoveClicked,
onRemix,
onReportClicked,
onReportClose,
onReportComment,
onReportSubmit,
onRestoreComment,
onAddToStudioClicked,
onAddToStudioClosed,
onToggleStudio,
onToggleComments,
onSeeAllComments,
onSeeInside,
onShare,
onUpdate
onToggleComments,
onToggleStudio,
onUpdate,
onUpdateProjectId,
originalInfo,
parentInfo,
projectHost,
projectId,
projectInfo,
projectStudios,
remixes,
replies,
reportOpen,
showModInfo,
singleCommentId,
userOwnsProject,
visibilityInfo
}) => {
const shareDate = ((projectInfo.history && projectInfo.history.shared)) ? projectInfo.history.shared : '';
const revisedDate = ((projectInfo.history && projectInfo.history.modified)) ? projectInfo.history.modified : '';
// Allow embedding html in banner messages coming from the server
const embedCensorMessage = message => (
// eslint-disable-next-line react/no-danger
<span dangerouslySetInnerHTML={{__html: message}} />
);
let banner;
if (visibilityInfo.deleted) { // If both censored and deleted, prioritize deleted banner
banner = (<Banner
className="banner-danger"
message={<FormattedMessage id="project.deletedBanner" />}
/>);
} else if (visibilityInfo.censored) {
if (visibilityInfo.reshareable) {
banner = (<Banner
actionMessage={<FormattedMessage id="project.share.shareButton" />}
className="banner-danger"
message={embedCensorMessage(visibilityInfo.censorMessage)}
onAction={onShare}
/>);
} else {
banner = (<Banner
className="banner-danger"
message={embedCensorMessage(visibilityInfo.censorMessage)}
/>);
}
} else if (canShare) {
if (isShared && justShared) { // if was shared a while ago, don't show any share banner
if (isNewScratcher) {
banner = (<Banner
className="banner-success"
message={<FormattedMessage id="project.share.sharedLong" />}
/>);
} else {
banner = (<Banner
className="banner-success"
message={<FormattedMessage id="project.share.sharedShort" />}
/>);
}
} else if (!isShared) {
banner = (<Banner
actionMessage={<FormattedMessage id="project.share.shareButton" />}
message={<FormattedMessage id="project.share.notShared" />}
onAction={onShare}
/>);
}
}
return (
<div className="preview">
{canShare && !isShared && (
<ShareBanner onShare={onShare} />
)}
{ projectInfo && projectInfo.author && projectInfo.author.id && (
<React.Fragment>
{banner}
<div className="inner">
<FlexRow className="preview-row force-row">
<FlexRow className="project-header">
<a href={`/users/${projectInfo.author.username}`}>
<Avatar
alt={projectInfo.author.username}
src={`https://cdn2.scratch.mit.edu/get_image/user/${projectInfo.author.id}_48x48.png`}
src={thumbnailUrl(projectInfo.author.id, 48)}
/>
</a>
<div className="title">
@ -142,9 +204,11 @@ const PreviewPresentation = ({
</FlexRow>
<MediaQuery minWidth={frameless.mobile}>
<div className="project-buttons">
{/* TODO: Hide Remix button for now until implemented */}
{(!userOwnsProject && false) &&
<Button className="button remix-button">
{canRemix &&
<Button
className="button remix-button"
onClick={onRemix}
>
<FormattedMessage id="project.remixButton" />
</Button>
}
@ -162,14 +226,18 @@ const PreviewPresentation = ({
<IntlGUI
isPlayerOnly
assetHost={assetHost}
backpackOptions={backpackOptions}
backpackHost={backpackHost}
backpackVisible={canUseBackpack}
basePath="/"
canRemix={canRemix}
canSave={canSave}
className="guiPlayer"
cloudHost={cloudHost}
isFullScreen={isFullScreen}
previewInfoVisible="false"
projectHost={projectHost}
projectId={projectId}
onUpdateProjectId={onUpdateProjectId}
/>
</div>
<MediaQuery maxWidth={frameless.tablet - 1}>
@ -191,6 +259,7 @@ const PreviewPresentation = ({
shareDate={shareDate}
onAddToStudioClicked={onAddToStudioClicked}
onAddToStudioClosed={onAddToStudioClosed}
onCopyProjectLink={onCopyProjectLink}
onReportClicked={onReportClicked}
onReportClose={onReportClose}
onReportSubmit={onReportSubmit}
@ -321,6 +390,7 @@ const PreviewPresentation = ({
shareDate={shareDate}
onAddToStudioClicked={onAddToStudioClicked}
onAddToStudioClosed={onAddToStudioClosed}
onCopyProjectLink={onCopyProjectLink}
onReportClicked={onReportClicked}
onReportClose={onReportClose}
onReportSubmit={onReportSubmit}
@ -328,6 +398,21 @@ const PreviewPresentation = ({
/>
</FlexRow>
</MediaQuery>
{showModInfo &&
<React.Fragment>
<div className="project-textlabel">
<FormattedMessage id="project.moderationInfoLabel" />
</div>
<ModInfo
revisedDate={revisedDate}
scripts={modInfo.scripts}
sprites={modInfo.sprites}
/>
</React.Fragment>
}
<MediaQuery minWidth={frameless.tablet}>
<FlexRow className="preview-row">
<FlexRow className="extension-list">
@ -365,23 +450,26 @@ const PreviewPresentation = ({
) : null}
</FlexRow>
<FlexRow className="comments-root-reply">
{projectInfo.comments_allowed ? (
isLoggedIn ? (
<ComposeComment
projectId={projectId}
onAddComment={onAddComment}
/>
{/* Do not show the top-level comment form in single comment mode */}
{!singleCommentId && (
<FlexRow className="comments-root-reply">
{projectInfo.comments_allowed ? (
isLoggedIn ? (
<ComposeComment
projectId={projectId}
onAddComment={onAddComment}
/>
) : (
/* TODO add box for signing in to leave a comment */
null
)
) : (
/* TODO add box for signing in to leave a comment */
null
)
) : (
<div className="comments-turned-off">
<FormattedMessage id="project.comments.turnedOff" />
</div>
)}
</FlexRow>
<div className="comments-turned-off">
<FormattedMessage id="project.comments.turnedOff" />
</div>
)}
</FlexRow>
)}
<FlexRow className="comments-list">
{comments.map(comment => (
@ -415,6 +503,14 @@ const PreviewPresentation = ({
<FormattedMessage id="general.loadMore" />
</Button>
}
{!!singleCommentId &&
<Button
className="button load-more-button"
onClick={onSeeAllComments}
>
<FormattedMessage id="general.seeAllComments" />
</Button>
}
</FlexRow>
</div>
<FlexRow className="column">
@ -433,15 +529,15 @@ const PreviewPresentation = ({
PreviewPresentation.propTypes = {
addToStudioOpen: PropTypes.bool,
assetHost: PropTypes.string,
backpackOptions: PropTypes.shape({
host: PropTypes.string,
visible: PropTypes.bool
}),
backpackHost: PropTypes.string,
canAddToStudio: PropTypes.bool,
canDeleteComments: PropTypes.bool,
canRemix: PropTypes.bool,
canReport: PropTypes.bool,
canRestoreComments: PropTypes.bool,
canSave: PropTypes.bool,
canShare: PropTypes.bool,
canUseBackpack: PropTypes.bool,
cloudHost: PropTypes.string,
comments: PropTypes.arrayOf(PropTypes.object),
editable: PropTypes.bool,
@ -451,27 +547,37 @@ PreviewPresentation.propTypes = {
intl: intlShape,
isFullScreen: PropTypes.bool,
isLoggedIn: PropTypes.bool,
isNewScratcher: PropTypes.bool,
isShared: PropTypes.bool,
justShared: PropTypes.bool,
loveCount: PropTypes.number,
loved: PropTypes.bool,
modInfo: PropTypes.shape({
scripts: PropTypes.number,
sprites: PropTypes.number
}),
moreCommentsToLoad: PropTypes.bool,
onAddComment: PropTypes.func,
onAddToStudioClicked: PropTypes.func,
onAddToStudioClosed: PropTypes.func,
onCopyProjectLink: PropTypes.func,
onDeleteComment: PropTypes.func,
onFavoriteClicked: PropTypes.func,
onLoadMore: PropTypes.func,
onLoveClicked: PropTypes.func,
onRemix: PropTypes.func,
onReportClicked: PropTypes.func.isRequired,
onReportClose: PropTypes.func.isRequired,
onReportComment: PropTypes.func.isRequired,
onReportSubmit: PropTypes.func.isRequired,
onRestoreComment: PropTypes.func,
onSeeAllComments: PropTypes.func,
onSeeInside: PropTypes.func,
onShare: PropTypes.func,
onToggleComments: PropTypes.func,
onToggleStudio: PropTypes.func,
onUpdate: PropTypes.func,
onUpdateProjectId: PropTypes.func,
originalInfo: projectShape,
parentInfo: projectShape,
projectHost: PropTypes.string,
@ -481,8 +587,15 @@ PreviewPresentation.propTypes = {
remixes: PropTypes.arrayOf(PropTypes.object),
replies: PropTypes.objectOf(PropTypes.array),
reportOpen: PropTypes.bool,
showModInfo: PropTypes.bool,
singleCommentId: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]),
userOwnsProject: PropTypes.bool
userOwnsProject: PropTypes.bool,
visibilityInfo: PropTypes.shape({
censored: PropTypes.bool,
censorMessage: PropTypes.string,
deleted: PropTypes.bool,
reshareable: PropTypes.bool
})
};
module.exports = injectIntl(PreviewPresentation);

View file

@ -1,750 +1,29 @@
// preview view can show either project page or editor page;
// idea is that we shouldn't require a page reload to switch back and forth
const bindAll = require('lodash.bindall');
const React = require('react');
const PropTypes = require('prop-types');
const connect = require('react-redux').connect;
const injectIntl = require('react-intl').injectIntl;
const parser = require('scratch-parser');
const Page = require('../../components/page/www/page.jsx');
const render = require('../../lib/render.jsx');
const storage = require('../../lib/storage.js').default;
const log = require('../../lib/log');
const EXTENSION_INFO = require('../../lib/extensions.js').default;
const jar = require('../../lib/jar.js');
const PreviewPresentation = require('./presentation.jsx');
const projectShape = require('./projectshape.jsx').projectShape;
const Registration = require('../../components/registration/registration.jsx');
const ConnectedLogin = require('../../components/login/connected-login.jsx');
const CanceledDeletionModal = require('../../components/login/canceled-deletion-modal.jsx');
const sessionActions = require('../../redux/session.js');
const navigationActions = require('../../redux/navigation.js');
const previewActions = require('../../redux/preview.js');
const frameless = require('../../lib/frameless');
const isSupportedBrowser = require('../../lib/supported-browser').default;
const UnsupportedBrowser = require('./unsupported-browser.jsx');
const GUI = require('scratch-gui');
const IntlGUI = injectIntl(GUI.default);
class Preview extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'addEventListeners',
'fetchCommunityData',
'handleAddComment',
'handleDeleteComment',
'handleToggleStudio',
'handleFavoriteToggle',
'handleLoadMore',
'handleLoveToggle',
'handlePopState',
'handleReportClick',
'handleReportClose',
'handleReportComment',
'handleReportSubmit',
'handleRestoreComment',
'handleAddToStudioClick',
'handleAddToStudioClose',
'handleSeeInside',
'handleShare',
'handleUpdateProjectId',
'handleUpdateProjectTitle',
'handleUpdate',
'handleToggleComments',
'initCounts',
'pushHistory',
'renderLogin',
'setScreenFromOrientation'
]);
const pathname = window.location.pathname.toLowerCase();
const parts = pathname.split('/').filter(Boolean);
// parts[0]: 'preview'
// parts[1]: either :id or 'editor'
// parts[2]: undefined if no :id, otherwise either 'editor' or 'fullscreen'
// Get single-comment id from url hash, using the #comments-{id} scheme from scratch2
const commentHashPrefix = '#comments-';
const singleCommentId = window.location.hash.indexOf(commentHashPrefix) !== -1 &&
parseInt(window.location.hash.replace(commentHashPrefix, ''), 10);
this.state = {
extensions: [],
favoriteCount: 0,
loveCount: 0,
projectId: parts[1] === 'editor' ? '0' : parts[1],
singleCommentId: singleCommentId,
addToStudioOpen: false,
reportOpen: false
};
this.addEventListeners();
/* In the beginning, if user is on mobile and landscape, go to fullscreen */
this.setScreenFromOrientation();
}
componentDidUpdate (prevProps, prevState) {
if (this.state.projectId > 0 &&
((this.props.sessionStatus !== prevProps.sessionStatus &&
this.props.sessionStatus === sessionActions.Status.FETCHED) ||
(this.state.projectId !== prevState.projectId))) {
this.fetchCommunityData();
this.getExtensions(this.state.projectId);
}
if (this.state.projectId === '0' && this.state.projectId !== prevState.projectId) {
this.props.resetProject();
}
if (this.props.projectInfo.id !== prevProps.projectInfo.id) {
if (typeof this.props.projectInfo.id === 'undefined') {
this.initCounts(0, 0);
} else {
this.initCounts(this.props.projectInfo.stats.favorites, this.props.projectInfo.stats.loves);
if (this.props.projectInfo.remix.parent !== null) {
this.props.getParentInfo(this.props.projectInfo.remix.parent);
}
if (this.props.projectInfo.remix.root !== null &&
this.props.projectInfo.remix.root !== this.props.projectInfo.remix.parent
) {
this.props.getOriginalInfo(this.props.projectInfo.remix.root);
}
}
}
if (this.props.playerMode !== prevProps.playerMode || this.props.fullScreen !== prevProps.fullScreen) {
this.pushHistory(history.state === null);
}
}
componentWillUnmount () {
this.removeEventListeners();
}
addEventListeners () {
window.addEventListener('popstate', this.handlePopState);
window.addEventListener('orientationchange', this.setScreenFromOrientation);
}
removeEventListeners () {
window.removeEventListener('popstate', this.handlePopState);
window.removeEventListener('orientationchange', this.setScreenFromOrientation);
}
fetchCommunityData () {
if (this.props.userPresent) {
const username = this.props.user.username;
const token = this.props.user.token;
if (this.state.singleCommentId) {
this.props.getCommentById(this.state.projectId, this.state.singleCommentId,
this.props.isAdmin, token);
} else {
this.props.getTopLevelComments(this.state.projectId, this.props.comments.length,
this.props.isAdmin, token);
}
this.props.getProjectInfo(this.state.projectId, token);
this.props.getRemixes(this.state.projectId, token);
this.props.getProjectStudios(this.state.projectId, token);
this.props.getCuratedStudios(username);
this.props.getFavedStatus(this.state.projectId, username, token);
this.props.getLovedStatus(this.state.projectId, username, token);
} else {
if (this.state.singleCommentId) {
this.props.getCommentById(this.state.projectId, this.state.singleCommentId);
} else {
this.props.getTopLevelComments(this.state.projectId, this.props.comments.length);
}
this.props.getProjectInfo(this.state.projectId);
this.props.getRemixes(this.state.projectId);
this.props.getProjectStudios(this.state.projectId);
}
}
setScreenFromOrientation () {
/*
* If the user is on a mobile device, switching to
* landscape format should make the fullscreen mode active
*/
const isMobileDevice = screen.height <= frameless.mobile || screen.width <= frameless.mobile;
const isAModalOpen = this.state.addToStudioOpen || this.state.reportOpen;
if (this.props.playerMode && isMobileDevice && !isAModalOpen) {
const isLandscape = screen.height < screen.width;
if (isLandscape) {
this.props.setFullScreen(true);
} else {
this.props.setFullScreen(false);
}
}
}
getExtensions (projectId) {
if (projectId > 0) {
storage
.load(storage.AssetType.Project, projectId, storage.DataFormat.JSON)
.then(projectAsset => { // NOTE: this is turning up null, breaking the line below.
let input = projectAsset.data;
if (typeof input === 'object' && !(input instanceof ArrayBuffer) &&
!ArrayBuffer.isView(input)) { // taken from scratch-vm
// If the input is an object and not any ArrayBuffer
// or an ArrayBuffer view (this includes all typed arrays and DataViews)
// turn the object into a JSON string, because we suspect
// this is a project.json as an object
// validate expects a string or buffer as input
// TODO not sure if we need to check that it also isn't a data view
input = JSON.stringify(input);
}
parser(projectAsset.data, false, (err, projectData) => {
if (err) {
log.error(`Unhandled project parsing error: ${err}`);
return;
}
const extensionSet = new Set();
if (projectData[0].extensions) {
projectData[0].extensions.forEach(extension => {
extensionSet.add(EXTENSION_INFO[extension]);
});
}
this.setState({
extensions: Array.from(extensionSet)
});
});
});
} else { // projectId is default or invalid; empty the extensions array
this.setState({
extensions: []
});
}
}
handleToggleComments () {
this.props.updateProject(
this.props.projectInfo.id,
{comments_allowed: !this.props.projectInfo.comments_allowed},
this.props.user.username,
this.props.user.token
);
}
handleAddComment (comment, topLevelCommentId) {
this.props.handleAddComment(comment, topLevelCommentId);
}
handleDeleteComment (id, topLevelCommentId) {
this.props.handleDeleteComment(this.state.projectId, id, topLevelCommentId, this.props.user.token);
}
handleReportComment (id, topLevelCommentId) {
this.props.handleReportComment(this.state.projectId, id, topLevelCommentId, this.props.user.token);
}
handleRestoreComment (id, topLevelCommentId) {
this.props.handleRestoreComment(this.state.projectId, id, topLevelCommentId, this.props.user.token);
}
handleReportClick () {
this.setState({reportOpen: true});
}
handleReportClose () {
this.setState({reportOpen: false});
}
handleAddToStudioClick () {
this.setState({addToStudioOpen: true});
}
handleAddToStudioClose () {
this.setState({addToStudioOpen: false});
}
handleReportSubmit (formData) {
this.props.reportProject(this.state.projectId, formData, this.props.user.token);
}
handlePopState () {
const path = window.location.pathname.toLowerCase();
const playerMode = path.indexOf('editor') === -1;
const fullScreen = path.indexOf('fullscreen') !== -1;
if (this.props.playerMode !== playerMode) {
this.props.setPlayer(playerMode);
}
if (this.props.fullScreen !== fullScreen) {
this.props.setFullScreen(fullScreen);
}
}
pushHistory (push) {
// update URI to match mode
const idPath = this.state.projectId ? `${this.state.projectId}/` : '';
let modePath = '';
if (!this.props.playerMode) modePath = 'editor/';
// fullscreen overrides editor
if (this.props.fullScreen) modePath = 'fullscreen/';
const newPath = `/preview/${idPath}${modePath}`;
if (push) {
history.pushState(
{},
document.title,
newPath
);
} else {
history.replaceState(
{},
document.title,
newPath
);
}
}
handleToggleStudio (studio) {
// only send add or leave request to server if we know current status
if ((typeof studio !== 'undefined') && ('includesProject' in studio)) {
this.props.toggleStudio(
(studio.includesProject === false),
studio.id,
this.props.projectInfo.id,
this.props.user.token
);
}
}
handleFavoriteToggle () {
this.props.setFavedStatus(
!this.props.faved,
this.props.projectInfo.id,
this.props.user.username,
this.props.user.token
);
if (this.props.faved) {
this.setState(state => ({
favoriteCount: state.favoriteCount - 1
}));
} else {
this.setState(state => ({
favoriteCount: state.favoriteCount + 1
}));
}
}
handleLoadMore () {
this.props.getTopLevelComments(this.state.projectId, this.props.comments.length,
this.props.isAdmin, this.props.user && this.props.user.token);
}
handleLoveToggle () {
this.props.setLovedStatus(
!this.props.loved,
this.props.projectInfo.id,
this.props.user.username,
this.props.user.token
);
if (this.props.loved) {
this.setState(state => ({
loveCount: state.loveCount - 1
}));
} else {
this.setState(state => ({
loveCount: state.loveCount + 1
}));
}
}
handleSeeInside () {
this.props.setPlayer(false);
}
handleShare () {
this.props.updateProject(
this.props.projectInfo.id,
{isPublished: true},
this.props.user.username,
this.props.user.token
);
}
handleUpdate (jsonData) {
this.props.updateProject(
this.props.projectInfo.id,
jsonData,
this.props.user.username,
this.props.user.token
);
}
handleUpdateProjectTitle (title) {
this.handleUpdate({
title: title
});
}
handleSetLanguage (locale) {
jar.set('scratchlanguage', locale);
}
handleUpdateProjectId (projectId, callback) {
this.setState({projectId: projectId}, () => {
const parts = window.location.pathname.toLowerCase()
.split('/')
.filter(Boolean);
let newUrl;
if (projectId === '0') {
newUrl = `/${parts[0]}/editor`;
} else {
newUrl = `/${parts[0]}/${projectId}/editor`;
}
history.pushState(
{projectId: projectId},
{projectId: projectId},
newUrl
);
if (callback) callback();
});
}
initCounts (favorites, loves) {
this.setState({
favoriteCount: favorites,
loveCount: loves
});
}
renderLogin ({onClose}) {
return (
<ConnectedLogin
key="login-dropdown-presentation"
/* eslint-disable react/jsx-no-bind */
onLogIn={(formData, callback) => {
this.props.handleLogIn(formData, result => {
if (result.success === true) {
onClose();
}
callback(result);
});
}}
/* eslint-ensable react/jsx-no-bind */
/>
);
}
render () {
return (
this.props.playerMode ?
<Page>
<PreviewPresentation
addToStudioOpen={this.state.addToStudioOpen}
assetHost={this.props.assetHost}
backpackOptions={this.props.backpackOptions}
canAddToStudio={this.props.canAddToStudio}
canDeleteComments={this.props.isAdmin || this.props.userOwnsProject}
canReport={this.props.canReport}
canRestoreComments={this.props.isAdmin}
canShare={this.props.canShare}
cloudHost={this.props.cloudHost}
comments={this.props.comments}
editable={this.props.isEditable}
extensions={this.state.extensions}
faved={this.props.faved}
favoriteCount={this.state.favoriteCount}
isFullScreen={this.state.isFullScreen}
isLoggedIn={this.props.isLoggedIn}
isShared={this.props.isShared}
loveCount={this.state.loveCount}
loved={this.props.loved}
moreCommentsToLoad={this.props.moreCommentsToLoad}
originalInfo={this.props.original}
parentInfo={this.props.parent}
projectHost={this.props.projectHost}
projectId={this.state.projectId}
projectInfo={this.props.projectInfo}
projectStudios={this.props.projectStudios}
remixes={this.props.remixes}
replies={this.props.replies}
reportOpen={this.state.reportOpen}
singleCommentId={this.state.singleCommentId}
userOwnsProject={this.props.userOwnsProject}
onAddComment={this.handleAddComment}
onAddToStudioClicked={this.handleAddToStudioClick}
onAddToStudioClosed={this.handleAddToStudioClose}
onDeleteComment={this.handleDeleteComment}
onFavoriteClicked={this.handleFavoriteToggle}
onLoadMore={this.handleLoadMore}
onLoveClicked={this.handleLoveToggle}
onReportClicked={this.handleReportClick}
onReportClose={this.handleReportClose}
onReportComment={this.handleReportComment}
onReportSubmit={this.handleReportSubmit}
onRestoreComment={this.handleRestoreComment}
onSeeInside={this.handleSeeInside}
onShare={this.handleShare}
onToggleComments={this.handleToggleComments}
onToggleStudio={this.handleToggleStudio}
onUpdate={this.handleUpdate}
/>
</Page> :
<React.Fragment>
<IntlGUI
hideIntro
assetHost={this.props.assetHost}
backpackOptions={this.props.backpackOptions}
basePath="/"
canCreateCopy={this.props.canCreateCopy}
canCreateNew={this.props.canCreateNew}
canRemix={this.props.canRemix}
canSave={this.props.canSave}
canShare={this.props.canShare}
className="gui"
cloudHost={this.props.cloudHost}
enableCommunity={this.props.enableCommunity}
isShared={this.props.isShared}
projectHost={this.props.projectHost}
projectId={this.state.projectId}
projectTitle={this.props.projectInfo.title}
renderLogin={this.renderLogin}
onLogOut={this.props.handleLogOut}
onOpenRegistration={this.props.handleOpenRegistration}
onSetLanguage={this.handleSetLanguage}
onShare={this.handleShare}
onToggleLoginOpen={this.props.handleToggleLoginOpen}
onUpdateProjectId={this.handleUpdateProjectId}
onUpdateProjectTitle={this.handleUpdateProjectTitle}
/>
<Registration />
<CanceledDeletionModal />
</React.Fragment>
);
}
if (isSupportedBrowser()) {
const ProjectView = require('./project-view.jsx');
render(
<ProjectView.View />,
document.getElementById('app'),
{
preview: previewActions.previewReducer,
...ProjectView.guiReducers
},
{
locales: ProjectView.initLocale(ProjectView.localesInitialState, window._locale),
scratchGui: ProjectView.initGuiState(ProjectView.guiInitialState)
},
ProjectView.guiMiddleware
);
} else {
render(<Page><UnsupportedBrowser /></Page>, document.getElementById('app'));
}
Preview.propTypes = {
assetHost: PropTypes.string.isRequired,
backpackOptions: PropTypes.shape({
host: PropTypes.string,
visible: PropTypes.bool
}),
canAddToStudio: PropTypes.bool,
canCreateCopy: PropTypes.bool,
canCreateNew: PropTypes.bool,
canRemix: PropTypes.bool,
canReport: PropTypes.bool,
canSave: PropTypes.bool,
canShare: PropTypes.bool,
cloudHost: PropTypes.string,
comments: PropTypes.arrayOf(PropTypes.object),
enableCommunity: PropTypes.bool,
faved: PropTypes.bool,
fullScreen: PropTypes.bool,
getCommentById: PropTypes.func.isRequired,
getCuratedStudios: PropTypes.func.isRequired,
getFavedStatus: PropTypes.func.isRequired,
getLovedStatus: PropTypes.func.isRequired,
getOriginalInfo: PropTypes.func.isRequired,
getParentInfo: PropTypes.func.isRequired,
getProjectInfo: PropTypes.func.isRequired,
getProjectStudios: PropTypes.func.isRequired,
getRemixes: PropTypes.func.isRequired,
getTopLevelComments: PropTypes.func.isRequired,
handleAddComment: PropTypes.func,
handleDeleteComment: PropTypes.func,
handleLogIn: PropTypes.func,
handleLogOut: PropTypes.func,
handleOpenRegistration: PropTypes.func,
handleReportComment: PropTypes.func,
handleRestoreComment: PropTypes.func,
handleToggleLoginOpen: PropTypes.func,
isAdmin: PropTypes.bool,
isEditable: PropTypes.bool,
isLoggedIn: PropTypes.bool,
isShared: PropTypes.bool,
loved: PropTypes.bool,
moreCommentsToLoad: PropTypes.bool,
original: projectShape,
parent: projectShape,
playerMode: PropTypes.bool,
projectHost: PropTypes.string.isRequired,
projectInfo: projectShape,
projectStudios: PropTypes.arrayOf(PropTypes.object),
remixes: PropTypes.arrayOf(PropTypes.object),
replies: PropTypes.objectOf(PropTypes.array),
reportProject: PropTypes.func,
resetProject: PropTypes.func,
sessionStatus: PropTypes.string,
setFavedStatus: PropTypes.func.isRequired,
setFullScreen: PropTypes.func.isRequired,
setLovedStatus: PropTypes.func.isRequired,
setPlayer: PropTypes.func.isRequired,
toggleStudio: PropTypes.func.isRequired,
updateProject: PropTypes.func.isRequired,
user: PropTypes.shape({
id: PropTypes.number,
banned: PropTypes.bool,
username: PropTypes.string,
token: PropTypes.string,
thumbnailUrl: PropTypes.string,
dateJoined: PropTypes.string,
email: PropTypes.string,
classroomId: PropTypes.string
}),
userOwnsProject: PropTypes.bool,
userPresent: PropTypes.bool
};
Preview.defaultProps = {
assetHost: process.env.ASSET_HOST,
backpackOptions: {
host: process.env.BACKPACK_HOST,
visible: true
},
cloudHost: process.env.CLOUDDATA_HOST,
projectHost: process.env.PROJECT_HOST,
sessionStatus: sessionActions.Status.NOT_FETCHED,
user: {},
userPresent: false
};
const mapStateToProps = state => {
const projectInfoPresent = state.preview.projectInfo &&
Object.keys(state.preview.projectInfo).length > 0 && state.preview.projectInfo.id > 0;
const userPresent = state.session.session.user !== null &&
typeof state.session.session.user !== 'undefined' &&
Object.keys(state.session.session.user).length > 0;
const isLoggedIn = state.session.status === sessionActions.Status.FETCHED &&
userPresent;
const isAdmin = isLoggedIn && state.session.session.permissions.admin;
const authorPresent = projectInfoPresent && state.preview.projectInfo.author &&
Object.keys(state.preview.projectInfo.author).length > 0;
const userOwnsProject = isLoggedIn && authorPresent &&
state.session.session.user.id === state.preview.projectInfo.author.id;
return {
canAddToStudio: userOwnsProject,
canCreateCopy: userOwnsProject && projectInfoPresent,
canCreateNew: isLoggedIn,
canRemix: isLoggedIn && projectInfoPresent && !userOwnsProject,
canReport: isLoggedIn && !userOwnsProject,
canSave: isLoggedIn && userOwnsProject,
canShare: userOwnsProject && state.permissions.social,
comments: state.preview.comments,
enableCommunity: projectInfoPresent,
faved: state.preview.faved,
fullScreen: state.scratchGui.mode.isFullScreen,
// project is editable iff logged in user is the author of the project, or
// logged in user is an admin.
isEditable: isLoggedIn &&
((authorPresent && state.preview.projectInfo.author.username === state.session.session.user.username) ||
state.permissions.admin === true),
isLoggedIn: isLoggedIn,
isAdmin: isAdmin,
// if we don't have projectInfo, assume it's shared until we know otherwise
isShared: !projectInfoPresent || state.preview.projectInfo.is_published,
loved: state.preview.loved,
moreCommentsToLoad: state.preview.moreCommentsToLoad,
original: state.preview.original,
parent: state.preview.parent,
playerMode: state.scratchGui.mode.isPlayerOnly,
projectInfo: state.preview.projectInfo,
projectStudios: state.preview.projectStudios,
remixes: state.preview.remixes,
replies: state.preview.replies,
sessionStatus: state.session.status, // check if used
user: state.session.session.user,
userOwnsProject: userOwnsProject,
userPresent: userPresent
};
};
const mapDispatchToProps = dispatch => ({
handleAddComment: (comment, topLevelCommentId) => {
dispatch(previewActions.addNewComment(comment, topLevelCommentId));
},
handleDeleteComment: (projectId, commentId, topLevelCommentId, token) => {
dispatch(previewActions.deleteComment(projectId, commentId, topLevelCommentId, token));
},
handleReportComment: (projectId, commentId, topLevelCommentId, token) => {
dispatch(previewActions.reportComment(projectId, commentId, topLevelCommentId, token));
},
handleRestoreComment: (projectId, commentId, topLevelCommentId, token) => {
dispatch(previewActions.restoreComment(projectId, commentId, topLevelCommentId, token));
},
handleOpenRegistration: event => {
event.preventDefault();
dispatch(navigationActions.setRegistrationOpen(true));
},
handleLogIn: (formData, callback) => {
dispatch(navigationActions.handleLogIn(formData, callback));
},
handleLogOut: event => {
event.preventDefault();
dispatch(navigationActions.handleLogOut());
},
handleToggleLoginOpen: event => {
event.preventDefault();
dispatch(navigationActions.toggleLoginOpen());
},
getOriginalInfo: id => {
dispatch(previewActions.getOriginalInfo(id));
},
getParentInfo: id => {
dispatch(previewActions.getParentInfo(id));
},
getProjectInfo: (id, token) => {
dispatch(previewActions.getProjectInfo(id, token));
},
getRemixes: id => {
dispatch(previewActions.getRemixes(id));
},
getProjectStudios: id => {
dispatch(previewActions.getProjectStudios(id));
},
getCuratedStudios: (username, token) => {
dispatch(previewActions.getCuratedStudios(username, token));
},
toggleStudio: (isAdd, studioId, id, token) => {
if (isAdd === true) {
dispatch(previewActions.addToStudio(studioId, id, token));
} else {
dispatch(previewActions.leaveStudio(studioId, id, token));
}
},
getTopLevelComments: (id, offset, isAdmin, token) => {
dispatch(previewActions.getTopLevelComments(id, offset, isAdmin, token));
},
getCommentById: (projectId, commentId, isAdmin, token) => {
dispatch(previewActions.getCommentById(projectId, commentId, isAdmin, token));
},
getFavedStatus: (id, username, token) => {
dispatch(previewActions.getFavedStatus(id, username, token));
},
setFavedStatus: (faved, id, username, token) => {
dispatch(previewActions.setFavedStatus(faved, id, username, token));
},
getLovedStatus: (id, username, token) => {
dispatch(previewActions.getLovedStatus(id, username, token));
},
setLovedStatus: (loved, id, username, token) => {
dispatch(previewActions.setLovedStatus(loved, id, username, token));
},
reportProject: (id, formData, token) => {
dispatch(previewActions.reportProject(id, formData, token));
},
resetProject: () => {
dispatch(previewActions.resetProject());
},
setOriginalInfo: info => {
dispatch(previewActions.setOriginalInfo(info));
},
setParentInfo: info => {
dispatch(previewActions.setParentInfo(info));
},
updateProject: (id, formData, username, token) => {
dispatch(previewActions.updateProject(id, formData, username, token));
},
setPlayer: player => {
dispatch(GUI.setPlayer(player));
},
setFullScreen: fullscreen => {
dispatch(GUI.setFullScreen(fullscreen));
}
});
const ConnectedPreview = connect(
mapStateToProps,
mapDispatchToProps
)(Preview);
GUI.setAppElement(document.getElementById('app'));
const initGuiState = guiInitialState => {
const pathname = window.location.pathname.toLowerCase();
const parts = pathname.split('/').filter(Boolean);
// parts[0]: 'preview'
// parts[1]: either :id or 'editor'
// parts[2]: undefined if no :id, otherwise either 'editor' or 'fullscreen'
if (parts.indexOf('editor') === -1) {
guiInitialState = GUI.initPlayer(guiInitialState);
}
if (parts.indexOf('fullscreen') !== -1) {
guiInitialState = GUI.initFullScreen(guiInitialState);
}
return guiInitialState;
};
render(
<ConnectedPreview />,
document.getElementById('app'),
{
preview: previewActions.previewReducer,
...GUI.guiReducers
},
{
locales: GUI.initLocale(GUI.localesInitialState, window._locale),
scratchGui: initGuiState(GUI.guiInitialState)
},
GUI.guiMiddleware
);

View file

@ -163,6 +163,7 @@ $stage-width: 480px;
}
.project-buttons {
font-weight: bold;
flex-shrink: 0;
}
@ -170,6 +171,11 @@ $stage-width: 480px;
margin-left: 1rem;
}
.inplace-textarea::placeholder {
padding: 0;
text-align: start;
}
.comments-container {
padding-right: 1.5rem;
min-width: 65%;
@ -206,17 +212,23 @@ $stage-width: 480px;
.remix-button,
.see-inside-button {
margin-top: 0;
border-radius: .25rem;
padding-top: .6875rem;
padding-right: .9375rem;
padding-bottom: .6875rem;
padding-left: .875rem;
height: 2.5rem;
font-size: .875rem;
font-weight: normal;
&:before {
display: inline-block;
margin-right: .5rem;
margin-top: -2px;
margin-right: .4375rem;
background-repeat: no-repeat;
background-position: center center;
background-size: contain;
width: 1.25rem;
height: 1.25rem;
width: 1.125rem;
height: 1.125rem;
vertical-align: middle;
content: "";
}

View file

@ -0,0 +1,871 @@
// preview view can show either project page or editor page;
// idea is that we shouldn't require a page reload to switch back and forth
const bindAll = require('lodash.bindall');
const React = require('react');
const PropTypes = require('prop-types');
const connect = require('react-redux').connect;
const injectIntl = require('react-intl').injectIntl;
const parser = require('scratch-parser');
const copy = require('clipboard-copy');
const Page = require('../../components/page/www/page.jsx');
const storage = require('../../lib/storage.js').default;
const log = require('../../lib/log');
const EXTENSION_INFO = require('../../lib/extensions.js').default;
const jar = require('../../lib/jar.js');
const thumbnailUrl = require('../../lib/user-thumbnail');
const PreviewPresentation = require('./presentation.jsx');
const projectShape = require('./projectshape.jsx').projectShape;
const Registration = require('../../components/registration/registration.jsx');
const ConnectedLogin = require('../../components/login/connected-login.jsx');
const CanceledDeletionModal = require('../../components/login/canceled-deletion-modal.jsx');
const NotAvailable = require('../../components/not-available/not-available.jsx');
const sessionActions = require('../../redux/session.js');
const navigationActions = require('../../redux/navigation.js');
const previewActions = require('../../redux/preview.js');
const frameless = require('../../lib/frameless');
const GUI = require('scratch-gui');
const IntlGUI = injectIntl(GUI.default);
class Preview extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'addEventListeners',
'fetchCommunityData',
'handleAddComment',
'handleCopyProjectLink',
'handleDeleteComment',
'handleToggleStudio',
'handleFavoriteToggle',
'handleLoadMore',
'handleLoveToggle',
'handlePopState',
'handleReportClick',
'handleReportClose',
'handleReportComment',
'handleReportSubmit',
'handleRestoreComment',
'handleAddToStudioClick',
'handleAddToStudioClose',
'handleRemix',
'handleSeeAllComments',
'handleSeeInside',
'handleShare',
'handleUpdateProjectId',
'handleUpdateProjectTitle',
'handleUpdate',
'handleToggleComments',
'initCounts',
'pushHistory',
'renderLogin',
'setScreenFromOrientation'
]);
const pathname = window.location.pathname.toLowerCase();
const parts = pathname.split('/').filter(Boolean);
// parts[0]: 'projects'
// parts[1]: either :id or 'editor'
// parts[2]: undefined if no :id, otherwise either 'editor' or 'fullscreen'
// Get single-comment id from url hash, using the #comments-{id} scheme from scratch2
const commentHashPrefix = '#comments-';
const singleCommentId = window.location.hash.indexOf(commentHashPrefix) !== -1 &&
parseInt(window.location.hash.replace(commentHashPrefix, ''), 10);
this.state = {
addToStudioOpen: false,
extensions: [],
favoriteCount: 0,
justShared: false,
loveCount: 0,
modInfo: {
scripts: 0,
sprites: 0
},
projectId: parts[1] === 'editor' ? '0' : parts[1],
reportOpen: false,
singleCommentId: singleCommentId
};
this.addEventListeners();
/* In the beginning, if user is on mobile and landscape, go to fullscreen */
this.setScreenFromOrientation();
}
componentDidUpdate (prevProps, prevState) {
if (this.state.projectId > 0 &&
((this.props.sessionStatus !== prevProps.sessionStatus &&
this.props.sessionStatus === sessionActions.Status.FETCHED) ||
(this.state.projectId !== prevState.projectId))) {
this.fetchCommunityData();
this.getProjectData(this.state.projectId);
this.setState({justShared: false}); // eslint-disable-line react/no-did-update-set-state
}
if (this.state.projectId === '0' && this.state.projectId !== prevState.projectId) {
this.props.resetProject();
this.setState({justShared: false}); // eslint-disable-line react/no-did-update-set-state
}
if (this.props.projectInfo.id !== prevProps.projectInfo.id) {
if (typeof this.props.projectInfo.id === 'undefined') {
this.initCounts(0, 0);
} else {
this.initCounts(this.props.projectInfo.stats.favorites, this.props.projectInfo.stats.loves);
if (this.props.projectInfo.remix.parent !== null) {
this.props.getParentInfo(this.props.projectInfo.remix.parent);
}
if (this.props.projectInfo.remix.root !== null &&
this.props.projectInfo.remix.root !== this.props.projectInfo.remix.parent
) {
this.props.getOriginalInfo(this.props.projectInfo.remix.root);
}
}
}
if (this.props.playerMode !== prevProps.playerMode || this.props.fullScreen !== prevProps.fullScreen) {
this.pushHistory(history.state === null);
}
}
componentWillUnmount () {
this.removeEventListeners();
}
addEventListeners () {
window.addEventListener('popstate', this.handlePopState);
window.addEventListener('orientationchange', this.setScreenFromOrientation);
}
removeEventListeners () {
window.removeEventListener('popstate', this.handlePopState);
window.removeEventListener('orientationchange', this.setScreenFromOrientation);
}
fetchCommunityData () {
if (this.props.userPresent) {
const username = this.props.user.username;
const token = this.props.user.token;
if (this.state.singleCommentId) {
this.props.getCommentById(this.state.projectId, this.state.singleCommentId,
this.props.isAdmin, token);
} else {
this.props.getTopLevelComments(this.state.projectId, this.props.comments.length,
this.props.isAdmin, token);
}
this.props.getProjectInfo(this.state.projectId, token);
this.props.getRemixes(this.state.projectId, token);
this.props.getProjectStudios(this.state.projectId, token);
this.props.getCuratedStudios(username);
this.props.getFavedStatus(this.state.projectId, username, token);
this.props.getLovedStatus(this.state.projectId, username, token);
} else {
if (this.state.singleCommentId) {
this.props.getCommentById(this.state.projectId, this.state.singleCommentId);
} else {
this.props.getTopLevelComments(this.state.projectId, this.props.comments.length);
}
this.props.getProjectInfo(this.state.projectId);
this.props.getRemixes(this.state.projectId);
this.props.getProjectStudios(this.state.projectId);
}
}
setScreenFromOrientation () {
/*
* If the user is on a mobile device, switching to
* landscape format should make the fullscreen mode active
*/
const isMobileDevice = screen.height <= frameless.mobile || screen.width <= frameless.mobile;
const isAModalOpen = this.state.addToStudioOpen || this.state.reportOpen;
if (this.props.playerMode && isMobileDevice && !isAModalOpen) {
const isLandscape = screen.height < screen.width;
if (isLandscape) {
this.props.setFullScreen(true);
} else {
this.props.setFullScreen(false);
}
}
}
getProjectData (projectId) {
if (projectId > 0) {
storage
.load(storage.AssetType.Project, projectId, storage.DataFormat.JSON)
.then(projectAsset => { // NOTE: this is turning up null, breaking the line below.
let input = projectAsset.data;
if (typeof input === 'object' && !(input instanceof ArrayBuffer) &&
!ArrayBuffer.isView(input)) { // taken from scratch-vm
// If the input is an object and not any ArrayBuffer
// or an ArrayBuffer view (this includes all typed arrays and DataViews)
// turn the object into a JSON string, because we suspect
// this is a project.json as an object
// validate expects a string or buffer as input
// TODO not sure if we need to check that it also isn't a data view
input = JSON.stringify(input);
}
parser(projectAsset.data, false, (err, projectData) => {
if (err) {
log.error(`Unhandled project parsing error: ${err}`);
return;
}
const extensionSet = new Set();
if (projectData[0].extensions) {
projectData[0].extensions.forEach(extension => {
const extensionInfo = EXTENSION_INFO[extension];
if (extensionInfo) {
extensionSet.add(extensionInfo);
}
});
}
const sprites = projectData[0].targets.length - 1; // don't count stage
const scripts = projectData[0].targets
.map(target =>
Object.values(target.blocks)
.filter(block => block.topLevel).length
)
.reduce((accumulator, currentVal) => accumulator + currentVal, 0);
this.setState({
extensions: Array.from(extensionSet),
modInfo: {
scripts: scripts,
sprites: sprites
}
});
});
});
} else { // projectId is default or invalid; empty the extensions array
this.setState({
extensions: [],
modInfo: {
scripts: 0,
sprites: 0
}
});
}
}
handleToggleComments () {
this.props.updateProject(
this.props.projectInfo.id,
{comments_allowed: !this.props.projectInfo.comments_allowed},
this.props.user.username,
this.props.user.token
);
}
handleAddComment (comment, topLevelCommentId) {
this.props.handleAddComment(comment, topLevelCommentId);
}
handleDeleteComment (id, topLevelCommentId) {
this.props.handleDeleteComment(this.state.projectId, id, topLevelCommentId, this.props.user.token);
}
handleReportComment (id, topLevelCommentId) {
this.props.handleReportComment(this.state.projectId, id, topLevelCommentId, this.props.user.token);
}
handleRestoreComment (id, topLevelCommentId) {
this.props.handleRestoreComment(this.state.projectId, id, topLevelCommentId, this.props.user.token);
}
handleReportClick () {
this.setState({reportOpen: true});
}
handleReportClose () {
this.setState({reportOpen: false});
}
handleAddToStudioClick () {
this.setState({addToStudioOpen: true});
}
handleAddToStudioClose () {
this.setState({addToStudioOpen: false});
}
handleReportSubmit (formData) {
this.props.reportProject(this.state.projectId, formData, this.props.user.token);
}
handlePopState () {
const path = window.location.pathname.toLowerCase();
const playerMode = path.indexOf('editor') === -1;
const fullScreen = path.indexOf('fullscreen') !== -1;
if (this.props.playerMode !== playerMode) {
this.props.setPlayer(playerMode);
}
if (this.props.fullScreen !== fullScreen) {
this.props.setFullScreen(fullScreen);
}
}
pushHistory (push) {
// update URI to match mode
const idPath = this.state.projectId ? `${this.state.projectId}/` : '';
let modePath = '';
if (!this.props.playerMode) modePath = 'editor/';
// fullscreen overrides editor
if (this.props.fullScreen) modePath = 'fullscreen/';
const newPath = `/projects/${idPath}${modePath}`;
if (push) {
history.pushState(
{},
document.title,
newPath
);
} else {
history.replaceState(
{},
document.title,
newPath
);
}
}
handleToggleStudio (studio) {
// only send add or leave request to server if we know current status
if ((typeof studio !== 'undefined') && ('includesProject' in studio)) {
this.props.toggleStudio(
(studio.includesProject === false),
studio.id,
this.props.projectInfo.id,
this.props.user.token
);
}
}
handleFavoriteToggle () {
this.props.setFavedStatus(
!this.props.faved,
this.props.projectInfo.id,
this.props.user.username,
this.props.user.token
);
if (this.props.faved) {
this.setState(state => ({
favoriteCount: state.favoriteCount - 1
}));
} else {
this.setState(state => ({
favoriteCount: state.favoriteCount + 1
}));
}
}
handleLoadMore () {
this.props.getTopLevelComments(this.state.projectId, this.props.comments.length,
this.props.isAdmin, this.props.user && this.props.user.token);
}
handleLoveToggle () {
this.props.setLovedStatus(
!this.props.loved,
this.props.projectInfo.id,
this.props.user.username,
this.props.user.token
);
if (this.props.loved) {
this.setState(state => ({
loveCount: state.loveCount - 1
}));
} else {
this.setState(state => ({
loveCount: state.loveCount + 1
}));
}
}
handleRemix () {
this.props.remixProject();
}
handleSeeInside () {
this.props.setPlayer(false);
this.setState({justShared: false});
}
handleShare () {
this.props.shareProject(
this.props.projectInfo.id,
this.props.user.token
);
this.setState({justShared: true});
}
handleUpdate (jsonData) {
this.props.updateProject(
this.props.projectInfo.id,
jsonData,
this.props.user.username,
this.props.user.token
);
}
handleUpdateProjectTitle (title) {
this.handleUpdate({
title: title
});
}
handleSetLanguage (locale) {
jar.set('scratchlanguage', locale);
}
handleUpdateProjectId (projectId, callback) {
this.setState({projectId: projectId}, () => {
const parts = window.location.pathname.toLowerCase()
.split('/')
.filter(Boolean);
let newUrl;
if (projectId === '0') {
newUrl = `/${parts[0]}/editor`;
} else {
newUrl = `/${parts[0]}/${projectId}/editor`;
}
history.pushState(
{projectId: projectId},
{projectId: projectId},
newUrl
);
if (callback) callback();
});
}
handleSeeAllComments () {
// Remove hash from URL
history.pushState('', document.title, window.location.pathname + window.location.search);
this.setState({singleCommentId: null});
this.props.handleSeeAllComments(
this.props.projectInfo.id,
this.props.isAdmin,
this.props.user.token
);
}
handleCopyProjectLink () {
// Use the pathname so we do not have to update this if path changes
// Also do not include hash or query params
copy(`${window.location.origin}${window.location.pathname}`);
}
initCounts (favorites, loves) {
this.setState({
favoriteCount: favorites,
loveCount: loves
});
}
renderLogin ({onClose}) {
return (
<ConnectedLogin
key="login-dropdown-presentation"
/* eslint-disable react/jsx-no-bind */
onLogIn={(formData, callback) => {
this.props.handleLogIn(formData, result => {
if (result.success === true) {
onClose();
}
callback(result);
});
}}
/* eslint-ensable react/jsx-no-bind */
/>
);
}
render () {
if (this.props.projectNotAvailable) {
return (
<Page>
<div className="preview">
<NotAvailable />
</div>
</Page>
);
}
return (
this.props.playerMode ?
<Page>
<PreviewPresentation
addToStudioOpen={this.state.addToStudioOpen}
assetHost={this.props.assetHost}
backpackHost={this.props.backpackHost}
canAddToStudio={this.props.canAddToStudio}
canDeleteComments={this.props.isAdmin || this.props.userOwnsProject}
canRemix={this.props.canRemix}
canReport={this.props.canReport}
canRestoreComments={this.props.isAdmin}
canSave={this.props.canSave}
canShare={this.props.canShare}
canUseBackpack={this.props.canUseBackpack}
cloudHost={this.props.cloudHost}
comments={this.props.comments}
editable={this.props.isEditable}
extensions={this.state.extensions}
faved={this.props.faved}
favoriteCount={this.state.favoriteCount}
isFullScreen={this.state.isFullScreen}
isLoggedIn={this.props.isLoggedIn}
isNewScratcher={this.props.isNewScratcher}
isShared={this.props.isShared}
justShared={this.state.justShared}
loveCount={this.state.loveCount}
loved={this.props.loved}
modInfo={this.state.modInfo}
moreCommentsToLoad={this.props.moreCommentsToLoad}
originalInfo={this.props.original}
parentInfo={this.props.parent}
projectHost={this.props.projectHost}
projectId={this.state.projectId}
projectInfo={this.props.projectInfo}
projectStudios={this.props.projectStudios}
remixes={this.props.remixes}
replies={this.props.replies}
reportOpen={this.state.reportOpen}
showModInfo={this.props.isAdmin}
singleCommentId={this.state.singleCommentId}
userOwnsProject={this.props.userOwnsProject}
visibilityInfo={this.props.visibilityInfo}
onAddComment={this.handleAddComment}
onAddToStudioClicked={this.handleAddToStudioClick}
onAddToStudioClosed={this.handleAddToStudioClose}
onCopyProjectLink={this.handleCopyProjectLink}
onDeleteComment={this.handleDeleteComment}
onFavoriteClicked={this.handleFavoriteToggle}
onLoadMore={this.handleLoadMore}
onLoveClicked={this.handleLoveToggle}
onRemix={this.handleRemix}
onReportClicked={this.handleReportClick}
onReportClose={this.handleReportClose}
onReportComment={this.handleReportComment}
onReportSubmit={this.handleReportSubmit}
onRestoreComment={this.handleRestoreComment}
onSeeAllComments={this.handleSeeAllComments}
onSeeInside={this.handleSeeInside}
onShare={this.handleShare}
onToggleComments={this.handleToggleComments}
onToggleStudio={this.handleToggleStudio}
onUpdate={this.handleUpdate}
onUpdateProjectId={this.handleUpdateProjectId}
/>
</Page> :
<React.Fragment>
<IntlGUI
assetHost={this.props.assetHost}
authorId={this.props.authorId}
authorThumbnailUrl={this.props.authorThumbnailUrl}
authorUsername={this.props.authorUsername}
backpackHost={this.props.backpackHost}
backpackVisible={this.props.canUseBackpack}
basePath="/"
canCreateCopy={this.props.canCreateCopy}
canCreateNew={this.props.canCreateNew}
canEditTitle={this.props.isEditable}
canRemix={this.props.canRemix}
canSave={this.props.canSave}
canShare={this.props.canShare}
className="gui"
cloudHost={this.props.cloudHost}
enableCommunity={this.props.enableCommunity}
isShared={this.props.isShared}
projectHost={this.props.projectHost}
projectId={this.state.projectId}
projectTitle={this.props.projectInfo.title}
renderLogin={this.renderLogin}
onLogOut={this.props.handleLogOut}
onOpenRegistration={this.props.handleOpenRegistration}
onSetLanguage={this.handleSetLanguage}
onShare={this.handleShare}
onToggleLoginOpen={this.props.handleToggleLoginOpen}
onUpdateProjectId={this.handleUpdateProjectId}
onUpdateProjectThumbnail={this.props.handleUpdateProjectThumbnail}
onUpdateProjectTitle={this.handleUpdateProjectTitle}
/>
<Registration />
<CanceledDeletionModal />
</React.Fragment>
);
}
}
Preview.propTypes = {
assetHost: PropTypes.string.isRequired,
// If there's no author, this will be false`
authorId: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
authorThumbnailUrl: PropTypes.string,
// If there's no author, this will be false`
authorUsername: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
backpackHost: PropTypes.string,
canAddToStudio: PropTypes.bool,
canCreateCopy: PropTypes.bool,
canCreateNew: PropTypes.bool,
canRemix: PropTypes.bool,
canReport: PropTypes.bool,
canSave: PropTypes.bool,
canShare: PropTypes.bool,
canUseBackpack: PropTypes.bool,
cloudHost: PropTypes.string,
comments: PropTypes.arrayOf(PropTypes.object),
enableCommunity: PropTypes.bool,
faved: PropTypes.bool,
fullScreen: PropTypes.bool,
getCommentById: PropTypes.func.isRequired,
getCuratedStudios: PropTypes.func.isRequired,
getFavedStatus: PropTypes.func.isRequired,
getLovedStatus: PropTypes.func.isRequired,
getOriginalInfo: PropTypes.func.isRequired,
getParentInfo: PropTypes.func.isRequired,
getProjectInfo: PropTypes.func.isRequired,
getProjectStudios: PropTypes.func.isRequired,
getRemixes: PropTypes.func.isRequired,
getTopLevelComments: PropTypes.func.isRequired,
handleAddComment: PropTypes.func,
handleDeleteComment: PropTypes.func,
handleLogIn: PropTypes.func,
handleLogOut: PropTypes.func,
handleOpenRegistration: PropTypes.func,
handleReportComment: PropTypes.func,
handleRestoreComment: PropTypes.func,
handleSeeAllComments: PropTypes.func,
handleToggleLoginOpen: PropTypes.func,
handleUpdateProjectThumbnail: PropTypes.func,
isAdmin: PropTypes.bool,
isEditable: PropTypes.bool,
isLoggedIn: PropTypes.bool,
isNewScratcher: PropTypes.bool,
isShared: PropTypes.bool,
loved: PropTypes.bool,
moreCommentsToLoad: PropTypes.bool,
original: projectShape,
parent: projectShape,
playerMode: PropTypes.bool,
projectHost: PropTypes.string.isRequired,
projectInfo: projectShape,
projectNotAvailable: PropTypes.bool,
projectStudios: PropTypes.arrayOf(PropTypes.object),
remixProject: PropTypes.func,
remixes: PropTypes.arrayOf(PropTypes.object),
replies: PropTypes.objectOf(PropTypes.array),
reportProject: PropTypes.func,
resetProject: PropTypes.func,
sessionStatus: PropTypes.string,
setFavedStatus: PropTypes.func.isRequired,
setFullScreen: PropTypes.func.isRequired,
setLovedStatus: PropTypes.func.isRequired,
setPlayer: PropTypes.func.isRequired,
shareProject: PropTypes.func.isRequired,
toggleStudio: PropTypes.func.isRequired,
updateProject: PropTypes.func.isRequired,
user: PropTypes.shape({
id: PropTypes.number,
banned: PropTypes.bool,
username: PropTypes.string,
token: PropTypes.string,
thumbnailUrl: PropTypes.string,
dateJoined: PropTypes.string,
email: PropTypes.string,
classroomId: PropTypes.string
}),
userOwnsProject: PropTypes.bool,
userPresent: PropTypes.bool,
visibilityInfo: PropTypes.shape({
censored: PropTypes.bool,
censorMessage: PropTypes.string,
deleted: PropTypes.bool,
reshareable: PropTypes.bool
})
};
Preview.defaultProps = {
assetHost: process.env.ASSET_HOST,
backpackHost: process.env.BACKPACK_HOST,
canUseBackpack: false,
cloudHost: process.env.CLOUDDATA_HOST,
projectHost: process.env.PROJECT_HOST,
sessionStatus: sessionActions.Status.NOT_FETCHED,
user: {},
userPresent: false
};
const mapStateToProps = state => {
const projectInfoPresent = state.preview.projectInfo &&
Object.keys(state.preview.projectInfo).length > 0 && state.preview.projectInfo.id > 0;
const userPresent = state.session.session.user !== null &&
typeof state.session.session.user !== 'undefined' &&
Object.keys(state.session.session.user).length > 0;
const isLoggedIn = state.session.status === sessionActions.Status.FETCHED &&
userPresent;
const isAdmin = isLoggedIn && state.session.session.permissions.admin;
const author = projectInfoPresent && state.preview.projectInfo.author;
const authorPresent = author && Object.keys(state.preview.projectInfo.author).length > 0;
const authorId = authorPresent && author.id && author.id.toString();
const authorUsername = authorPresent && author.username;
const userOwnsProject = isLoggedIn && authorPresent &&
state.session.session.user.id.toString() === authorId;
return {
authorId: authorId,
authorThumbnailUrl: thumbnailUrl(authorId),
authorUsername: authorUsername,
canAddToStudio: userOwnsProject,
canCreateCopy: userOwnsProject && projectInfoPresent,
canCreateNew: isLoggedIn,
canRemix: isLoggedIn && projectInfoPresent && !userOwnsProject,
canReport: isLoggedIn && !userOwnsProject,
canSave: isLoggedIn && userOwnsProject,
canShare: userOwnsProject && state.permissions.social,
canUseBackpack: isLoggedIn,
comments: state.preview.comments,
enableCommunity: projectInfoPresent,
faved: state.preview.faved,
fullScreen: state.scratchGui.mode.isFullScreen,
// project is editable iff logged in user is the author of the project, or
// logged in user is an admin.
isEditable: isLoggedIn &&
(authorUsername === state.session.session.user.username ||
state.permissions.admin === true),
isLoggedIn: isLoggedIn,
isAdmin: isAdmin,
isNewScratcher: isLoggedIn && state.permissions.new_scratcher,
// if we don't have projectInfo, assume it's shared until we know otherwise
isShared: !projectInfoPresent || state.preview.projectInfo.is_published,
loved: state.preview.loved,
moreCommentsToLoad: state.preview.moreCommentsToLoad,
original: state.preview.original,
parent: state.preview.parent,
playerMode: state.scratchGui.mode.isPlayerOnly,
projectInfo: state.preview.projectInfo,
projectNotAvailable: state.preview.projectNotAvailable,
projectStudios: state.preview.projectStudios,
remixes: state.preview.remixes,
replies: state.preview.replies,
sessionStatus: state.session.status, // check if used
user: state.session.session.user,
userOwnsProject: userOwnsProject,
userPresent: userPresent,
visibilityInfo: state.preview.visibilityInfo
};
};
const mapDispatchToProps = dispatch => ({
handleAddComment: (comment, topLevelCommentId) => {
dispatch(previewActions.addNewComment(comment, topLevelCommentId));
},
handleDeleteComment: (projectId, commentId, topLevelCommentId, token) => {
dispatch(previewActions.deleteComment(projectId, commentId, topLevelCommentId, token));
},
handleReportComment: (projectId, commentId, topLevelCommentId, token) => {
dispatch(previewActions.reportComment(projectId, commentId, topLevelCommentId, token));
},
handleRestoreComment: (projectId, commentId, topLevelCommentId, token) => {
dispatch(previewActions.restoreComment(projectId, commentId, topLevelCommentId, token));
},
handleOpenRegistration: event => {
event.preventDefault();
dispatch(navigationActions.setRegistrationOpen(true));
},
handleLogIn: (formData, callback) => {
dispatch(navigationActions.handleLogIn(formData, callback));
},
handleLogOut: event => {
event.preventDefault();
dispatch(navigationActions.handleLogOut());
},
handleToggleLoginOpen: event => {
event.preventDefault();
dispatch(navigationActions.toggleLoginOpen());
},
handleSeeAllComments: (id, isAdmin, token) => {
dispatch(previewActions.resetComments());
dispatch(previewActions.getTopLevelComments(id, 0, isAdmin, token));
},
handleUpdateProjectThumbnail: (id, blob) => {
dispatch(previewActions.updateProjectThumbnail(id, blob));
},
getOriginalInfo: id => {
dispatch(previewActions.getOriginalInfo(id));
},
getParentInfo: id => {
dispatch(previewActions.getParentInfo(id));
},
getProjectInfo: (id, token) => {
dispatch(previewActions.getProjectInfo(id, token));
},
getRemixes: id => {
dispatch(previewActions.getRemixes(id));
},
getProjectStudios: id => {
dispatch(previewActions.getProjectStudios(id));
},
getCuratedStudios: (username, token) => {
dispatch(previewActions.getCuratedStudios(username, token));
},
toggleStudio: (isAdd, studioId, id, token) => {
if (isAdd === true) {
dispatch(previewActions.addToStudio(studioId, id, token));
} else {
dispatch(previewActions.leaveStudio(studioId, id, token));
}
},
getTopLevelComments: (id, offset, isAdmin, token) => {
dispatch(previewActions.getTopLevelComments(id, offset, isAdmin, token));
},
getCommentById: (projectId, commentId, isAdmin, token) => {
dispatch(previewActions.getCommentById(projectId, commentId, isAdmin, token));
},
getFavedStatus: (id, username, token) => {
dispatch(previewActions.getFavedStatus(id, username, token));
},
setFavedStatus: (faved, id, username, token) => {
dispatch(previewActions.setFavedStatus(faved, id, username, token));
},
getLovedStatus: (id, username, token) => {
dispatch(previewActions.getLovedStatus(id, username, token));
},
setLovedStatus: (loved, id, username, token) => {
dispatch(previewActions.setLovedStatus(loved, id, username, token));
},
shareProject: (id, token) => {
dispatch(previewActions.shareProject(id, token));
},
reportProject: (id, formData, token) => {
dispatch(previewActions.reportProject(id, formData, token));
},
resetProject: () => {
dispatch(previewActions.resetProject());
},
setOriginalInfo: info => {
dispatch(previewActions.setOriginalInfo(info));
},
setParentInfo: info => {
dispatch(previewActions.setParentInfo(info));
},
updateProject: (id, formData, username, token) => {
dispatch(previewActions.updateProject(id, formData, username, token));
},
remixProject: () => {
dispatch(GUI.remixProject());
dispatch(previewActions.resetComments());
},
setPlayer: player => {
dispatch(GUI.setPlayer(player));
},
setFullScreen: fullscreen => {
dispatch(GUI.setFullScreen(fullscreen));
}
});
module.exports.View = connect(
mapStateToProps,
mapDispatchToProps
)(Preview);
// replace old Scratch 2.0-style hashtag URLs with updated format
if (window.location.hash) {
let pathname = window.location.pathname;
if (pathname.substr(-1) !== '/') {
pathname = `${pathname}/`;
}
if (window.location.hash === '#editor') {
history.replaceState({}, document.title,
`${pathname}editor${window.location.search}`);
}
if (window.location.hash === '#fullscreen') {
history.replaceState({}, document.title,
`${pathname}fullscreen${window.location.search}`);
}
}
// initialize GUI by calling its reducer functions depending on URL
GUI.setAppElement(document.getElementById('app'));
module.exports.initGuiState = guiInitialState => {
const pathname = window.location.pathname.toLowerCase();
const parts = pathname.split('/').filter(Boolean);
// parts[0]: 'projects'
// parts[1]: either :id or 'editor'
// parts[2]: undefined if no :id, otherwise either 'editor' or 'fullscreen'
if (parts.indexOf('editor') === -1) {
guiInitialState = GUI.initPlayer(guiInitialState);
}
if (parts.indexOf('fullscreen') !== -1) {
guiInitialState = GUI.initFullScreen(guiInitialState);
}
return guiInitialState;
};
module.exports.guiReducers = GUI.guiReducers;
module.exports.guiInitialState = GUI.guiInitialState;
module.exports.guiMiddleware = GUI.guiMiddleware;
module.exports.initLocale = GUI.initLocale;
module.exports.localesInitialState = GUI.localesInitialState;

View file

@ -3,6 +3,7 @@ const FormattedMessage = require('react-intl').FormattedMessage;
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const Avatar = require('../../components/avatar/avatar.jsx');
const projectShape = require('./projectshape.jsx').projectShape;
const thumbnailUrl = require('../../lib/user-thumbnail');
const RemixCredit = props => {
const projectInfo = props.projectInfo;
@ -11,7 +12,7 @@ const RemixCredit = props => {
<FlexRow className="remix-credit">
<Avatar
className="remix"
src={`https://cdn2.scratch.mit.edu/get_image/user/${projectInfo.author.id}_48x48.png`}
src={thumbnailUrl(projectInfo.author.id, 48)}
/>
<div className="credit-text">
<FormattedMessage
@ -24,7 +25,7 @@ const RemixCredit = props => {
),
projectLink: (
<a
href={`/preview/${projectInfo.id}`}
href={`/projects/${projectInfo.id}`}
title={projectInfo.title}
>
{projectInfo.title}

View file

@ -1,29 +0,0 @@
const PropTypes = require('prop-types');
const React = require('react');
const FormattedMessage = require('react-intl').FormattedMessage;
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const Button = require('../../components/forms/button.jsx');
require('./share-banner.scss');
const ShareBanner = ({onShare}) => (
<div className="share-banner-outer">
<FlexRow className="inner share-banner">
<span className="share-text">
<FormattedMessage id="project.share.notShared" />
</span>
<Button
className="button share-button"
onClick={onShare}
>
<FormattedMessage id="project.share.shareButton" />
</Button>
</FlexRow>
</div>
);
ShareBanner.propTypes = {
onShare: PropTypes.func
};
module.exports = ShareBanner;

View file

@ -1,35 +0,0 @@
@import "../../colors";
$navigation-height: 50px;
.share-banner-outer {
background-color: $ui-orange-25percent;
width: 100%;
overflow: hidden;
color: $ui-orange;
}
.share-banner {
align-items: center;
justify-content: space-between;
}
.share-button {
background-color: $ui-orange;
font-size: .875rem;
font-weight: normal;
// don't show an image in share button, for now.
// &:before {
// display: inline-block;
// margin-right: .5rem;
// background-image: url("/svgs/project/share-white.svg");
// background-repeat: no-repeat;
// background-position: center center;
// background-size: contain;
// width: 1.25rem;
// height: 1.25rem;
// vertical-align: middle;
// content: "";
// }
}

View file

@ -16,14 +16,14 @@ const Stats = props => (
key="loves"
onClick={props.onLoveClicked}
>
{approx(props.loveCount, {decimal: false})}
{approx(Math.max(0, props.loveCount), {decimal: false})}
</div>
<div
className={classNames('project-favorites', {favorited: props.faved})}
key="favorites"
onClick={props.onFavoriteClicked}
>
{approx(props.favoriteCount, {decimal: false})}
{approx(Math.max(0, props.favoriteCount), {decimal: false})}
</div>
<div
className="project-remixes"

View file

@ -47,7 +47,10 @@ const Subactions = props => (
)}
</React.Fragment>
}
<Button className="action-button copy-link-button">
<Button
className="action-button copy-link-button"
onClick={props.onCopyProjectLink}
>
<FormattedMessage id="general.copyLink" />
</Button>
{(props.canReport) &&
@ -80,6 +83,7 @@ Subactions.propTypes = {
canReport: PropTypes.bool,
onAddToStudioClicked: PropTypes.func,
onAddToStudioClosed: PropTypes.func,
onCopyProjectLink: PropTypes.func,
onReportClicked: PropTypes.func.isRequired,
onReportClose: PropTypes.func.isRequired,
onReportSubmit: PropTypes.func.isRequired,

View file

@ -54,7 +54,7 @@
text-decoration: none;
line-height: .875rem;
font-size: .75rem;
font-weight: normal;
font-weight: bold;
&.studio-button,
&.copy-link-button,

View file

@ -0,0 +1,51 @@
const React = require('react');
const FormattedMessage = require('react-intl').FormattedMessage;
require('./unsupported-browser.scss');
const UnsupportedBrowser = () => (
<div className="unsupported-browser">
<div className="content" >
<div className="illustration" />
<div className="body">
<h2>
<FormattedMessage id="general.unsupportedBrowser" />
</h2>
<p>
<FormattedMessage id="general.unsupportedBrowserDescription" />
</p>
<div className="button-row">
{ /* eslint-disable react/jsx-no-bind */ }
<button
className="back-button"
onClick={
() => (window.history.back())
}
>
<FormattedMessage id="general.back" />
</button>
{ /* eslint-enable react/jsx-no-bind */ }
</div>
<div className="faq-link-text">
<FormattedMessage
id="general.3faq"
values={{
faqLink: (
<a
className="faq-link"
href="/info/faq"
>
<FormattedMessage id="general.faq" />
</a>
)
}}
/>
</div>
</div>
</div>
</div>
);
module.exports = UnsupportedBrowser;

View file

@ -0,0 +1,80 @@
@import "../../colors";
#view {
position: relative;
padding: 0;
width: 100%;
}
.unsupported-browser {
position: absolute;
background-color: $ui-blue;
width: 100%;
height: 100%;
h2 {
font-size: 1.5rem;
}
.content {
margin: 100px auto;
outline: none;
border: .25rem solid $ui-white-15percent;
border-radius: .5rem;
padding: 0;
width: 500px;
overflow: hidden;
color: $type-gray;
user-select: none;
}
.illustration {
background-color: $ui-blue;
background-image: url("/images/unsupported.png");
background-size: cover;
width: 100%;
height: 208px;
}
[dir="rtl"] .illustration {
transform: scaleX(-1);
}
.body {
background: $ui-white;
padding: 1.5rem 2.25rem;
text-align: center;
}
/* Confirmation buttons at the bottom of the modal */
.button-row {
display: flex;
margin: 1.5rem 0;
text-align: right;
font-weight: bolder;
justify-content: center;
}
.back-button {
border: 1px solid $ui-blue;
border-radius: .25rem;
background: $ui-blue;
cursor: pointer;
padding: .5rem 2rem;
color: $ui-white;
font-size: .875rem;
font-weight: bold;
}
.faq-link-text {
margin: 2rem 0 .5rem 0;
color: $type-gray;
font-size: .875rem;
}
.faq-link {
text-decoration: none;
color: $ui-blue;
}
}

View file

@ -0,0 +1,34 @@
{
"onePointFour.intro": "The previous version of Scratch, version 1.4, is still available for download.",
"onePointFour.introNoteLabel": "Note:",
"onePointFour.introNote": "{noteLabel} You can still share projects from 1.4 to the Scratch website. However, projects created in Scratch 2.0 cannot be opened in 1.4.",
"onePointFour.downloads": "Downloads",
"onePointFour.macTitle": "Mac OS X",
"onePointFour.macBody": "Compatible with Mac OSX 10.4 or later",
"onePointFour.windowsTitle": "Windows",
"onePointFour.windowsBody": "Compatible with Windows 2000, XP, Vista, 7, and 8",
"onePointFour.windowsNetworkInstaller": "installer",
"onePointFour.windowsNetwork": "Network deployments use the {windowsNetworkInstaller}",
"onePointFour.linuxTitle": "Debian / Ubuntu",
"onePointFour.linuxBody": "Compatible with Ubuntu 12.04 or later",
"onePointFour.linuxInstall": "Install Scratch with Software Center",
"onePointFour.linuxOptions": "{linuxInstall} or {linuxDownload}",
"onePointFour.linuxDownload": "download here",
"onePointFour.faqsTitle": "Frequently Asked Questions",
"onePointFour.resourcesQ": "What resources are available to help me learn how to use Scratch 1.4?",
"onePointFour.gettingStartedGuide": "Scratch 1.4 Getting Started Guide",
"onePointFour.referenceGuide": "Scratch 1.4 Reference Guide",
"onePointFour.scratchCards": "Scratch Cards",
"onePointFour.resourcesA": "For a step-by-step introduction, download the {gettingStartedGuide}. The {referenceGuide} has a thorough explanation of the Scratch interface and programming language. {scratchCards} provide brief explanations that show how to make animations and interactive projects with Scratch.",
"onePointFour.requirementsQ": "What are the system requirements for Scratch 1.4?",
"onePointFour.requirementsDisplay": "Display: 800 x 480 or larger, thousands or millions of colors (16-bit color or greater) ",
"onePointFour.requirementsOS": "Operating System: Windows 2000 or later, Mac OS X 10.4 or later, Ubuntu Linux 9.04 or later (For other versions of Linux, see the Linux Installer page) ",
"onePointFour.requirementsDisk": "Disk: at least 120 megabytes of free space to install Scratch.",
"onePointFour.requirementsCPUMemory": "CPU and memory: Most computers have enough memory to run Scratch 1.4, but very old computers may run Scratch slowly.",
"onePointFour.requirementsSoundVideo": "Sound / Video: Sound playback requires speakers (or headphones), and recording requires a microphone. Many laptops have speakers and microphones built in. Scratch 1.4 can use a USB or built-in camera (optional).",
"onePointFour.errorQ": "What if I get an error when I try to upload or share my project to the Scratch website?",
"onePointFour.errorFileTooBig": "File too big. The largest file limit is 10 MB. To shrink the size of your Scratch project, click the Edit menu > and select compress sounds, or compress images. If you have compressed sounds and your project still won't upload, try deleting some of the sounds to make it smaller. ",
"onePointFour.errorInternet": "Internet connection too slow and times out. Try making the file smaller. If that doesn't work, try connecting from a different computer or connection or web browser.",
"onePointFour.errorProxy": "Proxy server getting in the way. Try an Internet connection that does not go through a proxy server, or configure Scratch to use your proxy server. ",
"onePointFour.errorLogin": "Wrong username and password. Try logging into Scratch website to make sure username and password are correct. "
}

View file

@ -0,0 +1,183 @@
const FormattedMessage = require('react-intl').FormattedMessage;
const React = require('react');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
const TitleBanner = require('../../components/title-banner/title-banner.jsx');
const Page = require('../../components/page/www/page.jsx');
const render = require('../../lib/render.jsx');
require('./scratch_1.4.scss');
const OnePointFour = () => (
<div className="download">
<TitleBanner className="masthead">
<div className="inner">
<h1 className="title-banner-h1">
Scratch 1.4
</h1>
<p className="title-banner-p intro">
<FormattedMessage id="onePointFour.intro" />
</p>
</div>
<div className="band">
<SubNavigation className="inner">
<a
className="sub-nav-item"
href="#installation"
>
<li>
<FormattedMessage id="onePointFour.downloads" />
</li>
</a>
<a
className="sub-nav-item"
href="#faqs"
>
<li>
<FormattedMessage id="onePointFour.faqsTitle" />
</li>
</a>
</SubNavigation>
</div>
</TitleBanner>
<div className="download-content">
<section
className="installation"
id="installation"
>
<div className="inner">
<p className="callout">
<FormattedMessage
id="onePointFour.introNote"
values={{
noteLabel: (
<b><FormattedMessage id="onePointFour.introNoteLabel" /></b>
)
}}
/>
</p>
<FlexRow className="three-col-row">
<div className="installation-column">
<img
alt="scratch for mac"
src="/images/scratch_1.4/scratch-mac.png"
/>
<h3><FormattedMessage id="onePointFour.macTitle" /></h3>
<p><FormattedMessage id="onePointFour.macBody" /></p>
<ul className="installation-downloads">
<li className="installation-downloads-item">
<a href="http://download.scratch.mit.edu/MacScratch1.4.dmg">
MacScratch1.4.dmg
</a>
</li>
</ul>
</div>
<div className="installation-column">
<img
alt="scratch for windows"
src="/images/scratch_1.4/scratch-win.png"
/>
<h3><FormattedMessage id="onePointFour.windowsTitle" /></h3>
<p><FormattedMessage id="onePointFour.windowsBody" /></p>
<ul
className="installation-downloads"
key="installation-downloads"
>
<li className="installation-downloads-item">
<a href="http://download.scratch.mit.edu/ScratchInstaller1.4.exe">
ScratchInstaller1.4.exe
</a>
</li>
<li className="installation-downloads-item">
<FormattedMessage
id="onePointFour.windowsNetwork"
values={{
windowsNetworkInstaller: (
<a href="http://download.scratch.mit.edu/Scratch1.4.msi.installer.zip">
<FormattedMessage id="onePointFour.windowsNetworkInstaller" />
</a>
)
}}
/>
</li>
</ul>
</div>
<div className="installation-column">
<img
alt="scratch for linux"
src="/images/scratch_1.4/scratch-linux.png"
/>
<h3><FormattedMessage id="onePointFour.linuxTitle" /></h3>
<p><FormattedMessage id="onePointFour.linuxBody" /></p>
<ul className="installation-downloads">
<li className="installation-downloads-item">
<FormattedMessage
id="onePointFour.linuxOptions"
values={{
linuxInstall: (
<a href="apt:scratch">
<FormattedMessage id="onePointFour.linuxInstall" />
</a>
),
linuxDownload: (
<a href="http://ubuntu.media.mit.edu/ubuntu//pool/universe/s/scratch/scratch_1.4.0.6~dfsg1-5~ubuntu12.04.1_all.deb">
<FormattedMessage id="onePointFour.linuxDownload" />
</a>
)
}}
/>
</li>
</ul>
</div>
</FlexRow>
</div>
</section>
<div className="inner">
<section id="faqs">
<span className="nav-spacer" />
<h2><FormattedMessage id="onePointFour.faqsTitle" /></h2>
<h3><FormattedMessage id="onePointFour.resourcesQ" /></h3>
<p>
<FormattedMessage
id="onePointFour.resourcesA"
values={{
gettingStartedGuide: (
<a href="http://download.scratch.mit.edu/ScratchGettingStartedv14.pdf">
<FormattedMessage id="onePointFour.gettingStartedGuide" />
</a>
),
referenceGuide: (
<a href="http://download.scratch.mit.edu/ScratchReferenceGuide14.pdf">
<FormattedMessage id="onePointFour.referenceGuide" />
</a>
),
scratchCards: (
<a href="http://download.scratch.mit.edu/ScratchCardsAll-v1.4-PDF.zip">
<FormattedMessage id="onePointFour.scratchCards" />
</a>
)
}}
/>
</p>
<h3><FormattedMessage id="onePointFour.requirementsQ" /></h3>
<p><FormattedMessage id="onePointFour.requirementsDisplay" /></p>
<p><FormattedMessage id="onePointFour.requirementsOS" /></p>
<p><FormattedMessage id="onePointFour.requirementsDisk" /></p>
<p><FormattedMessage id="onePointFour.requirementsCPUMemory" /></p>
<p><FormattedMessage id="onePointFour.requirementsSoundVideo" /></p>
<h3><FormattedMessage id="onePointFour.errorQ" /></h3>
<ol>
<li><FormattedMessage id="onePointFour.errorFileTooBig" /></li>
<li><FormattedMessage id="onePointFour.errorInternet" /></li>
<li><FormattedMessage id="onePointFour.errorProxy" /></li>
<li><FormattedMessage id="onePointFour.errorLogin" /></li>
</ol>
</section>
</div>
</div>
</div>
);
render(<Page><OnePointFour /></Page>, document.getElementById('app'));

View file

@ -0,0 +1,120 @@
@import "../../colors";
@import "../../frameless";
#view {
padding: 0;
}
.download {
.title-banner {
&.masthead {
margin-bottom: 0;
background-color: $ui-blue-dark;
padding-bottom: 0;
h1 {
margin: 0 0 2rem 0;
text-align: left;
color: $ui-white;
}
p {
margin: 0;
text-align: left;
color: $ui-white;
a {
border-bottom: 1px solid $ui-white;
color: $ui-white;
}
}
.band {
margin-top: 2rem;
background-color: $ui-white-15percent;
padding: 1rem 0;
}
.sub-nav {
text-align: left;
justify-content: flex-start;
li {
margin: 0 .5rem 0 0;
}
}
}
}
.sub-nav-item {
margin: .5rem;
}
.callout {
text-align: center;
}
.download-content {
padding-bottom: 2rem;
}
.three-col-row {
align-items: flex-start;
}
.installation {
background-color: $ui-gray;
padding: 2rem;
}
.installation-column {
padding-top: 40px;
max-width: $cols4;
text-align: center;
p {
margin-right: .5rem;
margin-left: .5rem;
}
}
.installation-downloads {
padding-left: 0;
list-style: none;
}
.installation-downloads-item {
margin: .25rem;
padding: 0;
text-align: center;
}
section {
margin-bottom: 2rem;
}
.mod-link {
color: $ui-white;
}
#faqs {
h2 {
margin-bottom: 1rem;
}
}
@media #{$small} {
.inner {
.installation-column {
max-width: 100%;
}
}
}
@media #{$intermediate-and-smaller} {
.three-col-row {
flex-direction: column;
align-items: center;
}
}
}

BIN
static/images/404-giga.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -0,0 +1,46 @@
<svg id="livetype" xmlns="http://www.w3.org/2000/svg" width="119.66407" height="40" viewBox="0 0 119.66407 40">
<title>Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917</title>
<g>
<g>
<g>
<path d="M110.13477,0H9.53468c-.3667,0-.729,0-1.09473.002-.30615.002-.60986.00781-.91895.0127A13.21476,13.21476,0,0,0,5.5171.19141a6.66509,6.66509,0,0,0-1.90088.627A6.43779,6.43779,0,0,0,1.99757,1.99707,6.25844,6.25844,0,0,0,.81935,3.61816a6.60119,6.60119,0,0,0-.625,1.90332,12.993,12.993,0,0,0-.1792,2.002C.00587,7.83008.00489,8.1377,0,8.44434V31.5586c.00489.3105.00587.6113.01515.9219a12.99232,12.99232,0,0,0,.1792,2.0019,6.58756,6.58756,0,0,0,.625,1.9043A6.20778,6.20778,0,0,0,1.99757,38.001a6.27445,6.27445,0,0,0,1.61865,1.1787,6.70082,6.70082,0,0,0,1.90088.6308,13.45514,13.45514,0,0,0,2.0039.1768c.30909.0068.6128.0107.91895.0107C8.80567,40,9.168,40,9.53468,40H110.13477c.3594,0,.7246,0,1.084-.002.3047,0,.6172-.0039.9219-.0107a13.279,13.279,0,0,0,2-.1768,6.80432,6.80432,0,0,0,1.9082-.6308,6.27742,6.27742,0,0,0,1.6172-1.1787,6.39482,6.39482,0,0,0,1.1816-1.6143,6.60413,6.60413,0,0,0,.6191-1.9043,13.50643,13.50643,0,0,0,.1856-2.0019c.0039-.3106.0039-.6114.0039-.9219.0078-.3633.0078-.7246.0078-1.0938V9.53613c0-.36621,0-.72949-.0078-1.09179,0-.30664,0-.61426-.0039-.9209a13.5071,13.5071,0,0,0-.1856-2.002,6.6177,6.6177,0,0,0-.6191-1.90332,6.46619,6.46619,0,0,0-2.7988-2.7998,6.76754,6.76754,0,0,0-1.9082-.627,13.04394,13.04394,0,0,0-2-.17676c-.3047-.00488-.6172-.01074-.9219-.01269-.3594-.002-.7246-.002-1.084-.002Z" style="fill: #a6a6a6"/>
<path d="M8.44483,39.125c-.30468,0-.602-.0039-.90429-.0107a12.68714,12.68714,0,0,1-1.86914-.1631,5.88381,5.88381,0,0,1-1.65674-.5479,5.40573,5.40573,0,0,1-1.397-1.0166,5.32082,5.32082,0,0,1-1.02051-1.3965,5.72186,5.72186,0,0,1-.543-1.6572,12.41351,12.41351,0,0,1-.1665-1.875c-.00634-.2109-.01464-.9131-.01464-.9131V8.44434S.88185,7.75293.8877,7.5498a12.37039,12.37039,0,0,1,.16553-1.87207,5.7555,5.7555,0,0,1,.54346-1.6621A5.37349,5.37349,0,0,1,2.61183,2.61768,5.56543,5.56543,0,0,1,4.01417,1.59521a5.82309,5.82309,0,0,1,1.65332-.54394A12.58589,12.58589,0,0,1,7.543.88721L8.44532.875H111.21387l.9131.0127a12.38493,12.38493,0,0,1,1.8584.16259,5.93833,5.93833,0,0,1,1.6709.54785,5.59374,5.59374,0,0,1,2.415,2.41993,5.76267,5.76267,0,0,1,.5352,1.64892,12.995,12.995,0,0,1,.1738,1.88721c.0029.2832.0029.5874.0029.89014.0079.375.0079.73193.0079,1.09179V30.4648c0,.3633,0,.7178-.0079,1.0752,0,.3252,0,.6231-.0039.9297a12.73126,12.73126,0,0,1-.1709,1.8535,5.739,5.739,0,0,1-.54,1.67,5.48029,5.48029,0,0,1-1.0156,1.3857,5.4129,5.4129,0,0,1-1.3994,1.0225,5.86168,5.86168,0,0,1-1.668.5498,12.54218,12.54218,0,0,1-1.8692.1631c-.2929.0068-.5996.0107-.8974.0107l-1.084.002Z"/>
</g>
<g id="_Group_" data-name="&lt;Group&gt;">
<g id="_Group_2" data-name="&lt;Group&gt;">
<g id="_Group_3" data-name="&lt;Group&gt;">
<path id="_Path_" data-name="&lt;Path&gt;" d="M24.76888,20.30068a4.94881,4.94881,0,0,1,2.35656-4.15206,5.06566,5.06566,0,0,0-3.99116-2.15768c-1.67924-.17626-3.30719,1.00483-4.1629,1.00483-.87227,0-2.18977-.98733-3.6085-.95814a5.31529,5.31529,0,0,0-4.47292,2.72787c-1.934,3.34842-.49141,8.26947,1.3612,10.97608.9269,1.32535,2.01018,2.8058,3.42763,2.7533,1.38706-.05753,1.9051-.88448,3.5794-.88448,1.65876,0,2.14479.88448,3.591.8511,1.48838-.02416,2.42613-1.33124,3.32051-2.66914a10.962,10.962,0,0,0,1.51842-3.09251A4.78205,4.78205,0,0,1,24.76888,20.30068Z" style="fill: #fff"/>
<path id="_Path_2" data-name="&lt;Path&gt;" d="M22.03725,12.21089a4.87248,4.87248,0,0,0,1.11452-3.49062,4.95746,4.95746,0,0,0-3.20758,1.65961,4.63634,4.63634,0,0,0-1.14371,3.36139A4.09905,4.09905,0,0,0,22.03725,12.21089Z" style="fill: #fff"/>
</g>
</g>
<g>
<path d="M42.30227,27.13965h-4.7334l-1.13672,3.35645H34.42727l4.4834-12.418h2.083l4.4834,12.418H43.438ZM38.0591,25.59082h3.752l-1.84961-5.44727h-.05176Z" style="fill: #fff"/>
<path d="M55.15969,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238H48.4302v1.50586h.03418a3.21162,3.21162,0,0,1,2.88281-1.60059C53.645,21.34766,55.15969,23.16406,55.15969,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C52.30227,29.01563,53.24953,27.81934,53.24953,25.96973Z" style="fill: #fff"/>
<path d="M65.12453,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238H58.395v1.50586h.03418A3.21162,3.21162,0,0,1,61.312,21.34766C63.60988,21.34766,65.12453,23.16406,65.12453,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C62.26711,29.01563,63.21438,27.81934,63.21438,25.96973Z" style="fill: #fff"/>
<path d="M71.71047,27.03613c.1377,1.23145,1.334,2.04,2.96875,2.04,1.56641,0,2.69336-.80859,2.69336-1.91895,0-.96387-.67969-1.541-2.28906-1.93652l-1.60937-.3877c-2.28027-.55078-3.33887-1.61719-3.33887-3.34766,0-2.14258,1.86719-3.61426,4.51855-3.61426,2.624,0,4.42285,1.47168,4.4834,3.61426h-1.876c-.1123-1.23926-1.13672-1.9873-2.63379-1.9873s-2.52148.75684-2.52148,1.8584c0,.87793.6543,1.39453,2.25488,1.79l1.36816.33594c2.54785.60254,3.60645,1.626,3.60645,3.44238,0,2.32324-1.85059,3.77832-4.79395,3.77832-2.75391,0-4.61328-1.4209-4.7334-3.667Z" style="fill: #fff"/>
<path d="M83.34621,19.2998v2.14258h1.72168v1.47168H83.34621v4.99121c0,.77539.34473,1.13672,1.10156,1.13672a5.80752,5.80752,0,0,0,.61133-.043v1.46289a5.10351,5.10351,0,0,1-1.03223.08594c-1.833,0-2.54785-.68848-2.54785-2.44434V22.91406H80.16262V21.44238H81.479V19.2998Z" style="fill: #fff"/>
<path d="M86.065,25.96973c0-2.84863,1.67773-4.63867,4.29395-4.63867,2.625,0,4.29492,1.79,4.29492,4.63867,0,2.85645-1.66113,4.63867-4.29492,4.63867C87.72609,30.6084,86.065,28.82617,86.065,25.96973Zm6.69531,0c0-1.9541-.89551-3.10742-2.40137-3.10742s-2.40039,1.16211-2.40039,3.10742c0,1.96191.89453,3.10645,2.40039,3.10645S92.76027,27.93164,92.76027,25.96973Z" style="fill: #fff"/>
<path d="M96.18606,21.44238h1.77246v1.541h.043a2.1594,2.1594,0,0,1,2.17773-1.63574,2.86616,2.86616,0,0,1,.63672.06934v1.73828a2.59794,2.59794,0,0,0-.835-.1123,1.87264,1.87264,0,0,0-1.93652,2.083v5.37012h-1.8584Z" style="fill: #fff"/>
<path d="M109.3843,27.83691c-.25,1.64355-1.85059,2.77148-3.89844,2.77148-2.63379,0-4.26855-1.76465-4.26855-4.5957,0-2.83984,1.64355-4.68164,4.19043-4.68164,2.50488,0,4.08008,1.7207,4.08008,4.46582v.63672h-6.39453v.1123a2.358,2.358,0,0,0,2.43555,2.56445,2.04834,2.04834,0,0,0,2.09082-1.27344Zm-6.28223-2.70215h4.52637a2.1773,2.1773,0,0,0-2.2207-2.29785A2.292,2.292,0,0,0,103.10207,25.13477Z" style="fill: #fff"/>
</g>
</g>
</g>
<g id="_Group_4" data-name="&lt;Group&gt;">
<g>
<path d="M37.82619,8.731a2.63964,2.63964,0,0,1,2.80762,2.96484c0,1.90625-1.03027,3.002-2.80762,3.002H35.67092V8.731Zm-1.22852,5.123h1.125a1.87588,1.87588,0,0,0,1.96777-2.146,1.881,1.881,0,0,0-1.96777-2.13379h-1.125Z" style="fill: #fff"/>
<path d="M41.68068,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C44.57522,13.99463,45.01369,13.42432,45.01369,12.44434Z" style="fill: #fff"/>
<path d="M51.57326,14.69775h-.92187l-.93066-3.31641h-.07031l-.92676,3.31641h-.91309l-1.24121-4.50293h.90137l.80664,3.436h.06641l.92578-3.436h.85254l.92578,3.436h.07031l.80273-3.436h.88867Z" style="fill: #fff"/>
<path d="M53.85354,10.19482H54.709v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915h-.88867V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
<path d="M59.09377,8.437h.88867v6.26074h-.88867Z" style="fill: #fff"/>
<path d="M61.21779,12.44434a2.13346,2.13346,0,1,1,4.24756,0,2.1338,2.1338,0,1,1-4.24756,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C64.11232,13.99463,64.5508,13.42432,64.5508,12.44434Z" style="fill: #fff"/>
<path d="M66.4009,13.42432c0-.81055.60352-1.27783,1.6748-1.34424l1.21973-.07031v-.38867c0-.47559-.31445-.74414-.92187-.74414-.49609,0-.83984.18213-.93848.50049h-.86035c.09082-.77344.81836-1.26953,1.83984-1.26953,1.12891,0,1.76563.562,1.76563,1.51318v3.07666h-.85547v-.63281h-.07031a1.515,1.515,0,0,1-1.35254.707A1.36026,1.36026,0,0,1,66.4009,13.42432Zm2.89453-.38477v-.37646l-1.09961.07031c-.62012.0415-.90137.25244-.90137.64941,0,.40527.35156.64111.835.64111A1.0615,1.0615,0,0,0,69.29543,13.03955Z" style="fill: #fff"/>
<path d="M71.34816,12.44434c0-1.42285.73145-2.32422,1.86914-2.32422a1.484,1.484,0,0,1,1.38086.79h.06641V8.437h.88867v6.26074h-.85156v-.71143h-.07031a1.56284,1.56284,0,0,1-1.41406.78564C72.0718,14.772,71.34816,13.87061,71.34816,12.44434Zm.918,0c0,.95508.4502,1.52979,1.20313,1.52979.749,0,1.21191-.583,1.21191-1.52588,0-.93848-.46777-1.52979-1.21191-1.52979C72.72121,10.91846,72.26613,11.49707,72.26613,12.44434Z" style="fill: #fff"/>
<path d="M79.23,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C82.12453,13.99463,82.563,13.42432,82.563,12.44434Z" style="fill: #fff"/>
<path d="M84.66945,10.19482h.85547v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915H87.605V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
<path d="M93.51516,9.07373v1.1416h.97559v.74854h-.97559V13.2793c0,.47168.19434.67822.63672.67822a2.96657,2.96657,0,0,0,.33887-.02051v.74023a2.9155,2.9155,0,0,1-.4834.04541c-.98828,0-1.38184-.34766-1.38184-1.21582v-2.543h-.71484v-.74854h.71484V9.07373Z" style="fill: #fff"/>
<path d="M95.70461,8.437h.88086v2.48145h.07031a1.3856,1.3856,0,0,1,1.373-.80664,1.48339,1.48339,0,0,1,1.55078,1.67871v2.90723H98.69v-2.688c0-.71924-.335-1.0835-.96289-1.0835a1.05194,1.05194,0,0,0-1.13379,1.1416v2.62988h-.88867Z" style="fill: #fff"/>
<path d="M104.76125,13.48193a1.828,1.828,0,0,1-1.95117,1.30273A2.04531,2.04531,0,0,1,100.73,12.46045a2.07685,2.07685,0,0,1,2.07617-2.35254c1.25293,0,2.00879.856,2.00879,2.27V12.688h-3.17969v.0498a1.1902,1.1902,0,0,0,1.19922,1.29,1.07934,1.07934,0,0,0,1.07129-.5459Zm-3.126-1.45117h2.27441a1.08647,1.08647,0,0,0-1.1084-1.1665A1.15162,1.15162,0,0,0,101.63527,12.03076Z" style="fill: #fff"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 864 312" enable-background="new 0 0 864 312" xml:space="preserve">
<g id="Layer_2">
<rect x="2.3" y="2.3" width="859.5" height="307.5"/>
<path fill="#D2D2D2" d="M4.5,4.5h855v303H4.5V4.5z M0,312h864V0H0V312z"/>
<g>
<defs>
<rect id="SVGID_1_" y="0" width="864" height="312"/>
</defs>
<clipPath id="SVGID_2_">
<use xlink:href="#SVGID_1_" overflow="visible"/>
</clipPath>
</g>
</g>
<g id="Layer_1">
<rect x="70.5" y="68.6" fill="#F25022" width="83.8" height="83.8"/>
<rect x="163" y="68.6" fill="#7FBA00" width="83.8" height="83.8"/>
<rect x="70.5" y="161" fill="#00A4EF" width="83.8" height="83.8"/>
<rect x="163" y="161" fill="#FFB900" width="83.8" height="83.8"/>
<path fill="#FFFFFF" d="M408.3,163c0-2.5,0.9-4.5,2.7-6.2c1.8-1.7,3.9-2.5,6.4-2.5c2.6,0,4.8,0.9,6.5,2.6c1.7,1.7,2.6,3.8,2.6,6.1
c0,2.4-0.9,4.5-2.7,6.1c-1.8,1.7-3.9,2.5-6.5,2.5c-2.6,0-4.7-0.8-6.5-2.5C409.2,167.4,408.3,165.4,408.3,163 M424.8,244.8h-14.9
v-63.5h14.9V244.8z"/>
<path fill="#FFFFFF" d="M470.2,233.9c2.2,0,4.7-0.5,7.4-1.5c2.7-1,5.1-2.4,7.4-4.1v13.9c-2.4,1.4-5.1,2.4-8.1,3.1
c-3,0.7-6.4,1.1-10,1.1c-9.3,0-16.9-3-22.8-8.9c-5.9-5.9-8.8-13.5-8.8-22.6c0-10.2,3-18.6,9-25.2c6-6.6,14.4-9.9,25.4-9.9
c2.8,0,5.6,0.4,8.5,1.1c2.9,0.7,5.1,1.6,6.8,2.5v14.3c-2.3-1.7-4.7-3-7.1-3.9c-2.4-0.9-4.9-1.4-7.4-1.4c-5.9,0-10.6,1.9-14.3,5.7
c-3.6,3.8-5.4,9-5.4,15.5c0,6.4,1.7,11.4,5.2,15C459.5,232.2,464.2,233.9,470.2,233.9"/>
<path fill="#FFFFFF" d="M527.5,180.3c1.2,0,2.3,0.1,3.2,0.2c0.9,0.2,1.8,0.4,2.4,0.6v15.1c-0.8-0.6-1.9-1.1-3.4-1.6
c-1.5-0.5-3.3-0.8-5.5-0.8c-3.7,0-6.8,1.5-9.3,4.6c-2.5,3.1-3.8,7.8-3.8,14.3v32.1h-14.9v-63.5h14.9v10h0.2
c1.4-3.5,3.4-6.2,6.2-8.1C520.3,181.3,523.6,180.3,527.5,180.3"/>
<path fill="#FFFFFF" d="M533.9,214c0-10.5,3-18.8,8.9-25c5.9-6.1,14.2-9.2,24.7-9.2c9.9,0,17.7,3,23.3,8.9
c5.6,5.9,8.4,13.9,8.4,23.9c0,10.3-3,18.5-8.9,24.6c-5.9,6.1-14,9.1-24.2,9.1c-9.8,0-17.7-2.9-23.4-8.7
C536.8,231.9,533.9,224,533.9,214 M549.5,213.5c0,6.6,1.5,11.7,4.5,15.2c3,3.5,7.3,5.2,12.9,5.2c5.4,0,9.6-1.7,12.4-5.2
s4.3-8.7,4.3-15.6c0-6.8-1.5-12-4.4-15.5c-2.9-3.5-7.1-5.2-12.4-5.2c-5.5,0-9.7,1.8-12.8,5.5C551,201.6,549.5,206.7,549.5,213.5"/>
<path fill="#FFFFFF" d="M621.4,198c0,2.1,0.7,3.8,2,5c1.4,1.2,4.4,2.7,9,4.6c6,2.4,10.2,5.1,12.6,8.1c2.4,3,3.6,6.6,3.6,10.8
c0,6-2.3,10.8-6.9,14.4c-4.6,3.6-10.8,5.4-18.6,5.4c-2.6,0-5.5-0.3-8.7-1c-3.2-0.6-5.9-1.5-8.1-2.4v-14.7c2.7,1.9,5.6,3.4,8.8,4.5
c3.1,1.1,6,1.7,8.5,1.7c3.4,0,5.9-0.5,7.5-1.4c1.6-0.9,2.4-2.5,2.4-4.8c0-2.1-0.8-3.8-2.5-5.2c-1.7-1.4-4.8-3.1-9.5-4.9
c-5.5-2.3-9.4-4.9-11.7-7.8c-2.3-2.9-3.5-6.6-3.5-11c0-5.7,2.3-10.4,6.8-14.1c4.5-3.7,10.4-5.5,17.7-5.5c2.2,0,4.7,0.2,7.5,0.7
c2.8,0.5,5.1,1.1,6.9,1.9v14.2c-2-1.3-4.3-2.4-6.9-3.4c-2.6-0.9-5.3-1.4-7.8-1.4c-2.8,0-5.1,0.6-6.6,1.7
C622.2,194.5,621.4,196.1,621.4,198"/>
<path fill="#FFFFFF" d="M655.2,214c0-10.5,3-18.8,8.9-25c5.9-6.1,14.2-9.2,24.7-9.2c9.9,0,17.7,3,23.3,8.9
c5.6,5.9,8.4,13.9,8.4,23.9c0,10.3-3,18.5-8.9,24.6c-5.9,6.1-14,9.1-24.2,9.1c-9.8,0-17.7-2.9-23.4-8.7
C658.1,231.9,655.2,224,655.2,214 M670.7,213.5c0,6.6,1.5,11.7,4.5,15.2c3,3.5,7.3,5.2,12.9,5.2c5.4,0,9.6-1.7,12.4-5.2
s4.3-8.7,4.3-15.6c0-6.8-1.5-12-4.4-15.5s-7.1-5.2-12.4-5.2c-5.5,0-9.7,1.8-12.8,5.5C672.2,201.6,670.7,206.7,670.7,213.5"/>
<path fill="#FFFFFF" d="M769.8,193.6v32.6c0,6.8,1.6,11.8,4.7,15.2c3.1,3.4,7.9,5,14.2,5c2.1,0,4.3-0.2,6.5-0.7
c2.2-0.5,3.8-0.9,4.7-1.5v-12.4c-0.9,0.6-2,1.1-3.2,1.5c-1.2,0.4-2.3,0.6-3.1,0.6c-3,0-5.3-0.8-6.7-2.4c-1.4-1.6-2.1-4.4-2.1-8.3
v-29.7H800v-12.2h-15.1v-18.8l-15,4.6v14.3h-22.3v-7.7c0-3.8,0.8-6.7,2.5-8.7c1.7-2,4.1-2.9,7.2-2.9c1.6,0,3,0.2,4.3,0.6
c1.2,0.4,2.1,0.8,2.6,1.1v-12.9c-1.1-0.4-2.3-0.6-3.7-0.8c-1.4-0.2-3-0.3-4.8-0.3c-6.8,0-12.4,2.1-16.7,6.4
c-4.3,4.3-6.5,9.7-6.5,16.4v8.8h-10.6v12.2h10.6v51.3h15.1v-51.3H769.8z"/>
<polygon fill="#FFFFFF" points="395.5,156.2 395.5,244.8 380.1,244.8 380.1,175.4 379.9,175.4 352.4,244.8 342.2,244.8 314,175.4
313.8,175.4 313.8,244.8 299.6,244.8 299.6,156.2 321.6,156.2 347.1,221.9 347.5,221.9 374.4,156.2 "/>
<path fill="#FFFFFF" d="M340,117.9c-5.3,3-11.3,4.5-18,4.5c-5.1,0-9.6-1.1-13.6-3.4c-3.9-2.2-7-5.4-9.1-9.5
c-2.1-4.1-3.2-8.7-3.2-13.8c0-5.4,1.2-10.3,3.5-14.5s5.6-7.6,9.9-10c4.3-2.4,9.1-3.6,14.5-3.6c2.7,0,5.4,0.3,8.1,0.8
c2.7,0.5,4.9,1.2,6.6,2v8c-4-2.7-9.1-4-15.3-4c-3.7,0-7,0.9-10,2.7c-3,1.8-5.3,4.3-6.9,7.4c-1.6,3.2-2.4,6.8-2.4,10.8
c0,6.3,1.7,11.3,5,15c3.3,3.7,7.9,5.5,13.7,5.5c3.7,0,7-0.7,9.8-2.2V99.9h-11.3v-6.6H340V117.9z"/>
<path fill="#FFFFFF" d="M383.1,104.6h-26.2c0.1,3.8,1.3,6.7,3.3,8.8c2.1,2,4.8,3.1,8.2,3.1c4.5,0,8.4-1.3,11.8-3.9v6.4
c-1.5,1-3.4,1.9-5.7,2.5c-2.3,0.6-4.7,0.9-7.1,0.9c-5.7,0-10.2-1.7-13.3-5.1c-3.1-3.4-4.7-8.2-4.7-14.4c0-3.8,0.8-7.3,2.3-10.3
c1.5-3.1,3.7-5.5,6.4-7.2c2.7-1.7,5.8-2.6,9.1-2.6c4.9,0,8.8,1.6,11.6,4.8c2.8,3.2,4.2,7.6,4.2,13.3V104.6z M375.8,98.9
c0-3.3-0.8-5.8-2.3-7.5c-1.5-1.7-3.6-2.6-6.4-2.6c-2.5,0-4.7,0.9-6.6,2.7s-3,4.3-3.6,7.4H375.8z"/>
<path fill="#FFFFFF" d="M411.5,121.1c-0.7,0.4-1.6,0.7-2.9,0.9c-1.2,0.2-2.3,0.3-3.3,0.3c-7,0-10.5-3.9-10.5-11.6V89.7h-6.4v-6h6.4
v-9l7.3-2.3v11.2h9.3v6h-9.3v19.8c0,2.5,0.4,4.3,1.2,5.3c0.8,1,2.2,1.5,4.3,1.5c1.4,0,2.7-0.4,3.8-1.2V121.1z"/>
<path fill="#FFFFFF" d="M447.7,70.3c0,1.2-0.4,2.2-1.3,3.1c-0.9,0.9-2,1.3-3.3,1.3c-1.3,0-2.4-0.4-3.3-1.3
c-0.9-0.8-1.3-1.9-1.3-3.2c0-1.3,0.5-2.4,1.4-3.2c0.9-0.8,2-1.3,3.2-1.3c1.2,0,2.3,0.4,3.2,1.3C447.3,68,447.7,69,447.7,70.3
M446.7,121.5h-7.3V83.7h7.3V121.5z"/>
<path fill="#FFFFFF" d="M478.4,121.1c-0.7,0.4-1.6,0.7-2.9,0.9c-1.2,0.2-2.3,0.3-3.3,0.3c-7,0-10.5-3.9-10.5-11.6V89.7h-6.4v-6h6.4
v-9l7.3-2.3v11.2h9.3v6h-9.3v19.8c0,2.5,0.4,4.3,1.2,5.3c0.8,1,2.2,1.5,4.3,1.5c1.4,0,2.7-0.4,3.8-1.2V121.1z"/>
<path fill="#FFFFFF" d="M526.2,71.7c-1.2-0.6-2.5-1-4-1c-4.1,0-6.1,2.5-6.1,7.5v5.4h8.6v6h-8.6v31.8h-7.3V89.7h-6.4v-6h6.4v-5.8
c0-4,1.1-7.2,3.4-9.6c2.3-2.4,5.4-3.6,9.4-3.6c2,0,3.5,0.2,4.7,0.7V71.7z"/>
<path fill="#FFFFFF" d="M553.1,91c-0.4-0.3-1.2-0.6-2.2-0.9c-1-0.2-1.9-0.4-2.6-0.4c-2.6,0-4.7,1.2-6.3,3.5c-1.6,2.3-2.4,5.3-2.4,9
v19.3h-7.3V83.7h7.3v7.6h0.2c0.8-2.6,2.1-4.6,3.8-6.1c1.7-1.5,3.7-2.2,5.9-2.2c1.5,0,2.7,0.2,3.5,0.5V91z"/>
<path fill="#FFFFFF" d="M594.8,102.4c0,6.1-1.8,11-5.3,14.6c-3.5,3.6-8.2,5.4-14,5.4c-5.7,0-10.3-1.8-13.7-5.3
c-3.4-3.5-5.1-8.2-5.1-14.1c0-6.3,1.8-11.2,5.3-14.8c3.5-3.6,8.3-5.4,14.4-5.4c5.7,0,10.2,1.7,13.4,5.2
C593.2,91.5,594.8,96.3,594.8,102.4 M587.3,102.6c0-4.5-1-7.9-2.9-10.2c-2-2.3-4.7-3.5-8.3-3.5c-3.6,0-6.5,1.2-8.6,3.7
c-2.1,2.4-3.1,5.9-3.1,10.3c0,4.3,1,7.6,3.1,10c2.1,2.4,4.9,3.6,8.6,3.6c3.7,0,6.5-1.2,8.4-3.5
C586.3,110.5,587.3,107.1,587.3,102.6"/>
<path fill="#FFFFFF" d="M660.1,121.5h-7.3V100c0-3.9-0.6-6.7-1.8-8.5c-1.2-1.8-3.3-2.6-6.2-2.6c-2.4,0-4.5,1.1-6.1,3.3
c-1.7,2.2-2.5,4.9-2.5,7.9v21.4h-7.3V99.3c0-6.9-2.7-10.4-8-10.4c-2.5,0-4.6,1.1-6.2,3.2s-2.4,4.8-2.4,8.1v21.4h-7.3V83.7h7.3v5.9
h0.1c2.7-4.6,6.7-6.8,11.8-6.8c2.4,0,4.6,0.7,6.6,2c1.9,1.3,3.3,3.2,4.2,5.6c1.5-2.6,3.2-4.5,5.3-5.8c2.1-1.2,4.5-1.9,7.3-1.9
c8.3,0,12.4,5.1,12.4,15.4V121.5z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View file

@ -0,0 +1 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 300 182" xml:space="preserve"><style>.st1{fill:#f90}</style><path d="M84.2 66.3c0 3.7.4 6.7 1.1 8.9.8 2.2 1.8 4.6 3.2 7.2.5.8.7 1.6.7 2.3 0 1-.6 2-1.9 3L81 91.8c-.9.6-1.8.9-2.6.9-1 0-2-.5-3-1.4-1.4-1.5-2.6-3.1-3.6-4.7-1-1.7-2-3.6-3.1-5.9-7.7 9.2-17.5 13.8-29.3 13.8-8.4 0-15.1-2.4-20-7.2-4.9-4.8-7.4-11.2-7.4-19.2 0-8.5 3-15.4 9.1-20.6s14.2-7.8 24.4-7.8c3.4 0 6.9.3 10.6.8 3.7.5 7.5 1.3 11.5 2.2v-7.3c0-7.6-1.6-12.9-4.7-16-3.2-3.1-8.6-4.6-16.3-4.6-3.5 0-7.1.4-10.8 1.3-3.7.9-7.3 2-10.8 3.4-1.6.7-2.8 1.1-3.5 1.3-.7.2-1.2.3-1.6.3-1.4 0-2.1-1-2.1-3.1v-4.9c0-1.6.2-2.8.7-3.5s1.4-1.4 2.8-2.1C24.8 5.7 29 4.2 33.9 3 38.8 1.7 44 1.1 49.5 1.1c11.9 0 20.6 2.7 26.1 8.1C81.2 14.8 84 23 84 33.9v32.3h.2zM43.7 81.4c3.3 0 6.7-.6 10.3-1.8 3.6-1.2 6.8-3.4 9.5-6.4 1.6-1.9 2.8-4 3.4-6.4.6-2.4 1-5.3 1-8.7V54c-2.9-.7-6-1.3-9.2-1.7s-6.3-.6-9.4-.6c-6.7 0-11.6 1.3-14.9 4-3.3 2.7-4.9 6.5-4.9 11.5 0 4.7 1.2 8.2 3.7 10.6 2.4 2.4 5.9 3.6 10.5 3.6zm80.1 10.8c-1.8 0-3-.3-3.8-1-.8-.6-1.5-2-2.1-3.9L94.5 10.2c-.6-2-.9-3.3-.9-4 0-1.6.8-2.5 2.4-2.5h9.8c1.9 0 3.2.3 3.9 1 .8.6 1.4 2 2 3.9l16.8 66.1L144 8.6c.5-2 1.1-3.3 1.9-3.9.8-.6 2.2-1 4-1h8c1.9 0 3.2.3 4 1 .8.6 1.5 2 1.9 3.9l15.8 66.9 17.3-66.9c.6-2 1.3-3.3 2-3.9.8-.6 2.1-1 3.9-1h9.3c1.6 0 2.5.8 2.5 2.5 0 .5-.1 1-.2 1.6-.1.6-.3 1.4-.7 2.5l-24 77.1c-.6 2-1.3 3.3-2.1 3.9-.8.6-2.1 1-3.8 1H175c-1.9 0-3.2-.3-4-1s-1.5-2-1.9-4L153.7 23l-15.4 64.3c-.5 2-1.1 3.3-1.9 4-.8.7-2.2 1-4 1h-8.6zM252 94.9c-5.2 0-10.4-.6-15.4-1.8-5-1.2-8.9-2.5-11.5-4-1.6-.9-2.7-1.9-3.1-2.8-.4-.9-.6-1.9-.6-2.8v-5.1c0-2.1.8-3.1 2.3-3.1.6 0 1.2.1 1.8.3.6.2 1.5.6 2.5 1 3.4 1.5 7.1 2.7 11 3.5 4 .8 7.9 1.2 11.9 1.2 6.3 0 11.2-1.1 14.6-3.3 3.4-2.2 5.2-5.4 5.2-9.5 0-2.8-.9-5.1-2.7-7-1.8-1.9-5.2-3.6-10.1-5.2l-14.5-4.5c-7.3-2.3-12.7-5.7-16-10.2-3.3-4.4-5-9.3-5-14.5 0-4.2.9-7.9 2.7-11.1s4.2-6 7.2-8.2c3-2.3 6.4-4 10.4-5.2S250.9.9 255.3.9c2.2 0 4.5.1 6.7.4 2.3.3 4.4.7 6.5 1.1 2 .5 3.9 1 5.7 1.6s3.2 1.2 4.2 1.8c1.4.8 2.4 1.6 3 2.5.6.8.9 1.9.9 3.3v4.7c0 2.1-.8 3.2-2.3 3.2-.8 0-2.1-.4-3.8-1.2-5.7-2.6-12.1-3.9-19.2-3.9-5.7 0-10.2.9-13.3 2.8-3.1 1.9-4.7 4.8-4.7 8.9 0 2.8 1 5.2 3 7.1 2 1.9 5.7 3.8 11 5.5l14.2 4.5c7.2 2.3 12.4 5.5 15.5 9.6 3.1 4.1 4.6 8.8 4.6 14 0 4.3-.9 8.2-2.6 11.6-1.8 3.4-4.2 6.4-7.3 8.8-3.1 2.5-6.8 4.3-11.1 5.6-4.5 1.4-9.2 2.1-14.3 2.1z" fill="#252f3e"/><path class="st1" d="M270.9 143.4c-32.8 24.2-80.5 37.1-121.5 37.1-57.5 0-109.3-21.3-148.4-56.6-3.1-2.8-.3-6.6 3.4-4.4 42.3 24.5 94.5 39.4 148.5 39.4 36.4 0 76.4-7.6 113.2-23.1 5.5-2.5 10.2 3.6 4.8 7.6z"/><path class="st1" d="M284.6 127.8c-4.2-5.4-27.7-2.6-38.4-1.3-3.2.4-3.7-2.4-.8-4.5 18.8-13.2 49.6-9.4 53.2-5 3.6 4.5-1 35.3-18.6 50.1-2.7 2.3-5.3 1.1-4.1-1.9 4-9.8 12.9-32.1 8.7-37.4z"/></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1 @@
<svg version="1.1" id="svg2" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 300 175" xml:space="preserve"><style>.st1{fill:#010101}.st2{fill:#fefefe}</style><path id="path1984" d="M145.9 0h145.9v145.8H145.9V0z" fill="#fff"/><path id="path6" class="st1" d="M0 0h145.9v145.8H0V0z"/><path id="path8" class="st2" d="M61.5 9.8c17.9-4.6 37.7-.8 52.8 9.8 7.9 5.5 14.5 12.8 19.3 21.1-10.3 5.8-20.4 11.7-30.7 17.5-1.8 1-3.7 2-5.5 3.2-2.8-4.8-7.4-8.7-12.7-10.5-3.6-1.3-7.5-1.5-11.2-1-10.9 1.4-20 11.5-19.8 22.6-.5 9.7 6 19.1 15.1 22.4 6.7 2.6 14.7 1.8 20.8-2.1 3.3-2 5.9-5 7.9-8.3 6.9 4.1 13.9 8 20.8 12 5.1 2.9 10.2 5.9 15.3 8.7-8.6 14.9-23.2 26.3-39.9 30.6-2.1.7-4.3.9-6.4 1.4-4.9.6-9.8 1.1-14.7.6-14.8-.8-29.1-7.1-39.9-17.2C21.6 110.4 14.2 96 12.4 80.8c-2-15.1 1.9-30.9 10.3-43.6 8.9-13.6 23-23.7 38.8-27.4z"/><path id="path10" class="st1" d="M156.3 10.5H198c14 17.3 27.8 34.8 41.8 52.1V10.5h41.7v125h-41.7c-13.6-17-27.3-34-40.9-51.1-.3-.3-.5-.6-.8-.9.1 17.3 0 34.6 0 52h-41.7c-.1-41.7-.1-83.4-.1-125z"/><path id="path12" class="st1" d="M4.8 165.2c-.2-4.1 2.8-8 6.8-9 4-1.3 8.6.7 10.6 4.3-1.5 1.1-3.3 1.8-4.8 2.9-.7-.8-1.5-1.7-2.7-1.9-1.9-.4-3.9 1-4.2 2.9-.3 1 .2 1.9.4 2.8 1.6 2.1 5.1 2 6.4-.3 1.6 1 3.2 1.9 4.8 2.8-1.7 2.8-4.9 4.9-8.3 4.5-4.7.5-9.2-4.1-9-9z"/><path id="path14" class="st1" d="M89.3 156.1c3.4-.7 7.1.8 9.2 3.5 3 3.9 2.2 10.1-1.8 13-2.3 1.9-5.4 2-8.2 1.6-2.3-.6-4.3-2.2-5.5-4.2-1.4-2.4-1.5-5.3-.7-7.9 1-3.1 3.8-5.5 7-6z"/><path id="path16" class="st1" d="M108.6 156.3c4.8-1.6 10.6 1.8 11.5 6.7 1.1 4.1-1.1 8.8-5 10.6-1.8 1-4 .8-6 .7-3.1-.6-5.7-2.9-6.8-5.8-1.9-4.9 1.1-11 6.3-12.2z"/><path id="path18" class="st1" d="M237.4 156.3c5.3-1.8 11.7 2.5 11.8 8.1.4 3.4-1.3 6.8-4.2 8.6-2.1 1.5-4.8 1.5-7.3 1.2-3.1-.7-5.7-3.2-6.7-6.3-.5-1.9-.5-3.9.1-5.8 1-2.7 3.3-5.1 6.3-5.8z"/><path id="path20" class="st1" d="M269.9 156.3h6v5.7c1.5-1.8 2.9-3.8 4.4-5.7h7.7c-2.4 2.8-4.6 5.8-7 8.6 2.4 3 4.7 6.2 7.1 9.3h-7.7c-1.4-2.2-2.9-4.5-4.4-6.7v6.7h-6c-.1-6-.1-11.9-.1-17.9z"/><path id="path22" class="st1" d="M28 156.3c3-.1 6 0 9 0 1.3 3.6 2.3 7.3 3.5 10.9.9 2.3 1.3 4.8 2.3 7h-6.5c-.3-1-.5-2.1-.9-3.1h-5.5c-.4 1-.7 2-1 3h-6.3c1.7-5.9 3.8-11.8 5.4-17.8z"/><path id="path24" class="st1" d="M44.3 156.3c3.7 0 7.5-.1 11.2 0 3.8 0 6.9 4.2 5.7 7.9-.4 1.8-1.8 3.2-3.3 4.1 1.3 1.9 2.4 4 3.6 5.9h-6.7c-1-1.7-1.8-3.6-2.8-5.3-.6 0-1.2 0-1.8.1v5.2h-6c.1-6 .1-11.9.1-17.9z"/><path id="path26" class="st1" d="M63 161.5v-5.2h17.9v5.2h-5.6v12.7h-6.7v-12.7H63z"/><path id="path28" class="st1" d="M122.1 174.2v-17.9c2 0 3.9-.1 5.9 0 2.2 2.3 4 4.9 6 7.3 0-2.4 0-4.9-.1-7.3h6v17.9h-5.8l-6-7.5v7.5h-6z"/><path id="path30" class="st1" d="M151.9 156.3h6.1c2 2.4 3.9 4.9 5.9 7.3v-7.3h5.9v17.9h-5.9c-2.1-2.4-3.9-4.9-5.9-7.3-.1 2.4 0 4.9 0 7.3h-6.1v-17.9z"/><path id="path32" class="st1" d="M172.3 156.3h15.6v5.2h-9.6v1.5h8.2v4.5h-8.2v1.8h9.6v4.9h-15.6v-17.9z"/><path id="path34" class="st1" d="M189.9 161.5v-5.2h17.9v5.2h-5.6v12.7h-6.7v-12.7h-5.6z"/><path id="path36" class="st1" d="M209.2 156.3h6c.6 2.5.8 5.1 1.5 7.7 1-2.4 2-4.7 2.9-7.1 1.1 2.4 1.9 4.9 3.1 7.2.2-2.6 1.1-5.1 1.3-7.7h6c-1 6-2.3 11.9-3.3 17.9h-4.9c-.9-1.5-1.4-3.2-2.2-4.8-.7 1.6-1.4 3.2-2 4.8h-4.9c-1.3-6-2.2-12-3.5-18z"/><path id="path38" class="st1" d="M251 174.2v-17.9h8.2c1.6 0 3.2-.1 4.7.3 2.3.9 4.2 3.2 4.1 5.8.2 2.4-1.4 4.6-3.4 5.7 1.1 2.1 2.4 4 3.5 6h-6.7c-.9-1.8-1.8-3.5-2.7-5.3h-1.9v5.3c-1.8.1-3.8.1-5.8.1z"/><path id="path40" class="st2" d="M32.7 161.3c.2 1.9 1 3.6 1.4 5.4H31c.8-1.8.9-3.7 1.7-5.4z"/><path id="path42" class="st2" d="M50.3 161.5c1.5 0 3-.1 4.4.1 1.1.7 1 2.4-.3 2.9-1.4.1-2.8 0-4.1 0v-3z"/><path id="path44" class="st2" d="M256.9 161.5c1.4 0 2.9-.1 4.3.1 1.1.4.9 1.7.5 2.6-1.5.5-3.2.2-4.8.3v-3z"/><path id="path46" class="st2" d="M89.5 162.3c1.7-1.1 4.1 0 4.6 1.9.4 1 0 2.1-.4 3.1-1.2 1.3-3.3 1.8-4.7.6-1.9-1.3-1.7-4.7.5-5.6z"/><path id="path48" class="st2" d="M108.8 162.8c1.6-1.7 4.9-.9 5.4 1.4.5 1.1-.1 2.1-.5 3.1-.9.7-1.8 1.4-3 1.2-2.7 0-4-3.9-1.9-5.7z"/><path id="path50" class="st2" d="M238.6 162.2c2-1.1 4.8.7 4.6 3 .2 2.5-3.1 4.4-5.1 2.8-2.2-1.2-1.9-4.9.5-5.8z"/><path id="path52" class="st1" d="M290.8 169.4c1.3 0 2.6-.1 3.9 0 .1.2.3.6.3.8-.6 0-1.1.1-1.7.1v4c-.3 0-.8 0-1.1.1v-4h-1.4v-1z"/><path id="path54" class="st1" d="M295.3 169.3c.5 0 1 0 1.5.1l.9 3.6c.3-1.2.5-2.5.9-3.7h1.4v4.9h-.9v-3.9c-.2 1.3-.5 2.6-.9 3.9h-1.1c-.2-1.3-.4-2.6-.8-3.8-.1 1.3-.1 2.6 0 3.9h-1v-5z"/></svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, 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"
viewBox="0 0 300 116.6" style="enable-background:new 0 0 300 116.6;" xml:space="preserve">
<style type="text/css">
.st0{fill:#D52F2F;}
</style>
<path class="st0" d="M204.2,11.9v78.3h23.5v-12h-7.8V0l-15.7,0L204.2,11.9L204.2,11.9z M0,78.2h8V40.5H0V30.1l8-1.3V18.3
C8,5.5,10.8,0,27,0c3.5,0,7.7,0.5,11.3,1.2L36.2,14c-2.5-0.4-3.7-0.5-5.2-0.5c-5.7,0-7.2,0.6-7.2,6.2v9.1h11.9v11.7H23.7v37.8h7.9
v12L0,90.2V78.2L0,78.2z M196.2,74.5c-2.5,0.5-4.6,0.5-6.2,0.5c-6.5,0.2-5.9-2-5.9-8.1V40.5h12.3V28.8h-12.3V0h-15.7v70.1
c0,13.8,3.4,20.1,18.2,20.1c3.5,0,8.3-0.9,12-1.7L196.2,74.5L196.2,74.5z M148.7,40.5v-2.1c-4.8-0.9-9.5-0.9-12.1-0.9
c-7.3,0-8.2,3.9-8.2,6c0,3,1,4.6,9,6.3c11.6,2.6,23.3,5.3,23.3,19.7c0,13.6-7,20.7-21.8,20.7c-9.9,0-19.5-2.1-26.8-4V74.5h11.9
l0,2.1c5.1,1,10.5,0.9,13.3,0.9c7.8,0,9.1-4.2,9.1-6.4c0-3.1-2.2-4.6-9.6-6.1c-13.8-2.4-24.8-7.1-24.8-21.1
c0-13.3,8.9-18.5,23.7-18.5c10,0,17.6,1.6,25,3.4v11.7L148.7,40.5L148.7,40.5z M68.9,74.4v-1.9H67v1.8c-8.4-0.5-15.2-7.3-15.7-15.7
h1.9v-1.9h-1.9c0.5-8.4,7.2-15.1,15.6-15.6V43h1.9v-1.9c8.3,0.4,15,6.9,15.7,15.2v0.5h-1.9v1.9h1.9v0.5C83.9,67.5,77.2,74,68.9,74.4
L68.9,74.4z M100.3,74.5l0-49H84.6v4.6c-3.3-2-6.9-3.4-10.7-4H74v-5.4h1.9v-4H60v4H62V26h0.1C47,28.8,35.6,42,35.6,57.8
c0,17.9,14.5,32.4,32.4,32.4c5.9,0,11.6-1.6,16.7-4.6l2.8,4.6h16.6V74.5L100.3,74.5L100.3,74.5z M267.5,28.8H300v11.7h-7.8l-19.9,49
c-5.7,13.8-15.1,26.7-29.4,26.7c-3.5,0-8.2-0.4-11.4-1.2l1.4-14.3c2.1,0.4,4.8,0.6,6.2,0.6c6.6,0,14.1-4.1,16.4-11.2l-20.2-49.7
h-7.8V28.8h32.5v11.7h-7.8l11.4,28.1l11.4-28.1h-7.7L267.5,28.8L267.5,28.8z M76.2,50.8L75,49.6l-6.1,5.3c-0.3-0.1-0.6-0.2-1-0.2
c-1.6,0-3,1.4-3,3.1c0,1.7,1.3,3.1,3,3.1s3-1.4,3-3.1c0-0.3,0-0.6-0.1-0.9L76.2,50.8"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 300 99" xml:space="preserve"><style>.st0{fill:#ea4335}.st2{fill:#4285f4}</style><path class="st0" d="M128.4 52.1c0 14.2-11.1 24.7-24.8 24.7S78.9 66.3 78.9 52.1c0-14.3 11.1-24.7 24.8-24.7s24.7 10.4 24.7 24.7zm-10.9 0c0-8.9-6.4-15-13.9-15s-13.9 6.1-13.9 15c0 8.8 6.4 15 13.9 15s13.9-6.2 13.9-15z"/><path d="M181.8 52.1c0 14.2-11.1 24.7-24.8 24.7-13.6 0-24.8-10.5-24.8-24.7 0-14.3 11.1-24.7 24.8-24.7 13.7 0 24.8 10.4 24.8 24.7zm-10.8 0c0-8.9-6.4-15-13.9-15s-13.9 6.1-13.9 15c0 8.8 6.4 15 13.9 15s13.9-6.2 13.9-15z" fill="#fbbc05"/><path class="st2" d="M233 28.9v44.4c0 18.3-10.7 25.7-23.5 25.7-12 0-19.2-8-21.9-14.6l9.4-3.9c1.7 4 5.8 8.8 12.4 8.8 8.1 0 13.2-5 13.2-14.5v-3.6h-.4c-2.4 3-7.1 5.6-13 5.6-12.3 0-23.6-10.8-23.6-24.6 0-14 11.3-24.8 23.6-24.8 5.9 0 10.6 2.6 13 5.5h.4v-4H233zm-9.6 23.3c0-8.7-5.8-15.1-13.2-15.1-7.5 0-13.7 6.4-13.7 15.1 0 8.6 6.3 14.9 13.7 14.9 7.4 0 13.2-6.3 13.2-14.9z"/><path d="M249.9 2.8v72.5h-10.6V2.8h10.6z" fill="#34a853"/><path class="st0" d="M291.1 60.2l8.4 5.6c-2.7 4-9.3 11-20.6 11-14 0-24.5-10.9-24.5-24.7 0-14.7 10.6-24.7 23.3-24.7 12.8 0 19.1 10.2 21.1 15.7l1.1 2.8-33 13.7c2.5 5 6.5 7.5 12 7.5 5.6 0 9.4-2.7 12.2-6.9zm-25.9-8.9l22.1-9.2c-1.2-3.1-4.9-5.2-9.2-5.2-5.5 0-13.1 4.9-12.9 14.4z"/><path class="st2" d="M38.9 45.7V35.2h35.3c.3 1.8.5 4 .5 6.3 0 7.9-2.1 17.6-9.1 24.5-6.7 7-15.3 10.8-26.7 10.8C17.8 76.8 0 59.6 0 38.4S17.8 0 38.9 0c11.7 0 20 4.6 26.3 10.6L57.7 18c-4.5-4.2-10.6-7.5-18.9-7.5-15.4 0-27.5 12.5-27.5 27.9s12.1 27.9 27.5 27.9c10 0 15.7-4 19.3-7.7 3-3 4.9-7.2 5.7-13l-24.9.1z"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<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 52.5 (67469) - http://www.bohemiancoding.com/sketch -->
<title>Moderation/Block Count</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M13.6964,7.83442 L13.6964,11.44342 C13.6964,11.69182 13.4948,11.89342 13.2464,11.89342 L8.4773,11.89342 C8.3576,11.89342 8.2433,11.94022 8.1596,12.02482 L7.5314,12.65302 C7.4477,12.73672 7.3334,12.78442 7.2137,12.78442 L4.8863,12.78442 C4.7675,12.78442 4.6532,12.73672 4.5677,12.65302 L3.9413,12.02482 C3.8567,11.94022 3.7415,11.89342 3.6227,11.89342 L2.45,11.89342 C2.2016,11.89342 2,11.69182 2,11.44342 L2,7.83442 C2,7.58602 2.2016,7.38442 2.45,7.38442 L3.6137,7.38442 C3.7334,7.38442 3.8477,7.43212 3.9314,7.51582 L4.5677,8.15302 C4.6532,8.23672 4.7675,8.28442 4.8863,8.28442 L7.2137,8.28442 C7.3334,8.28442 7.4477,8.23672 7.5314,8.15302 L8.1677,7.51582 C8.2532,7.43212 8.3675,7.38442 8.4863,7.38442 L13.2464,7.38442 C13.4948,7.38442 13.6964,7.58602 13.6964,7.83442 M16.0373,14.05 L16.0373,17.659 C16.0373,17.9074 15.8357,18.109 15.5873,18.109 L8.4773,18.109 C8.3576,18.109 8.2433,18.1558 8.1596,18.2404 L7.5314,18.8686 C7.4477,18.9523 7.3334,19 7.2137,19 L4.8863,19 C4.7675,19 4.6532,18.9523 4.5677,18.8686 L3.9413,18.2404 C3.8567,18.1558 3.7415,18.109 3.6227,18.109 L2.45,18.109 C2.2016,18.109 2,17.9074 2,17.659 L2,14.05 C2,13.8016 2.2016,13.6 2.45,13.6 L3.6137,13.6 C3.7334,13.6 3.8477,13.6477 3.9314,13.7314 L4.5677,14.3686 C4.6532,14.4523 4.7675,14.5 4.8863,14.5 L7.2137,14.5 C7.3334,14.5 7.4477,14.4523 7.5314,14.3686 L8.1677,13.7314 C8.2532,13.6477 8.3675,13.6 8.4863,13.6 L15.5873,13.6 C15.8357,13.6 16.0373,13.8016 16.0373,14.05 Z M18.00036,5.059 C18.00036,5.3074 17.79786,5.509 17.55036,5.509 L8.47766,5.509 C8.35796,5.509 8.24366,5.5558 8.15906,5.6404 L7.53176,6.2686 C7.44716,6.3523 7.33286,6.4 7.21316,6.4 L4.88666,6.4 C4.76696,6.4 4.65266,6.3523 4.56806,6.2686 L3.94076,5.6404 C3.85616,5.5558 3.74186,5.509 3.62306,5.509 L2.45036,5.509 C2.20196,5.509 2.00036,5.3074 2.00036,5.059 L2.00036,1.45 C2.00036,1.2016 2.20196,1 2.45036,1 L3.61316,1 C3.73286,1 3.84716,1.0477 3.93176,1.1314 L4.56806,1.7686 C4.65266,1.8523 4.76696,1.9 4.88666,1.9 L7.21316,1.9 C7.33286,1.9 7.44716,1.8523 7.53176,1.7686 L8.16806,1.1314 C8.25266,1.0477 8.36696,1 8.48666,1 L17.55036,1 C17.79786,1 18.00036,1.2016 18.00036,1.45 L18.00036,5.059 Z" id="path-1"></path>
</defs>
<g id="Moderation/Block-Count" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="Script-Count" fill="#575E75" xlink:href="#path-1"></use>
<g id="Color/Gray" mask="url(#mask-2)" fill="#575E75">
<rect id="Color" x="0" y="0" width="20" height="20"></rect>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="40px" height="40px" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>ev3-block-icon</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="ev3-block-icon" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="ev3" transform="translate(5.500000, 3.500000)" fill-rule="nonzero">
<rect id="Rectangle-path" stroke="#7C87A5" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" x="0.5" y="3.59" width="28" height="25.81" rx="1"></rect>
<rect id="Rectangle-path" stroke="#7C87A5" fill="#E6E7E8" stroke-linecap="round" stroke-linejoin="round" x="2.5" y="0.5" width="24" height="32" rx="1"></rect>
<rect id="Rectangle-path" stroke="#7C87A5" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" x="2.5" y="14.5" width="24" height="13"></rect>
<path d="M14.5,10.5 L14.5,14.5" id="Shape" stroke="#7C87A5" fill="#E6E7E8" stroke-linecap="round" stroke-linejoin="round"></path>
<rect id="Rectangle-path" fill="#414757" x="4.5" y="2.5" width="20" height="10" rx="1"></rect>
<rect id="Rectangle-path" fill="#7C87A5" opacity="0.5" x="13.5" y="20.13" width="2" height="2" rx="0.5"></rect>
<path d="M9.06,20.13 L10.56,20.13 C10.8361424,20.13 11.06,20.3538576 11.06,20.63 L11.06,21.63 C11.06,21.9061424 10.8361424,22.13 10.56,22.13 L9.06,22.13 C8.50771525,22.13 8.06,21.6822847 8.06,21.13 C8.06,20.5777153 8.50771525,20.13 9.06,20.13 Z" id="Shape" fill="#7C87A5" opacity="0.5"></path>
<path d="M18.91,20.13 L20.42,20.13 C20.6961424,20.13 20.92,20.3538576 20.92,20.63 L20.92,21.63 C20.92,21.9061424 20.6961424,22.13 20.42,22.13 L18.92,22.13 C18.3677153,22.13 17.92,21.6822847 17.92,21.13 C17.9199726,20.581597 18.3616245,20.135484 18.91,20.13 Z" id="Shape" fill="#7C87A5" opacity="0.5" transform="translate(19.420000, 21.130000) rotate(-180.000000) translate(-19.420000, -21.130000) "></path>
<path d="M8.23,17.5 L5,17.5 C4.72385763,17.5 4.5,17.2761424 4.5,17 L4.5,14.5 L10.5,14.5 L8.65,17.28 C8.55466961,17.4179082 8.39765006,17.5001566 8.23,17.5 Z" id="Shape" fill="#7C87A5" opacity="0.5"></path>
<path d="M18.15,18.85 L17.65,19.35 C17.5523416,19.4440756 17.4980339,19.5744142 17.5,19.71 L17.5,20 C17.5,20.2761424 17.2761424,20.5 17,20.5 L16.5,20.5 C16.2238576,20.5 16,20.2761424 16,20 C16,19.7238576 15.7761424,19.5 15.5,19.5 L13.5,19.5 C13.2238576,19.5 13,19.7238576 13,20 C13,20.2761424 12.7761424,20.5 12.5,20.5 L12,20.5 C11.7238576,20.5 11.5,20.2761424 11.5,20 L11.5,19.71 C11.5019661,19.5744142 11.4476584,19.4440756 11.35,19.35 L10.85,18.85 C10.6582167,18.6521863 10.6582167,18.3378137 10.85,18.14 L12.36,16.65 C12.4502803,16.5528617 12.5773961,16.4983835 12.71,16.5 L16.29,16.5 C16.4226039,16.4983835 16.5497197,16.5528617 16.64,16.65 L18.15,18.14 C18.3417833,18.3378137 18.3417833,18.6521863 18.15,18.85 Z" id="Shape" fill="#7C87A5" opacity="0.5"></path>
<path d="M10.85,23.45 L11.35,22.95 C11.4476584,22.8559244 11.5019661,22.7255858 11.5,22.59 L11.5,22.3 C11.5,22.0238576 11.7238576,21.8 12,21.8 L12.5,21.8 C12.7761424,21.8 13,22.0238576 13,22.3 C13,22.5761424 13.2238576,22.8 13.5,22.8 L15.5,22.8 C15.7761424,22.8 16,22.5761424 16,22.3 C16,22.0238576 16.2238576,21.8 16.5,21.8 L17,21.8 C17.2761424,21.8 17.5,22.0238576 17.5,22.3 L17.5,22.59 C17.4980339,22.7255858 17.5523416,22.8559244 17.65,22.95 L18.15,23.45 C18.3405714,23.6444218 18.3405714,23.9555782 18.15,24.15 L16.64,25.65 C16.5497197,25.7471383 16.4226039,25.8016165 16.29,25.8 L12.71,25.8 C12.5773961,25.8016165 12.4502803,25.7471383 12.36,25.65 L10.85,24.15 C10.6594286,23.9555782 10.6594286,23.6444218 10.85,23.45 Z" id="Shape" fill="#7C87A5" opacity="0.5"></path>
<path d="M21.5,27.5 L26.5,27.5 L26.5,31.5 C26.5,32.0522847 26.0522847,32.5 25.5,32.5 L21.5,32.5 L21.5,27.5 Z" id="Shape" stroke="#CC4C23" fill="#F15A29" stroke-linecap="round" stroke-linejoin="round"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><style>.st2{fill:red}.st3{fill:#e0e0e0}.st4{fill:none;stroke:#666;stroke-width:.5;stroke-miterlimit:10}</style><path d="M35 28H5a1 1 0 0 1-1-1V12c0-.6.4-1 1-1h30c.5 0 1 .4 1 1v15c0 .5-.5 1-1 1z" fill="#fff" id="Layer_6"/><g id="Layer_4"><path class="st2" d="M4 25h32v2.7H4zM13 24h-2.2a1 1 0 0 1-1-1v-9.7c0-.6.4-1 1-1H13c.6 0 1 .4 1 1V23c0 .6-.5 1-1 1z"/><path class="st2" d="M6.1 19.3v-2.2c0-.5.4-1 1-1h9.7c.5 0 1 .5 1 1v2.2c0 .5-.5 1-1 1H7.1a1 1 0 0 1-1-1z"/><circle class="st2" cx="22.8" cy="18.2" r="3.4"/><circle class="st2" cx="30.6" cy="18.2" r="3.4"/><path class="st2" d="M4.2 27h31.9v.7H4.2z"/></g><g id="Layer_5"><circle class="st3" cx="22.8" cy="18.2" r="2.3"/><circle class="st3" cx="30.6" cy="18.2" r="2.3"/><path class="st3" d="M12.5 22.9h-1.2c-.3 0-.5-.2-.5-.5V14c0-.3.2-.5.5-.5h1.2c.3 0 .5.2.5.5v8.4c0 .3-.2.5-.5.5z"/><path class="st3" d="M7.2 18.7v-1.2c0-.3.2-.5.5-.5h8.4c.3 0 .5.2.5.5v1.2c0 .3-.2.5-.5.5H7.7c-.3 0-.5-.2-.5-.5zM4 26h32v2H4z"/></g><g id="Layer_3"><path class="st4" d="M35.2 27.9H4.8a1 1 0 0 1-1-1V12.1c0-.6.5-1 1-1h30.5c.5 0 1 .4 1 1V27a1 1 0 0 1-1.1.9z"/><path class="st4" d="M35.2 27.9H4.8a1 1 0 0 1-1-1V12.1c0-.6.5-1 1-1h30.5c.5 0 1 .4 1 1V27a1 1 0 0 1-1.1.9z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="40px" height="40px" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
<title>Extensions/Software/Text-to-Speech-Block</title>
<desc>Created with Sketch.</desc>
<g id="Extensions/Software/Text-to-Speech-Block" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-opacity="0.15">
<g id="text2speech" transform="translate(4.000000, 4.000000)" fill-rule="nonzero" stroke="#000000">
<path d="M11.5,17.6693435 C11.5,16.6539269 10.0060145,16.0844274 9.11256024,16.8883 L6.41256024,19.050714 C5.39346755,19.8668994 4.07497351,20.3317575 2.7,20.3317575 L2.3,20.3317575 C1.26519233,20.3317575 0.5,21.0212003 0.5,21.904071 L0.5,26.1387986 C0.5,27.0216693 1.26519233,27.711112 2.3,27.711112 L2.7,27.711112 C4.15755682,27.711112 5.45372322,28.1335271 6.51972098,28.998 L9.11840293,31.1593216 C10.0261855,31.9090793 11.5,31.3472689 11.5,30.2834255 L11.5,17.6693435 Z" id="speaker" fill="#4D4D4D"></path>
<path d="M21.6436066,16.5 C19.9770099,18.4370234 17.1050275,19.9285714 15.6666667,19.9285714 C15.5126397,19.9285714 15.3166292,19.895903 15.1097265,19.7924517 C14.7376039,19.6063904 14.5,19.2499846 14.5,18.7619048 C14.5,18.6568041 14.5170555,18.5545076 14.5494467,18.4540844 C14.6257545,18.2175063 15.1735721,17.467531 15.2772071,17.2809881 C15.5463526,16.7965261 15.739025,16.2063561 15.8432891,15.4160034 C13.1897005,13.9268369 11.5,11.1139668 11.5,8 C11.5,3.30557963 15.3055796,-0.5 20,-0.5 L24,-0.5 C28.6944204,-0.5 32.5,3.30557963 32.5,8 C32.5,12.6944204 28.6944204,16.5 24,16.5 L21.6436066,16.5 Z" id="speech" fill="#FFFFFF"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<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 52.5 (67469) - http://www.bohemiancoding.com/sketch -->
<title>Moderation/Last Revised</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M4.03537756,1.10307692 L4.03537756,0.768923077 C4.03537756,0.344088654 4.37977391,-0.000307692308 4.80460833,-0.000307692308 C5.22944276,-0.000307692308 5.5738391,0.344088654 5.5738391,0.768923077 L5.5738391,1.10307692 L9.67600256,1.10307692 L9.67600256,0.768923077 C9.67600256,0.344088654 10.0203989,-0.000307692308 10.4452333,-0.000307692308 C10.8700678,-0.000307692308 11.2144641,0.344088654 11.2144641,0.768923077 L11.2144641,1.10307692 L12.375,1.10307692 C13.6693961,1.10307692 14.7275641,2.13054632 14.7275641,3.41076923 L14.7275641,6.49076923 C14.7275641,6.91560365 14.3831678,7.26 13.9583333,7.26 L2.06089744,7.26 L2.06089744,12.6461538 C2.06089744,13.0664069 2.42030185,13.4153846 2.875,13.4153846 L4.40925,13.4153846 C4.83408442,13.4153846 5.17848077,13.759781 5.17848077,14.1846154 C5.17848077,14.6094498 4.83408442,14.9538462 4.40925,14.9538462 L2.875,14.9538462 C1.58060393,14.9538462 0.522435897,13.9263768 0.522435897,12.6461538 L0.522435897,6.49076923 L0.522435897,3.41076923 C0.522435897,2.13054632 1.58060393,1.10307692 2.875,1.10307692 L4.03537756,1.10307692 Z M4.03537756,2.64153846 L2.875,2.64153846 C2.42030185,2.64153846 2.06089744,2.99051616 2.06089744,3.41076923 L2.06089744,5.72153846 L13.1891026,5.72153846 L13.1891026,3.41076923 C13.1891026,2.99051616 12.8296981,2.64153846 12.375,2.64153846 L11.2144641,2.64153846 L11.2144641,2.97507692 C11.2144641,3.39991135 10.8700678,3.74430769 10.4452333,3.74430769 C10.0203989,3.74430769 9.67600256,3.39991135 9.67600256,2.97507692 L9.67600256,2.64153846 L5.5738391,2.64153846 L5.5738391,2.97507692 C5.5738391,3.39991135 5.22944276,3.74430769 4.80460833,3.74430769 C4.37977391,3.74430769 4.03537756,3.39991135 4.03537756,2.97507692 L4.03537756,2.64153846 Z M11.76035,19.4624615 L6.93118333,14.7547692 C6.64618333,14.4947692 6.64618333,14.0470769 6.93118333,13.7716923 L11.76035,9.06246154 C12.2195167,8.63169231 12.99535,8.93938462 12.99535,9.57015385 L12.99535,12.0332308 C13.8661833,11.9393846 14.7370167,11.5870769 15.4495167,10.924 C16.4786833,10.0163077 17.08035,8.51015385 16.89035,6.93938462 L16.8745167,6.91015385 C16.8111833,6.38553846 17.1911833,5.90861538 17.7295167,5.84707692 C18.1886833,5.80092308 18.60035,6.04707692 18.7745167,6.44707692 C19.5820167,8.43169231 19.40785,10.9086154 18.1095167,12.9086154 C17.03285,14.6178462 15.1645167,15.9393846 12.99535,16.4024615 L12.99535,18.9716923 C12.99535,19.5855385 12.2195167,19.9086154 11.76035,19.4624615 Z" id="path-1"></path>
</defs>
<g id="Moderation/Last-Revised" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="Last-Revised" fill="#575E75" fill-rule="nonzero" xlink:href="#path-1"></use>
<g id="Color/Gray" mask="url(#mask-2)" fill="#575E75">
<rect id="Color" x="0" y="0" width="20" height="20"></rect>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<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 52.5 (67469) - http://www.bohemiancoding.com/sketch -->
<title>Moderation/Sprite Count</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M19,12.278912 C19,17.2463833 14.9561385,19.3221441 9.98866737,19.3221441 C5.02308499,19.3221441 1,17.2463833 1,12.278912 C1,11.05121 1.20587618,10.0123852 1.60440714,9.14354991 L1.45330535,1.96621498 C1.43441763,1.17293059 2.32214061,0.719625225 2.96432319,1.19181831 L6.79853095,4.08164001 C7.68625393,3.60944692 8.78174187,3.40168196 9.98866737,3.40168196 C11.1993704,3.40168196 12.3137461,3.60944692 13.201469,4.08164001 L17.0356768,1.19181831 C17.6589717,0.719625225 18.5466946,1.17293059 18.5466946,1.96621498 L18.3955929,9.14354991 C18.792235,10.0123852 19,11.05121 19,12.278912 Z M13.7854774,15.375932 C14.0517943,15.094505 14.0121301,14.6393108 13.7118153,14.3748827 C13.4284995,14.1293423 12.9733054,14.1671177 12.710766,14.4712101 C12.5011123,14.7148617 12.1989087,14.8470758 11.8778174,14.8470758 C11.2734103,14.8470758 10.7634418,14.355995 10.7634418,13.7327001 L10.7634418,12.6938753 C11.8589297,12.3916717 12.6899895,11.468062 12.6899895,10.8617661 C12.6899895,10.1062572 11.500063,10.1062572 10.064596,10.1062572 C8.61213012,10.1062572 7.44109129,10.1062572 7.44109129,10.8617661 C7.44109129,11.468062 8.23437566,12.3916717 9.34686254,12.6749876 L9.34686254,13.7327001 C9.34686254,14.355995 8.85767051,14.8470758 8.25137461,14.8470758 C7.91328437,14.8470758 7.60919203,14.7148617 7.40142707,14.4712101 C7.15588667,14.1671177 6.70258132,14.1293423 6.40037775,14.3748827 C6.10006296,14.6393108 6.07928646,15.094505 6.32671563,15.375932 C6.79890871,15.9425637 7.49586569,16.263655 8.25137461,16.263655 C8.95022036,16.263655 9.59240294,15.9633402 10.064596,15.4911471 C10.5197901,15.9633402 11.1600839,16.263655 11.8778174,16.263655 C12.6163274,16.263655 13.3132844,15.9425637 13.7854774,15.375932 Z" id="path-1"></path>
</defs>
<g id="Moderation/Sprite-Count" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="Sprite-Count" fill="#575E75" xlink:href="#path-1"></use>
<g id="Color/Gray" mask="url(#mask-2)" fill="#575E75">
<rect id="Color" x="0" y="0" width="20" height="20"></rect>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -262,7 +262,7 @@ describe('test Scratch Family links in footer', function () {
.click();
cy
.url()
.should('match', /^http:\/\/www\.scratchjr\.org\/?$/);
.should('match', /^https:\/\/www\.scratchjr\.org\/?$/);
});
it.skip('click Scratch Day', function (){

View file

@ -22,7 +22,7 @@ describe('test links in nav bar while signed out', function (){
.click();
cy
.url()
.should('eq', baseUrl + '/projects/editor/?tip_bar=home');
.should('eq', baseUrl + '/projects/editor/?tutorial=getStarted');
});
it('click Explore', function (){

View file

@ -1,5 +1,6 @@
const webdriver = require('selenium-webdriver');
const bindAll = require('lodash.bindall');
require('chromedriver');
const headless = process.env.SMOKE_HEADLESS || false;
const remote = process.env.SMOKE_REMOTE || false;

View file

@ -82,7 +82,7 @@ test('clicking See Inside should take you to the editor', t => {
.then(() => clickXpath('//a[@data-control="edit"]'))
.then(() => driver.getCurrentUrl())
.then(function (u) {
var expectedUrl = '/#editor';
var expectedUrl = '/editor';
t.equal(u.substr(-expectedUrl.length), expectedUrl, 'after clicking, the URL should end in #editor');
})
.then(() => t.end());

View file

@ -269,7 +269,7 @@ tap.test('clickScratchEdLink', options, t => {
// SCRATCH JR (SCRATCHJR)
tap.test('clickScratchJrLink', options, t => {
const linkText = 'ScratchJr';
const expectedUrl = 'http://www.scratchjr.org/';
const expectedUrl = 'https://www.scratchjr.org/';
clickFooterLinks(linkText).then(url => {
t.equal(url, expectedUrl);
t.end();

View file

@ -33,7 +33,7 @@ tap.beforeEach(function () {
// the create link changes depending on whether the user is signed in or not (tips window opens)
tap.test('checkCreateLinkWhenSignedOut', function (t) {
var xPathLink = '//li[contains(@class, "link") and contains(@class, "create")]/a';
var expectedHref = '/projects/editor/?tip_bar=home';
var expectedHref = '/projects/editor/?tutorial=getStarted';
driver.findElement(webdriver.By.xpath(xPathLink))
.then(function (element) {
return element.getAttribute('href');

View file

@ -0,0 +1,130 @@
const tap = require('tap');
const Preview = require('../../../src/redux/preview');
const initialState = Preview.getInitialState();
const reducer = Preview.previewReducer;
let state;
tap.tearDown(() => process.nextTick(process.exit));
tap.test('Reducer', t => {
t.type(reducer, 'function');
t.type(initialState, 'object');
// Reducers should return their default state when called without state
let undefinedState;
t.deepEqual(initialState, reducer(undefinedState, {type: 'fake action'}));
t.end();
});
tap.test('resetProject', t => {
state = reducer({some: 'garbage'}, Preview.resetProject());
t.deepEqual(state, initialState);
t.end();
});
tap.test('setProjectInfo', t => {
// Initial values
t.equal(initialState.projectNotAvailable, false);
t.deepEqual(initialState.projectInfo, {});
// setProjectInfo action with an `info` value sets the projectInfo
state = reducer(initialState, Preview.setProjectInfo('a value'));
t.equal(state.projectInfo, 'a value');
t.equal(state.projectNotAvailable, false);
// setProjectInfo action with null info sets projectNotAvailable to true
// and resets the project info back to default state
state = reducer(initialState, Preview.setProjectInfo(null));
t.deepEqual(state.projectInfo, initialState.projectInfo);
t.equal(state.projectNotAvailable, true);
t.end();
});
tap.test('updateProjectInfo', t => {
const info = {a: 'value a', b: 'value b'};
state = reducer({projectInfo: info}, Preview.updateProjectInfo({
b: 'new value b',
c: 'new value c'
}));
t.deepEqual(state.projectInfo, {
a: 'value a',
b: 'new value b',
c: 'new value c'
});
t.end();
});
tap.test('setComments', t => {
// Initial value
t.deepEqual(initialState.comments, []);
state = reducer(initialState, Preview.setComments([1, 2]));
state = reducer(state, Preview.setComments([3, 4]));
t.deepEqual(state.comments, [1, 2, 3, 4]);
t.end();
});
const commentState = {
comments: [
{id: 'id1', visibility: 'visible'},
{id: 'id2', visibility: 'visible'},
{id: 'id3', visibility: 'visible'}
],
replies: {
id1: [
{id: 'id4', visibility: 'visible'},
{id: 'id5', visibility: 'visible'}
]
}
};
tap.test('setCommentDeleted, top level comment', t => {
state = reducer(commentState, Preview.setCommentDeleted('id2'));
t.equal(state.comments[1].visibility, 'deleted');
t.end();
});
tap.test('setCommentDeleted, reply comment', t => {
state = reducer(commentState, Preview.setCommentDeleted('id4', 'id1'));
t.equal(state.replies.id1[0].visibility, 'deleted');
t.end();
});
tap.test('setRepliesDeleted/Restored', t => {
state = reducer(commentState, Preview.setRepliesDeleted('id1'));
t.equal(state.replies.id1[0].visibility, 'deleted');
t.equal(state.replies.id1[1].visibility, 'deleted');
state = reducer(state, Preview.setRepliesRestored('id1'));
t.equal(state.replies.id1[0].visibility, 'visible');
t.equal(state.replies.id1[1].visibility, 'visible');
t.end();
});
tap.test('setCommentReported, top level comment', t => {
state = reducer(commentState, Preview.setCommentReported('id2'));
t.equal(state.comments[1].visibility, 'reported');
t.end();
});
tap.test('setCommentReported, reply comment', t => {
state = reducer(commentState, Preview.setCommentReported('id4', 'id1'));
t.equal(state.replies.id1[0].visibility, 'reported');
t.end();
});
tap.test('addNewComment, top level comment', t => {
state = reducer(commentState, Preview.addNewComment({id: 'new comment'}));
// Adds comment to beginning of list
t.equal(state.comments[0].id, 'new comment');
t.end();
});
tap.test('addNewComment, reply comment', t => {
state = reducer(commentState, Preview.addNewComment({id: 'new comment'}, 'id1'));
// Adds replies to the end of the replies list
t.equal(state.replies.id1[2].id, 'new comment');
t.end();
});

View file

@ -33,7 +33,7 @@ VersionPlugin.prototype.apply = function (compiler) {
callback();
};
const options = this.options;
compiler.plugin('emit', function (compilation, callback) {
const sha = process.env.WWW_VERSION;
if (!sha) { // eslint-disable-line no-negated-condition
@ -169,6 +169,7 @@ module.exports = {
'process.env.BACKPACK_HOST': '"' + (process.env.BACKPACK_HOST || 'https://backpack.scratch.mit.edu') + '"',
'process.env.CLOUDDATA_HOST': '"' + (process.env.CLOUDDATA_HOST || 'clouddata.scratch.mit.edu') + '"',
'process.env.PROJECT_HOST': '"' + (process.env.PROJECT_HOST || 'https://projects.scratch.mit.edu') + '"',
'process.env.STATIC_HOST': '"' + (process.env.STATIC_HOST || 'https://cdn2.scratch.mit.edu') + '"',
'process.env.SCRATCH_ENV': '"' + (process.env.SCRATCH_ENV || 'development') + '"'
}),
new webpack.optimize.CommonsChunkPlugin({