Unverified Commit 5e64a4aa authored by Ray Schamp's avatar Ray Schamp Committed by GitHub

Merge pull request #1952 from LLK/release/june-2018

Release for June 2018
parents 78b49c5f ae5d825c
......@@ -145,3 +145,8 @@ file_filter = localizations/research/<lang>.json
source_file = src/views/research/l10n.json
source_lang = en
type = KEYVALUEJSON
[scratch-website.preview-l10njson]
source_file = src/views/preview/l10n.json
source_lang = en
type = KEYVALUEJSON
FROM node:8
RUN mkdir -p /var/app/current
WORKDIR /var/app/current
COPY . ./
RUN rm -rf ./node_modules
RUN npm install
EXPOSE 8333
......@@ -9,7 +9,7 @@
### Where am I?
Physically? No idea.
Digitally? You’re at Scratch’s open source web client!
Digitally? You’re at Scratch’s open source web client!
We’re working to update the [Scratch website](https://scratch.mit.edu) to use a new codebase, contained in this repository.
......@@ -23,7 +23,7 @@ We’re currently building Scratch using [React](https://facebook.github.io/reac
### Before Getting Started
* Make sure you have node (v4.2 or higher) and npm [installed](https://docs.npmjs.com/getting-started/installing-node)
We use npm (Node Package Manager) to maintain and update packages required to build the site.
We use npm (Node Package Manager) to maintain and update packages required to build the site.
### Update Packages
It's important to make sure that all of the dependencies are up to date because the scratch-www code only works with specific versions of the dependencies. You can update the packages by running this command:
......@@ -98,6 +98,9 @@ npm test
```
### To Deploy
Deploying to staging or production will upload code to S3 and configure Fastly.
```bash
npm install
virtualenv ENV
......@@ -132,3 +135,35 @@ Additionally, if you set `FALLBACK=https://scratch.mit.edu`, be aware that click
#### Windows
Some users have experienced difficulties when trying to get our web client to work on Windows. One solution could be to use [Cygwin](https://www.cygwin.com/). If that doesn't work, you might want to use [Wubi](https://wiki.ubuntu.com/WubiGuide) (Windows XP, Vista, 7) or [Wubiuefi](https://github.com/hakuna-m/wubiuefi) (Windows 8 or higher). Wubi(uefi) is a Windows Installer for Ubuntu that allows you to have Ubuntu and Windows on one disk, without the need of an extra partition.
#### Docker
_This section is only relevant to the Scratch Team since it requires access to private repositories, so is not usable by 3rd party contributors._
A set of [Docker](https://www.docker.com/what-docker) related files are provided to create isolated [container](https://www.docker.com/what-container) environments suitable for end-to-end local development:
* Dockerfile
* docker-compose.yml
* docker_entrypoint.sh
##### Docker Quick Start (CLI)
Make sure you already have the Scratch REST API running locally in its docker environment.
```
$ docker-compose up
```
After this has launched you will be able to access a running copy of `scratch-www` on port 8333 via `http://localhost:8333`
##### Docker Configuration
`Dockerfile` defines how a `scratch-www` docker image is created.
`docker-compose.yml` takes care of launching `scratch-www` into a development environment that is composed of other components, such as the Scratch REST API server and the legacy Scratch code. If you have not already setup the Scratch REST API in your local environment, you will need to modify `docker-compose.yml` by removing `external: true` from:
```yaml
networks:
scratchapi_scratch_network:
external: true
```
The Scratch trademarks, including the Scratch name, logo, the Scratch Cat, Gobo, Pico, Nano, Tera and Giga graphics (the "Marks"), are property of the Massachusetts Institute of Technology (MIT), and the use of the Marks is governed by this policy.
You may use the Marks to refer to Scratch in Substantially Unmodified form.
"Substantially Unmodified" means the source code provided by MIT, possibly with minor modifications including but not limited to: bug fixes (including security), changing the locations of files for better integration with the host operating system, adding documentation, and changes to the dynamic linking of libraries.
A version is not "Substantially Unmodified" if it incorporates features not present in a release of Scratch by MIT. If you do make a substantial modification, to avoid confusion with versions of Scratch produced by MIT you must remove all Marks from your version of the software and refrain from using any of the Marks to refer to your version.
The Scratch trademarks, including the Scratch name, logo, the Scratch Cat, Gobo, Pico, Nano, Tera and Giga graphics (the "Marks"), are property of the Massachusetts Institute of Technology (MIT). Marks may not be used to endorse or promote products derived from this software without specific prior written permission.
version: '3.4'
volumes:
npm_data:
runtime_data:
intl_data:
networks:
scratchapi_scratch_network:
external: true
services:
app:
container_name: scratch-www-app
hostname: scratch-www-app
environment:
- API_HOST=http://localhost:8491
- FALLBACK=http://scratchr2-app:8080
build:
context: ./
dockerfile: Dockerfile
image: scratch-www:latest
command: ./docker_entrypoint.sh npm start
volumes:
- type: bind
source: ./
target: /var/app/current
volume:
nocopy: true
- type: bind
source: ../scratch-gui
target: /var/app/current/scratch-gui
volume:
nocopy: true
- npm_data:/var/app/current/node_modules
- runtime_data:/runtime
- intl_data:/var/app/current/intl
ports:
- "8333:8333"
networks:
- scratchapi_scratch_network
#!/usr/bin/env bash
echo "App Entrypoint"
if [ ! -f /runtime/.translations ]; then
echo "Generating intl/translations"
make translations
touch /runtime/.translations
fi
exec "$@"
......@@ -6,8 +6,9 @@
"start": "make start",
"stop": "make stop",
"test": "make test",
"smoke": "make smoke",
"smoke": "tap ./test/integration/smoke-testing/*.js --timeout=3600",
"smoke-verbose": "make smoke-verbose",
"smoke-sauce": "tap ./test/integration/smoke-testing/*.js --timeout=60000",
"watch": "make watch",
"build": "make build",
"dev": "make watch && make start &"
......@@ -30,7 +31,8 @@
"lodash.defaults": "4.0.1",
"newrelic": "1.25.4",
"raven": "0.10.0",
"scratch-gui": "0.1.0-prerelease.20180529181946"
"scratch-parser": "^4.2.0",
"scratch-storage": "^0.5.1"
},
"devDependencies": {
"ajv": "6.4.0",
......@@ -97,6 +99,7 @@
"redux-thunk": "2.0.1",
"sass-lint": "1.5.1",
"sass-loader": "6.0.6",
"scratch-gui": "latest",
"scratchr2_translations": "git://github.com/LLK/scratchr2_translations.git#master",
"slick-carousel": "1.6.0",
"source-map-support": "0.3.2",
......
......@@ -38,6 +38,8 @@ class InplaceInput extends React.Component {
<FRCTextarea
className="inplace-textarea"
componentRef={this.setRef}
elementWrapperClassName="grow"
label={null}
rowClassName={classNames('textarea-row no-label', className)}
onBlur={this.handleBlur}
{...props}
......
......@@ -5,7 +5,6 @@ const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const Modal = require('../base/modal.jsx');
const log = require('../../../lib/log.js');
const Form = require('../../forms/form.jsx');
const Button = require('../../forms/button.jsx');
......@@ -16,48 +15,74 @@ const TextArea = require('../../forms/textarea.jsx');
require('../../forms/button.scss');
require('./modal.scss');
const REPORT_OPTIONS = [
{
value: '',
label: {id: 'report.reasonPlaceHolder'},
prompt: {id: 'report.promptPlaceholder'}
},
{
value: '0',
label: {id: 'report.reasonCopy'},
prompt: {id: 'report.promptCopy'}
},
{
value: '1',
label: {id: 'report.reasonUncredited'},
prompt: {id: 'report.promptUncredited'}
},
{
value: '2',
label: {id: 'report.reasonScary'},
prompt: {id: 'report.promptScary'}
},
{
value: '3',
label: {id: 'report.reasonLanguage'},
prompt: {id: 'report.promptLanguage'}
},
{
value: '4',
label: {id: 'report.reasonMusic'},
prompt: {id: 'report.promptMusic'}
},
{
value: '8',
label: {id: 'report.reasonImage'},
prompt: {id: 'report.promptImage'}
},
{
value: '5',
label: {id: 'report.reasonPersonal'},
prompt: {id: 'report.promptPersonal'}
},
{
value: '6',
label: {id: 'general.other'},
prompt: {id: 'report.promptGuidelines'}
}
];
class ReportModal extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleReasonSelect',
'handleSubmit'
'handleReportCategorySelect'
]);
this.state = {
prompt: props.intl.formatMessage({id: 'report.promptPlaceholder'}),
reason: '',
waiting: false
};
this.state = {reportCategory: this.props.report.category};
}
handleReasonSelect (name, value) {
const prompts = [
this.props.intl.formatMessage({id: 'report.promptCopy'}),
this.props.intl.formatMessage({id: 'report.promptUncredited'}),
this.props.intl.formatMessage({id: 'report.promptScary'}),
this.props.intl.formatMessage({id: 'report.promptLanguage'}),
this.props.intl.formatMessage({id: 'report.promptMusic'}),
this.props.intl.formatMessage({id: 'report.promptPersonal'}),
this.props.intl.formatMessage({id: 'report.promptGuidelines'}),
'not used',
this.props.intl.formatMessage({id: 'report.promptImage'})
];
this.setState({prompt: prompts[value], reason: value});
handleReportCategorySelect (name, value) {
this.setState({reportCategory: value});
}
handleSubmit (formData) {
this.setState({waiting: true});
this.props.onReport(formData, err => {
if (err) log.error(err);
this.setState({
prompt: this.props.intl.formatMessage({id: 'report.promptPlaceholder'}),
reason: '',
waiting: false
});
});
lookupPrompt (value) {
const prompt = REPORT_OPTIONS.find(item => item.value === value).prompt;
return this.props.intl.formatMessage(prompt);
}
render () {
const {
intl,
onReport, // eslint-disable-line no-unused-vars
report,
type,
...modalProps
} = this.props;
......@@ -66,6 +91,7 @@ class ReportModal extends React.Component {
<Modal
className="mod-report"
contentLabel={contentLabel}
isOpen={report.open}
{...modalProps}
>
<div>
......@@ -88,68 +114,38 @@ class ReportModal extends React.Component {
/>
<Form
className="report"
onSubmit={this.handleSubmit}
onSubmit={onReport}
>
<Select
required
name="reason"
options={[
{
value: '',
label: this.props.intl.formatMessage({id: 'report.reasonPlaceHolder'})
},
{
value: '0',
label: this.props.intl.formatMessage({id: 'report.reasonCopy'})
},
{
value: '1',
label: this.props.intl.formatMessage({id: 'report.reasonUncredited'})
},
{
value: '2',
label: this.props.intl.formatMessage({id: 'report.reasonScary'})
},
{
value: '3',
label: this.props.intl.formatMessage({id: 'report.reasonLanguage'})
},
{
value: '4',
label: this.props.intl.formatMessage({id: 'report.reasonMusic'})
},
{
value: '8',
label: this.props.intl.formatMessage({id: 'report.reasonImage'})
},
{
value: '5',
label: this.props.intl.formatMessage({id: 'report.reasonPersonal'})
},
{
value: '6',
label: this.props.intl.formatMessage({id: 'general.other'})
}
]}
value={this.state.reason}
onChange={this.handleReasonSelect}
elementWrapperClassName="report-modal-field"
label={null}
name="report_category"
options={REPORT_OPTIONS.map(option => ({
value: option.value,
label: this.props.intl.formatMessage(option.label)
}))}
value={this.state.reportCategory}
onChange={this.handleReportCategorySelect}
/>
<TextArea
required
className="report-text"
name="reportText"
placeholder={this.state.prompt}
elementWrapperClassName="report-modal-field"
label={null}
name="notes"
placeholder={this.lookupPrompt(this.state.reportCategory)}
validationErrors={{
maxLength: this.props.intl.formatMessage({id: 'report.tooLongError'}),
minLength: this.props.intl.formatMessage({id: 'report.tooShortError'})
}}
validations={{
// TODO find out max and min characters
maxLength: 500,
minLength: 30
minLength: 20
}}
value={report.notes}
/>
{this.state.reportWaiting ? [
{report.waiting ? [
<Button
className="submit-button white"
disabled="disabled"
......@@ -180,6 +176,12 @@ ReportModal.propTypes = {
intl: intlShape,
onReport: PropTypes.func,
onRequestClose: PropTypes.func,
report: PropTypes.shape({
category: PropTypes.string,
notes: PropTypes.string,
open: PropTypes.bool,
waiting: PropTypes.bool
}),
type: PropTypes.string
};
......
......@@ -10,11 +10,11 @@
outline: none;
padding: 0;
width: 30rem;
overflow: hidden;
user-select: none;
}
.report-modal-header {
border-radius: 1rem 1rem 0 0;
box-shadow: inset 0 -1px 0 0 $ui-coral-dark;
background-color: $ui-coral;
padding-top: .75rem;
......@@ -36,4 +36,45 @@
width: 80%;
line-height: 1.5rem;
font-size: .875rem;
.validation-message {
$arrow-border-width: 1rem;
display: block;
position: absolute;
top: 0;
left: 0;
transform: translate(23.5rem, 0);
margin-left: $arrow-border-width;
border: 1px solid $active-gray;
border-radius: 5px;
background-color: $ui-orange;
padding: 1rem;
max-width: 18.75rem;
min-height: 1rem;
overflow: visible;
color: $type-white;
&:before {
display: block;
position: absolute;
top: 1rem;
left: -$arrow-border-width / 2;
transform: rotate(45deg);
border-bottom: 1px solid $active-gray;
border-left: 1px solid $active-gray;
border-radius: 5px;
background-color: $ui-orange;
width: $arrow-border-width;
height: $arrow-border-width;
content: "";
}
}
}
.report-modal-field {
position: relative;
}
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
require('./share-banner.scss');
const ShareBanner = props => (
<div className={classNames('shareBanner', props.className)}>
<div className="inner">
{props.children}
</div>
</div>
);
ShareBanner.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
module.exports = ShareBanner;
@import "../../colors";
$navigation-height: 50px;
.shareBanner {
background-color: $ui-orange-25percent;
width: 100%;
overflow: hidden;
color: $ui-orange;
}
......@@ -14,7 +14,7 @@ const ThumbnailColumn = props => (
if (props.itemType === 'preview') {
return (
<Thumbnail
avatar={`https://cdn2.scratch.mit.edu/get_image/user/${item.author.i}_32x32.png`}
avatar={`https://cdn2.scratch.mit.edu/get_image/user/${item.author.id}_32x32.png`}
creator={item.author.username}
favorites={item.stats.favorites}
href={href}
......
......@@ -36,3 +36,25 @@ const Raven = require('raven-js');
window._locale = updateLocale();
})();
/**
* -----------------------------------------------------------------------------
* Console warning
* -----------------------------------------------------------------------------
*/
(() => {
window.onload = function () {
/* eslint-disable no-console */
console.log('%cStop!', 'color: #F00; font-size: 30px; -webkit-text-stroke: 1px black; font-weight:bold');
console.log(
'This is part of your browser intended for developers. ' +
'If someone told you to copy-and-paste something here, ' +
'don\'t do it! It could allow them to take over your ' +
'Scratch account, delete all of your projects, or do many ' +
'other harmful things. If you don\'t understand what exactly ' +
'you are doing here, you should close this window without doing ' +
'anything.'
);
/* eslint-enable no-console */
};
})();
const EXTENSION_INFO = {
microbit: {
name: 'micro:bit',
icon: 'extension-microbit.svg',
hasStatus: true
},
music: {
l10nId: 'preview.musicExtensionChip',
icon: 'extension-music.svg'
},
pen: {
l10nId: 'preview.penExtensionChip',
icon: 'extension-pen.svg'
},
speak: {
name: 'Amazon Polly'
},
speech: {
l10nId: 'preview.speechExtensionChip'
},
translate: {
l10nId: 'preview.translateExtensionChip',
icon: 'extension-translate.svg'
},
videoSensing: {
l10nId: 'preview.videoMotionChip',
icon: 'extension-videomotion.svg'
},
wedo2: {
name: 'LEGO WeDo 2.0',
icon: 'extension-wedo2.svg',
hasStatus: true
}
};
export default EXTENSION_INFO;
import ScratchStorage from 'scratch-storage';
const PROJECT_SERVER = 'https://projects.scratch.mit.edu';
/**
* Wrapper for ScratchStorage which adds default web sources.
* @todo make this more configurable
*/
class Storage extends ScratchStorage {
constructor () {
super();
this.addWebSource(
[this.AssetType.Project],
projectAsset => {
const [projectId, revision] = projectAsset.assetId.split('.');
return revision ?
`${PROJECT_SERVER}/internalapi/project/${projectId}/get/${revision}` :
`${PROJECT_SERVER}/internalapi/project/${projectId}/get/`;
}
);
}
}
const storage = new Storage();
export default storage;
......@@ -67,7 +67,7 @@ const About = () => (
id="about.aroundTheWorldDescription"
values={{
translationLink: (
<a href="http://wiki.scratch.mit.edu/wiki/How_to_Translate_Scratch">
<a href="https://en.scratch-wiki.info/wiki/How_to_Translate_Scratch">
<FormattedMessage id="about.translationLinkText" />
</a>
)
......@@ -119,7 +119,7 @@ const About = () => (
id="about.researchDescription"
values={{
researchLink: (
<a href="/info/research">
<a href="/research">
<FormattedMessage id="about.researchLinkText" />
</a>
),
......@@ -141,7 +141,7 @@ const About = () => (
<h3><FormattedMessage id="about.learnMore" /></h3>
<ul className="list">
<li>
<a href="/help"><FormattedMessage id="about.learnMoreHelp" /></a>
<a href="/tips"><FormattedMessage id="about.learnMoreHelp" /></a>
</li>
<li>
<a href="/info/faq"><FormattedMessage id="about.learnMoreFaq" /></a>
......
const classNames = require('classnames');
const React = require('react');
const PropTypes = require('prop-types');
const FormattedMessage = require('react-intl').FormattedMessage;
require('./extension-chip.scss');
const ExtensionChip = props => (
<div className={classNames('extension-chip', {'has-status': props.hasStatus})}>
{props.iconURI &&
<img
className="extension-icon"
src={props.iconURI}
/>
}
<div className="extension-content">
{props.extensionL10n ?
<FormattedMessage id={props.extensionL10n} /> :
<span>{props.extensionName}</span>
}
{props.hasStatus && (
<div className="extension-status">
Needs Connection
</div>
)}
</div>
</div>
);
ExtensionChip.propTypes = {
extensionL10n: PropTypes.string,
extensionName: PropTypes.string,
hasStatus: PropTypes.bool,
iconURI: PropTypes.string
};
module.exports = ExtensionChip;
@import "../../colors";
.extension-chip {
display: flex;
margin: 0 .625rem .625rem 0;
border-radius: 5px;
background-color: $ui-blue-25percent;
padding: .625rem;
height: 3rem;
flex-flow: row;
box-sizing: border-box;
&.has-status {
background-color: $ui-orange-25percent;
}
.extension-icon {
margin-right: 5px;
width: 2rem;
height: 2rem;
}
.extension-content {
display: flex;
margin: 0 5px;
font-size: .875rem;
justify-content: center;
flex-flow: column;
}
.extension-status {
border-radius: 10px;
background-color: $ui-orange;
padding: 0 5px;
text-align: center;
color: $ui-white;
font-size: .675rem;
}
}
{
"preview.musicExtensionChip": "Music",
"preview.penExtensionChip": "Pen",
"preview.speechExtensionChip": "Google Speech",
"preview.translateExtensionChip": "Google Translate",
"preview.videoMotionChip": "Video Motion"
}
\ No newline at end of file
const bindAll = require('lodash.bindall');
const FormattedDate = require('react-intl').FormattedDate;
const injectIntl = require('react-intl').injectIntl;
const PropTypes = require('prop-types');
......@@ -10,401 +9,305 @@ const approx = require('approximate-number');
const GUI = require('scratch-gui').default;
const IntlGUI = injectIntl(GUI);
const sessionActions = require('../../redux/session.js');
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 CappedNumber = require('../../components/cappednumber/cappednumber.jsx');
const ShareBanner = require('../../components/share-banner/share-banner.jsx');
const ThumbnailColumn = require('../../components/thumbnailcolumn/thumbnailcolumn.jsx');
const ShareBanner = require('./share-banner.jsx');
const RemixCredit = require('./remix-credit.jsx');
const RemixList = require('./remix-list.jsx');
const StudioList = require('./studio-list.jsx');
const InplaceInput = require('../../components/forms/inplace-input.jsx');
const ReportModal = require('../../components/modal/report/modal.jsx');
const ExtensionChip = require('./extension-chip.jsx');
const projectShape = require('./projectshape.jsx').projectShape;
require('./preview.scss');
class PreviewPresentation extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleReportClick',
'handleReportClose',
'handleReportSubmit'
]);
this.state = {
reportOpen: false
};
}
handleReportClick (e) {
e.preventDefault();
this.setState({reportOpen: true});
}
handleReportClose () {
this.setState({reportOpen: false});
}
handleReportSubmit (formData, callback) {
const data = {
...formData,
id: this.props.projectId,
username: this.props.user.username
};
console.log('submit report data', data); // eslint-disable-line no-console
// TODO: pass error to modal via callback.
callback();
this.setState({reportOpen: false});
}
render () {
const {
editable,
faved,
favoriteCount,
isFullScreen,
loved,
loveCount,
originalInfo,
parentInfo,
projectId,
projectInfo,
remixes,
sessionStatus,
studios,
user,
onFavoriteClicked,
onLoveClicked,
onSeeInside,
onUpdate
// ...otherProps TBD
} = this.props;
const shareDate = (projectInfo.history && projectInfo.history.shared) ? projectInfo.history.shared : '';
return (
<div className="preview">
{projectInfo.history && shareDate === '' &&
<ShareBanner>
const PreviewPresentation = ({
editable,
extensions,
faved,
favoriteCount,
isFullScreen,
isLoggedIn,
isShared,
loved,
loveCount,
originalInfo,
parentInfo,
projectId,
projectInfo,
remixes,
report,
studios,
userOwnsProject,
onFavoriteClicked,
onLoveClicked,
onReportClicked,
onReportClose,
onReportSubmit,
onSeeInside,
onUpdate
}) => {
const shareDate = ((projectInfo.history && projectInfo.history.shared)) ? projectInfo.history.shared : '';
return (
<div className="preview">
<ShareBanner shared={isShared} />
{ projectInfo && projectInfo.author && projectInfo.author.id && (
<div className="inner">
<Formsy>
<FlexRow className="preview-row">
<span className="share-text">
This project is not shared — so only you can see it. Click share to let everyone see it!
</span>
<Button className="button share-button">
Share
</Button>
</FlexRow>
</ShareBanner>
}
{ projectInfo && projectInfo.author && projectInfo.author.id && (
<div className="inner">
<Formsy>
<FlexRow className="preview-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`}
/>
</a>
<div className="title">
{editable ?
<InplaceInput
className="project-title"
handleUpdate={onUpdate}
name="title"
validationErrors={{
maxLength: 'Sorry title is too long'
// maxLength: props.intl.formatMessage({
// id: 'project.titleMaxLength'
// })
}}
validations={{
maxLength: 100
}}
value={projectInfo.title}
/> :
<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`}
/>
</a>
<div className="title">
{editable ?
<InplaceInput
className="project-title"
handleUpdate={onUpdate}
name="title"
validationErrors={{
maxLength: 'Sorry title is too long'
// maxLength: props.intl.formatMessage({
// id: 'project.titleMaxLength'
// })
}}
validations={{
maxLength: 100
}}
value={projectInfo.title}
/> :
<React.Fragment>
<div className="project-title">{projectInfo.title}</div>
}
</div>
</FlexRow>
<div className="project-buttons">
{sessionStatus === sessionActions.Status.FETCHED &&
Object.keys(user).length > 0 &&
user.id !== projectInfo.author.id &&
<Button className="button remix-button">
Remix
</Button>
{'by '}
<a href={`/users/${projectInfo.author.username}`}>
{projectInfo.author.username}
</a>
</React.Fragment>
}
<Button
className="button see-inside-button"
onClick={onSeeInside}
>
See Inside
</Button>
</div>
</FlexRow>
<FlexRow className="preview-row">
<div className="guiPlayer">
<IntlGUI
isPlayerOnly
basePath="/"
className="guiPlayer"
isFullScreen={isFullScreen}
previewInfoVisible="false"
projectId={projectId}
/>
</div>
<FlexRow className="project-notes">
{parentInfo && parentInfo.author && parentInfo.id && (
<FlexRow className="remix-credit">
<Avatar
className="remix"
src={`https://cdn2.scratch.mit.edu/get_image/user/${parentInfo.author.id}_48x48.png`}
/>
<div className="credit-text">
Thanks to <a
href={`/users/${parentInfo.author.username}`}
>
{parentInfo.author.username}
</a> for the original project <a
href={`/preview/${parentInfo.id}`}
>
{parentInfo.title}
</a>.
</div>
</FlexRow>
)}
{originalInfo && originalInfo.author && originalInfo.id && (
<FlexRow className="remix-credit">
<Avatar
className="remix"
src={`https://cdn2.scratch.mit.edu/get_image/user/${originalInfo.author.id}_48x48.png`}
/>
<div className="credit-text">
Thanks to <a
href={`/users/${originalInfo.author.username}`}
>
{originalInfo.author.username}
</a> for the original project <a
href={`/preview/${originalInfo.id}`}
>
{originalInfo.title}
</a>.
</div>
</FlexRow>
)}
{/* eslint-disable max-len */}
<FlexRow className="description-block">
<div className="project-textlabel">
Instructions
</div>
{editable ?
<InplaceInput
className={classNames(
'project-description-edit',
{remixes: parentInfo && parentInfo.author}
)}
handleUpdate={onUpdate}
name="instructions"
placeholder="Tell people how to use your project (such as which keys to press)."
type="textarea"
validationErrors={{
maxLength: 'Sorry description is too long'
// maxLength: props.intl.formatMessage({
// id: 'project.descriptionMaxLength'
// })
}}
validations={{
// TODO: actual 5000
maxLength: 1000
}}
value={projectInfo.instructions}
/> :
<div className="project-description">
{decorateText(projectInfo.instructions)}
</div>
}
</FlexRow>
<FlexRow className="description-block">
<div className="project-textlabel">
Notes and Credits
</div>
{editable ?
<InplaceInput
className={classNames(
'project-description-edit',
'last',
{remixes: parentInfo && parentInfo.author}
)}
handleUpdate={onUpdate}
name="description"
placeholder="How did you make this project? Did you use ideas scripts or artwork from other people? Thank them here."
type="textarea"
validationErrors={{
maxLength: 'Sorry description is too long'
// maxLength: props.intl.formatMessage({
// id: 'project.descriptionMaxLength'
// })
}}
validations={{
// TODO: actual 5000
maxLength: 1000
}}
value={projectInfo.description}
/> :
<div className="project-description last">
{decorateText(projectInfo.description)}
</div>
}
</FlexRow>
{/* eslint-enable max-len */}
</FlexRow>
</FlexRow>
<FlexRow className="preview-row">
<FlexRow className="stats">
<div
className={classNames('project-loves', {loved: loved})}
key="loves"
onClick={onLoveClicked}
>
{approx(loveCount, {decimal: false})}
</div>
<div
className={classNames('project-favorites', {favorited: faved})}
key="favorites"
onClick={onFavoriteClicked}
>
{approx(favoriteCount, {decimal: false})}
</div>
<div
className="project-remixes"
key="remixes"
>
{approx(projectInfo.stats.remixes, {decimal: false})}
</div>
<div
className="project-views"
key="views"
>
<CappedNumber value={projectInfo.stats.views} />
<div className="project-buttons">
{/* TODO: Hide Remix button for now until implemented */}
{(!userOwnsProject && false) &&
<Button className="button remix-button">
Remix
</Button>
}
<Button
className="button see-inside-button"
onClick={onSeeInside}
>
See Inside
</Button>
</div>
</FlexRow>
<FlexRow className="preview-row">
<div className="guiPlayer">
<IntlGUI
isPlayerOnly
basePath="/"
className="guiPlayer"
isFullScreen={isFullScreen}
previewInfoVisible="false"
projectId={projectId}
/>
</div>
<FlexRow className="project-notes">
<RemixCredit projectInfo={parentInfo} />
<RemixCredit projectInfo={originalInfo} />
{/* eslint-disable max-len */}
<FlexRow className="description-block">
<div className="project-textlabel">
Instructions
</div>
{editable ?
<InplaceInput
className={classNames(
'project-description-edit',
{remixes: parentInfo && parentInfo.author}
)}
handleUpdate={onUpdate}
name="instructions"
placeholder="Tell people how to use your project (such as which keys to press)."
type="textarea"
validationErrors={{
maxLength: 'Sorry description is too long'
// maxLength: props.intl.formatMessage({
// id: 'project.descriptionMaxLength'
// })
}}
validations={{
// TODO: actual 5000
maxLength: 1000
}}
value={projectInfo.instructions}
/> :
<div className="project-description">
{decorateText(projectInfo.instructions)}
</div>
}
</FlexRow>
<FlexRow className="subactions">
<div className="share-date">
<div className="copyleft">&copy;</div>
{' '}
{/* eslint-disable react/jsx-sort-props */}
{shareDate === null ?
'Unshared' :
<FormattedDate
value={Date.parse(shareDate)}
day="2-digit"
month="short"
year="numeric"
/>
}
{/* eslint-enable react/jsx-sort-props */}
<FlexRow className="description-block">
<div className="project-textlabel">
Notes and Credits
</div>
<FlexRow className="action-buttons">
<Button className="action-button studio-button">
Add to Studio
</Button>
<Button className="action-button copy-link-button">
Copy Link
</Button>
{
sessionStatus === sessionActions.Status.FETCHED &&
Object.keys(user).length > 0 &&
user.id !== projectInfo.author.id && [
<Button
className="action-button report-button"
key="report-button"
onClick={this.handleReportClick}
>
Report
</Button>,
<ReportModal
isOpen={this.state.reportOpen}
key="report-modal"
type="project"
onReport={this.handleReportSubmit}
onRequestClose={this.handleReportClose}
/>
]
}
</FlexRow>
{editable ?
<InplaceInput
className={classNames(
'project-description-edit',
'last',
{remixes: parentInfo && parentInfo.author}
)}
handleUpdate={onUpdate}
name="description"
placeholder="How did you make this project? Did you use ideas scripts or artwork from other people? Thank them here."
type="textarea"
validationErrors={{
maxLength: 'Sorry description is too long'
// maxLength: props.intl.formatMessage({
// id: 'project.descriptionMaxLength'
// })
}}
validations={{
// TODO: actual 5000
maxLength: 1000
}}
value={projectInfo.description}
/> :
<div className="project-description last">
{decorateText(projectInfo.description)}
</div>
}
</FlexRow>
{/* eslint-enable max-len */}
</FlexRow>
<FlexRow className="preview-row">
<div className="comments-container">
<div className="project-title">
Comments go here
</div>
</FlexRow>
<FlexRow className="preview-row">
<FlexRow className="stats">
<div
className={classNames('project-loves', {loved: loved})}
key="loves"
onClick={onLoveClicked}
>
{approx(loveCount, {decimal: false})}
</div>
<FlexRow className="column">
{/* hide remixes if there aren't any */}
{remixes && remixes.length !== 0 && (
<FlexRow className="remix-list">
<div className="project-title">
Remixes
</div>
{remixes && remixes.length === 0 ? (
// TODO: style remix invitation
<span>Invite user to remix</span>
) : (
<ThumbnailColumn
cards
showAvatar
itemType="preview"
items={remixes.slice(0, 5)}
showFavorites={false}
showLoves={false}
showViews={false}
/>
)}
</FlexRow>
)}
{/* hide studios if there aren't any */}
{studios && studios.length !== 0 && (
<FlexRow className="studio-list">
<div className="project-title">
Studios
</div>
{studios && studios.length === 0 ? (
// TODO: invite user to add to studio?
<span>None </span>
) : (
<ThumbnailColumn
cards
showAvatar
itemType="gallery"
items={studios.slice(0, 5)}
showFavorites={false}
showLoves={false}
showViews={false}
/>
)}
</FlexRow>
)}
<div
className={classNames('project-favorites', {favorited: faved})}
key="favorites"
onClick={onFavoriteClicked}
>
{approx(favoriteCount, {decimal: false})}
</div>
<div
className="project-remixes"
key="remixes"
>
{approx(projectInfo.stats.remixes, {decimal: false})}
</div>
<div
className="project-views"
key="views"
>
<CappedNumber value={projectInfo.stats.views} />
</div>
</FlexRow>
<FlexRow className="subactions">
<div className="share-date">
<div className="copyleft">&copy;</div>
{' '}
{/* eslint-disable react/jsx-sort-props */}
{shareDate === null ?
'Unshared' :
<FormattedDate
value={Date.parse(shareDate)}
day="2-digit"
month="short"
year="numeric"
/>
}
{/* eslint-enable react/jsx-sort-props */}
</div>
<FlexRow className="action-buttons">
<Button className="action-button studio-button">
Add to Studio
</Button>
<Button className="action-button copy-link-button">
Copy Link
</Button>
{(isLoggedIn && !userOwnsProject) &&
<React.Fragment>
<Button
className="action-button report-button"
key="report-button"
onClick={onReportClicked}
>
Report
</Button>,
<ReportModal
key="report-modal"
report={report}
type="project"
onReport={onReportSubmit}
onRequestClose={onReportClose}
/>
</React.Fragment>
}
</FlexRow>
</FlexRow>
</Formsy>
</div>
)}
</div>
);
}
}
</FlexRow>
<FlexRow className="preview-row">
<FlexRow className="extension-list">
{extensions && extensions.map(extension => (
<ExtensionChip
extensionL10n={extension.l10nId}
extensionName={extension.name}
hasStatus={extension.hasStatus}
iconURI={extension.icon && `/svgs/project/${extension.icon}`}
key={extension.name || extension.l10nId}
/>
))}
</FlexRow>
</FlexRow>
<FlexRow className="preview-row">
<div className="comments-container">
<div className="project-title" />
</div>
<FlexRow className="column">
<RemixList remixes={remixes} />
<StudioList studios={studios} />
</FlexRow>
</FlexRow>
</Formsy>
</div>
)}
</div>
);
};
PreviewPresentation.propTypes = {
editable: PropTypes.bool,
extensions: PropTypes.arrayOf(PropTypes.object),
faved: PropTypes.bool,
favoriteCount: PropTypes.number,
isFullScreen: PropTypes.bool,
isLoggedIn: PropTypes.bool,
isShared: PropTypes.bool,
loveCount: PropTypes.number,
loved: PropTypes.bool,
onFavoriteClicked: PropTypes.func,
onLoveClicked: PropTypes.func,
onReportClicked: PropTypes.func.isRequired,
onReportClose: PropTypes.func.isRequired,
onReportSubmit: PropTypes.func.isRequired,
onSeeInside: PropTypes.func,
onUpdate: PropTypes.func,
originalInfo: projectShape,
......@@ -412,18 +315,14 @@ PreviewPresentation.propTypes = {
projectId: PropTypes.string,
projectInfo: projectShape,
remixes: PropTypes.arrayOf(PropTypes.object),
sessionStatus: PropTypes.string.isRequired,
report: PropTypes.shape({
category: PropTypes.string,
notes: PropTypes.string,
open: PropTypes.bool,
waiting: PropTypes.bool
}),
studios: PropTypes.arrayOf(PropTypes.object),
user: PropTypes.shape({
id: PropTypes.number,
banned: PropTypes.bool,
username: PropTypes.string,
token: PropTypes.string,
thumbnailUrl: PropTypes.string,
dateJoined: PropTypes.string,
email: PropTypes.string,
classroomId: PropTypes.string
}).isRequired
userOwnsProject: PropTypes.bool
};
module.exports = injectIntl(PreviewPresentation);
......@@ -3,8 +3,12 @@ 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 PreviewPresentation = require('./presentation.jsx');
const projectShape = require('./projectshape.jsx').projectShape;
......@@ -24,12 +28,35 @@ class Preview extends React.Component {
'handleLoveToggle',
'handlePermissions',
'handlePopState',
'handleReportClick',
'handleReportClose',
'handleReportSubmit',
'handleSeeInside',
'handleUpdate',
'initCounts',
'pushHistory'
'isShared',
'pushHistory',
'userOwnsProject'
]);
this.state = this.initState();
const pathname = window.location.pathname.toLowerCase();
const parts = pathname.split('/').filter(Boolean);
// parts[0]: 'preview'
// parts[1]: either :id or 'editor'
// parts[2]: undefined if no :id, otherwise either 'editor' or 'fullscreen'
this.state = {
editable: false,
extensions: [],
favoriteCount: 0,
loveCount: 0,
projectId: parts[1] === 'editor' ? 0 : parts[1],
report: {
category: '',
notes: '',
open: false,
waiting: false
}
};
this.getExtensions(this.state.projectId);
this.addEventListeners();
}
componentDidUpdate (prevProps) {
......@@ -52,6 +79,7 @@ class Preview extends React.Component {
}
if (this.props.projectInfo.id !== prevProps.projectInfo.id) {
this.getExtensions(this.state.projectId);
this.initCounts(this.props.projectInfo.stats.favorites, this.props.projectInfo.stats.loves);
this.handlePermissions();
if (this.props.projectInfo.remix.parent !== null) {
......@@ -70,25 +98,71 @@ class Preview extends React.Component {
componentWillUnmount () {
this.removeEventListeners();
}
initState () {
const pathname = window.location.pathname.toLowerCase();
const parts = pathname.split('/').filter(Boolean);
// parts[0]: 'preview'
// parts[1]: either :id or 'editor'
// parts[2]: undefined if no :id, otherwise either 'editor' or 'fullscreen'
return {
editable: false,
favoriteCount: 0,
loveCount: 0,
projectId: parts[1] === 'editor' ? 0 : parts[1]
};
}
addEventListeners () {
window.addEventListener('popstate', this.handlePopState);
}
removeEventListeners () {
window.removeEventListener('popstate', this.handlePopState);
}
getExtensions (projectId) {
storage
.load(storage.AssetType.Project, projectId, storage.DataFormat.JSON)
.then(projectAsset => {
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)
});
});
});
}
handleReportClick () {
this.setState({report: {...this.state.report, open: true}});
}
handleReportClose () {
this.setState({report: {...this.state.report, open: false}});
}
handleReportSubmit (formData) {
this.setState({report: {
category: formData.report_category,
notes: formData.notes,
open: this.state.report.open,
waiting: true}
});
const data = {
...formData,
id: this.state.projectId,
user: this.props.user.username
};
console.log('submit report data', data); // eslint-disable-line no-console
this.setState({report: {
category: '',
notes: '',
open: false,
waiting: false}
});
}
handlePopState () {
const path = window.location.pathname.toLowerCase();
const playerMode = path.indexOf('editor') === -1;
......@@ -179,6 +253,28 @@ class Preview extends React.Component {
loveCount: loves
});
}
isShared () {
return (
// if we don't have projectInfo assume shared until we know otherwise
Object.keys(this.props.projectInfo).length === 0 || (
this.props.projectInfo.history &&
this.props.projectInfo.history.shared.length > 0
)
);
}
isLoggedIn () {
return (
this.props.sessionStatus === sessionActions.Status.FETCHED &&
Object.keys(this.props.user).length > 0
);
}
userOwnsProject () {
return (
this.isLoggedIn() &&
Object.keys(this.props.projectInfo).length > 0 &&
this.props.user.id === this.props.projectInfo.author.id
);
}
render () {
return (
this.props.playerMode ?
......@@ -186,9 +282,12 @@ class Preview extends React.Component {
<PreviewPresentation
comments={this.props.comments}
editable={this.state.editable}
extensions={this.state.extensions}
faved={this.props.faved}
favoriteCount={this.state.favoriteCount}
isFullScreen={this.state.isFullScreen}
isLoggedIn={this.isLoggedIn()}
isShared={this.isShared()}
loveCount={this.state.loveCount}
loved={this.props.loved}
originalInfo={this.props.original}
......@@ -196,11 +295,15 @@ class Preview extends React.Component {
projectId={this.state.projectId}
projectInfo={this.props.projectInfo}
remixes={this.props.remixes}
sessionStatus={this.props.sessionStatus}
report={this.state.report}
studios={this.props.studios}
user={this.props.user}
userOwnsProject={this.userOwnsProject()}
onFavoriteClicked={this.handleFavoriteToggle}
onLoveClicked={this.handleLoveToggle}
onReportClicked={this.handleReportClick}
onReportClose={this.handleReportClose}
onReportSubmit={this.handleReportSubmit}
onSeeInside={this.handleSeeInside}
onUpdate={this.handleUpdate}
/>
......
@import "../../colors";
@import "../../frameless";
/* stage size contants
/* stage size constants
* this is a hack right now - stage includes padding of .5rem (8px) for alignment in gui
* in www the player is placed with margin -.5rem to align the edge.
* the height is calculated from the actual height on the page (404)
......@@ -10,13 +10,6 @@ $gui-width: 496px;
$stage-width: 480px;
$stage-height: 404px;
// remix credit height: 52px
// project text label line-height + margin-bottom .5rem: 19px + 8px = 27px
// Formsy wrapper adds 3px to the input height for
$description-input: 166px; // $stage-height / 2 - $project-label - $wrapper - margin
$description-input-small: 120px; // normal $description-input - $remix-credit
/* override view padding for share banner */
#view {
padding: 0 0 20px 0;
......@@ -138,7 +131,6 @@ $description-input-small: 120px; // normal $description-input - $remix-credit
width: 60%;
}
.share-button,
.remix-button,
.see-inside-button {
margin-top: 0;
......@@ -158,18 +150,6 @@ $description-input-small: 120px; // normal $description-input - $remix-credit
}
}
.shareText {
align-self: center;
}
.share-button {
background-color: $ui-orange;
&:before {
background-image: url("/svgs/project/share-white.svg");
}
}
.remix-button {
background-color: $ui-green;
......@@ -189,6 +169,7 @@ $description-input-small: 120px; // normal $description-input - $remix-credit
margin-top: 1rem;
justify-content: space-between;
align-items: flex-start;
flex-wrap: nowrap;
}
.guiPlayer {
......@@ -237,10 +218,12 @@ $description-input-small: 120px; // normal $description-input - $remix-credit
}
.description-block {
display: flex;
width: 100%;
min-height: 0;
flex-direction: column;
align-items: flex-start;
flex-grow: 1;
flex: 1;
}
.project-textlabel {
......@@ -256,6 +239,7 @@ $description-input-small: 120px; // normal $description-input - $remix-credit
background-color: $ui-blue-10percent;
padding: .5rem;
width: calc(100% - (1rem + 2px));
overflow: auto;
white-space: pre-line;
font-size: 1rem;
// flex-grow
......@@ -267,6 +251,7 @@ $description-input-small: 120px; // normal $description-input - $remix-credit
}
.project-description-edit {
display: flex;
margin-bottom: .75rem;
border: 1px solid $ui-blue-10percent;
border-radius: 8px;
......@@ -293,15 +278,12 @@ $description-input-small: 120px; // normal $description-input - $remix-credit
}
}
.inplace-textarea {
height: $description-input;
& > .grow {
display: flex;
flex: 1;
}
}
.project-description-edit.remixes .inplace-textarea {
height: $description-input-small;
}
.copyleft {
display: inline-block;
transform: scale(-1, 1);
......@@ -475,6 +457,10 @@ $description-input-small: 120px; // normal $description-input - $remix-credit
background-image: url("/svgs/project/report-white.svg");
}
}
.extension-list {
justify-content: flex-start;
}
.remix-list,
.studio-list {
......
const React = require('react');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const Avatar = require('../../components/avatar/avatar.jsx');
const projectShape = require('./projectshape.jsx').projectShape;
const RemixCredit = props => {
const projectInfo = props.projectInfo;
if (Object.keys(projectInfo).length === 0) return null;
return (
<FlexRow className="remix-credit">
<Avatar
className="remix"
src={`https://cdn2.scratch.mit.edu/get_image/user/${projectInfo.author.id}_48x48.png`}
/>
<div className="credit-text">
Thanks to <a
href={`/users/${projectInfo.author.username}`}
>
{projectInfo.author.username}
</a> for the original project <a
href={`/preview/${projectInfo.id}`}
>
{projectInfo.title}
</a>.
</div>
</FlexRow>
);
};
RemixCredit.propTypes = {
projectInfo: projectShape
};
module.exports = RemixCredit;
const React = require('react');
const PropTypes = require('prop-types');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const ThumbnailColumn = require('../../components/thumbnailcolumn/thumbnailcolumn.jsx');
const projectShape = require('./projectshape.jsx').projectShape;
const RemixList = props => {
const remixes = props.remixes;
if (remixes.length === 0) return null;
return (
<FlexRow className="remix-list">
<div className="project-title">
Remixes
</div>
{remixes.length === 0 ? (
// TODO: style remix invitation
<span>Invite user to remix</span>
) : (
<ThumbnailColumn
cards
showAvatar
itemType="preview"
items={remixes.slice(0, 5)}
showFavorites={false}
showLoves={false}
showViews={false}
/>
)}
</FlexRow>
);
};
RemixList.propTypes = {
remixes: PropTypes.arrayOf(projectShape)
};
module.exports = RemixList;
const PropTypes = require('prop-types');
const React = require('react');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const Button = require('../../components/forms/button.jsx');
require('./share-banner.scss');
const ShareBanner = props => {
if (props.shared) return null;
return (
<div className="shareBanner">
<div className="inner">
<FlexRow className="preview-row">
<span className="share-text">
This project is not shared — so only you can see it. Click share to let everyone see it!
</span>
<Button className="button share-button">
Share
</Button>
</FlexRow>
</div>
</div>
);
};
ShareBanner.propTypes = {
shared: PropTypes.bool.isRequired
};
module.exports = ShareBanner;
@import "../../colors";
$navigation-height: 50px;
.shareBanner {
background-color: $ui-orange-25percent;
width: 100%;
overflow: hidden;
color: $ui-orange;
}
.share-button {
margin-top: 0;
background-color: $ui-orange;
font-size: .875rem;
font-weight: normal;
&:before {
display: inline-block;
margin-right: .5rem;
background-image: url("/svgs/project/share-white.svg");
background-repeat: no-repeat;
background-position: center center;
background-size: contain;
width: 1.25rem;
height: 1.25rem;
vertical-align: middle;
content: "";
}
}
const React = require('react');
const PropTypes = require('prop-types');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const ThumbnailColumn = require('../../components/thumbnailcolumn/thumbnailcolumn.jsx');
const projectShape = require('./projectshape.jsx').projectShape;
const StudioList = props => {
const studios = props.studios;
if (studios.length === 0) return null;
return (
<FlexRow className="remix-list">
<div className="project-title">
Studios
</div>
{studios.length === 0 ? (
// TODO: style remix invitation
<span>Invite user to add to studio</span>
) : (
<ThumbnailColumn
cards
showAvatar
itemType="studio"
items={studios.slice(0, 5)}
showFavorites={false}
showLoves={false}
showViews={false}
/>
)}
</FlexRow>
);
};
StudioList.propTypes = {
studios: PropTypes.arrayOf(projectShape)
};
module.exports = StudioList;
......@@ -45,7 +45,7 @@ class Search extends React.Component {
while (term.indexOf('&') > -1) {
term = term.substring(0, term.indexOf('&'));
}
term = decodeURI(term.split('+').join(' '));
term = decodeURIComponent(term.split('+').join(' '));
this.props.dispatch(navigationActions.setSearchTerm(term));
}
componentDidUpdate (prevProps) {
......
......@@ -27,7 +27,7 @@ class Splash extends React.Component {
'shouldShowEmailConfirmation'
]);
this.state = {
projectCount: 20000000, // gets the shared project count
projectCount: 30000000, // gets the shared project count
news: [], // gets news posts from the scratch Tumblr
emailConfirmationModalOpen: false, // flag that determines whether to show banner to request email conf.
refreshCacheStatus: 'notrequested'
......
......@@ -35,5 +35,5 @@
"teacherRegistration.emailStepTitle": "Email Address",
"teacherRegistration.emailStepDescription": "We will send you a confirmation email that will allow you to access your Scratch Teacher Account.",
"teacherRegistration.validationEmailMatch": "The emails do not match",
"teacherRegistration.validationAge": "Sorry, teachers must be at least 13 years old"
"teacherRegistration.validationAge": "Sorry, teachers must be at least 16 years old"
}
......@@ -93,7 +93,7 @@ class TeacherRegistration extends React.Component {
}
render () {
const permissions = this.props.session.permissions || {};
return (
<Deck className="teacher-registration">
{this.state.registrationError ?
......@@ -106,7 +106,7 @@ class TeacherRegistration extends React.Component {
onNextStep={this.handleAdvanceStep}
/>
<Steps.DemographicsStep
birthOffset={13}
birthOffset={16}
waiting={this.state.waiting}
onNextStep={this.handleAdvanceStep}
/>
......
<?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>microbit-block-icon</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="microbit-block-icon" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="microbit" transform="translate(3.500000, 6.000000)">
<polygon id="Shape" stroke="#7C87A5" stroke-width="0.22" fill="#E6E7E8" fill-rule="nonzero" stroke-linecap="round" stroke-linejoin="round" points="19.11 0.11 19.11 1 13.78 1 13.78 0.11 14.22 0.11 14.22 0.33 14.67 0.33 14.67 0.11 18.22 0.11 18.22 0.33 18.67 0.33 18.67 0.11"></polygon>
<path d="M30.66,1 L2.22,1 C1.24082518,0.999984548 0.445500982,1.79084064 0.44,2.77 L0.44,25 C0.441253651,25.7700944 0.93758836,26.4520502 1.67,26.69 C1.84871651,26.7421345 2.03383687,26.7690611 2.22,26.77 L30.66,26.77 C30.8461631,26.7690611 31.0312835,26.7421345 31.21,26.69 C31.9424116,26.4520502 32.4387463,25.7700944 32.44,25 L32.44,2.77 C32.434499,1.79084064 31.6391748,0.999984548 30.66,1 Z M3.11,23.54 C2.71343518,23.54 2.34699416,23.3284352 2.14871175,22.985 C1.95042933,22.6415648 1.95042933,22.2184352 2.14871175,21.875 C2.34699416,21.5315648 2.71343518,21.32 3.11,21.32 C3.50656482,21.32 3.87300584,21.5315648 4.07128825,21.875 C4.26957067,22.2184352 4.26957067,22.6415648 4.07128825,22.985 C3.87300584,23.3284352 3.50656482,23.54 3.11,23.54 Z M9.38,23.54 C8.93014083,23.5399817 8.52477626,23.2684441 8.35357179,22.8524366 C8.18236732,22.436429 8.27918149,21.9582239 8.59871605,21.641568 C8.91825061,21.3249121 9.39731226,21.2324295 9.81175347,21.4073917 C10.2261947,21.5823538 10.4940528,21.9901591 10.49,22.44 C10.4845121,23.0491272 9.98915197,23.5400247 9.38,23.54 Z M16.44,23.54 C15.9901408,23.5399817 15.5847763,23.2684441 15.4135718,22.8524366 C15.2423673,22.436429 15.3391815,21.9582239 15.658716,21.641568 C15.9782506,21.3249121 16.4573123,21.2324295 16.8717535,21.4073917 C17.2861947,21.5823538 17.5540528,21.9901591 17.55,22.44 C17.5445121,23.0491272 17.049152,23.5400247 16.44,23.54 Z M23.5,23.54 C23.1034352,23.54 22.7369942,23.3284352 22.5387117,22.985 C22.3404293,22.6415648 22.3404293,22.2184352 22.5387117,21.875 C22.7369942,21.5315648 23.1034352,21.32 23.5,21.32 C23.8965648,21.32 24.2630058,21.5315648 24.4612883,21.875 C24.6595707,22.2184352 24.6595707,22.6415648 24.4612883,22.985 C24.2630058,23.3284352 23.8965648,23.54 23.5,23.54 Z M29.77,23.54 C29.3201408,23.5399817 28.9147763,23.2684441 28.7435718,22.8524366 C28.5723673,22.436429 28.6691815,21.9582239 28.988716,21.641568 C29.3082506,21.3249121 29.7873123,21.2324295 30.2017535,21.4073917 C30.6161947,21.5823538 30.8840528,21.9901591 30.88,22.44 C30.8745121,23.0491272 30.379152,23.5400247 29.77,23.54 Z" id="Shape" stroke="#231F20" stroke-width="0.89" fill="#231F20" fill-rule="nonzero" opacity="0.1"></path>
<path d="M30.66,1 L2.22,1 C1.24082518,0.999984548 0.445500982,1.79084064 0.44,2.77 L0.44,25 C0.441253651,25.7700944 0.93758836,26.4520502 1.67,26.69 C1.84871651,26.7421345 2.03383687,26.7690611 2.22,26.77 L30.66,26.77 C30.8461631,26.7690611 31.0312835,26.7421345 31.21,26.69 C31.9424116,26.4520502 32.4387463,25.7700944 32.44,25 L32.44,2.77 C32.434499,1.79084064 31.6391748,0.999984548 30.66,1 Z M3.11,23.54 C2.71343518,23.54 2.34699416,23.3284352 2.14871175,22.985 C1.95042933,22.6415648 1.95042933,22.2184352 2.14871175,21.875 C2.34699416,21.5315648 2.71343518,21.32 3.11,21.32 C3.50656482,21.32 3.87300584,21.5315648 4.07128825,21.875 C4.26957067,22.2184352 4.26957067,22.6415648 4.07128825,22.985 C3.87300584,23.3284352 3.50656482,23.54 3.11,23.54 Z M9.38,23.54 C8.93014083,23.5399817 8.52477626,23.2684441 8.35357179,22.8524366 C8.18236732,22.436429 8.27918149,21.9582239 8.59871605,21.641568 C8.91825061,21.3249121 9.39731226,21.2324295 9.81175347,21.4073917 C10.2261947,21.5823538 10.4940528,21.9901591 10.49,22.44 C10.4845121,23.0491272 9.98915197,23.5400247 9.38,23.54 Z M16.44,23.54 C15.9901408,23.5399817 15.5847763,23.2684441 15.4135718,22.8524366 C15.2423673,22.436429 15.3391815,21.9582239 15.658716,21.641568 C15.9782506,21.3249121 16.4573123,21.2324295 16.8717535,21.4073917 C17.2861947,21.5823538 17.5540528,21.9901591 17.55,22.44 C17.5445121,23.0491272 17.049152,23.5400247 16.44,23.54 Z M23.5,23.54 C23.1034352,23.54 22.7369942,23.3284352 22.5387117,22.985 C22.3404293,22.6415648 22.3404293,22.2184352 22.5387117,21.875 C22.7369942,21.5315648 23.1034352,21.32 23.5,21.32 C23.8965648,21.32 24.2630058,21.5315648 24.4612883,21.875 C24.6595707,22.2184352 24.6595707,22.6415648 24.4612883,22.985 C24.2630058,23.3284352 23.8965648,23.54 23.5,23.54 Z M29.77,23.54 C29.3201408,23.5399817 28.9147763,23.2684441 28.7435718,22.8524366 C28.5723673,22.436429 28.6691815,21.9582239 28.988716,21.641568 C29.3082506,21.3249121 29.7873123,21.2324295 30.2017535,21.4073917 C30.6161947,21.5823538 30.8840528,21.9901591 30.88,22.44 C30.8745121,23.0491272 30.379152,23.5400247 29.77,23.54 Z" id="Shape" fill="#414757" fill-rule="nonzero"></path>
<path d="M0.44,9 L0.44,2.77 C0.445500982,1.79084064 1.24082518,0.999984548 2.22,1 L8.44,1 L0.44,9 Z" id="Shape" fill="#4C97FF" fill-rule="nonzero"></path>
<polygon id="Shape" fill="#4C97FF" fill-rule="nonzero" points="8.44 4.56 8.44 1 12 1"></polygon>
<polygon id="Shape" fill="#4C97FF" fill-rule="nonzero" points="12 3.67 12 1 14.67 1"></polygon>
<polygon id="Shape" stroke="#4C97FF" stroke-width="0.22" fill="#4C97FF" fill-rule="nonzero" stroke-linecap="round" stroke-linejoin="round" points="28.44 9.89 28.44 7.22 31.11 7.22"></polygon>
<polygon id="Shape" stroke="#4C97FF" stroke-width="0.22" fill="#4C97FF" fill-rule="nonzero" stroke-linecap="round" stroke-linejoin="round" points="4.45 16.11 4.45 18.78 1.78 18.78"></polygon>
<rect id="Rectangle-path" fill="#FFFFFF" fill-rule="nonzero" x="0.89" y="12.56" width="1" height="1" rx="0.44"></rect>
<rect id="Rectangle-path" fill="#FFFFFF" fill-rule="nonzero" x="31.11" y="12.56" width="1" height="1" rx="0.44"></rect>
<path d="M17.56,5.56 C17.7312083,5.55999999 17.87,5.42120826 17.87,5.25 C17.87,5.07879174 17.7312083,4.94000001 17.56,4.94 C17.3887917,4.94 17.25,5.07879173 17.25,5.25 C17.25,5.42120827 17.3887917,5.56 17.56,5.56" id="Shape" fill="#4C97FF" fill-rule="nonzero"></path>
<path d="M15.3,4.94 C15.1892477,4.93999999 15.0869083,4.99908565 15.0315321,5.09499999 C14.9761559,5.19091433 14.9761559,5.30908567 15.0315321,5.40500001 C15.0869083,5.50091435 15.1892477,5.56000001 15.3,5.56 C15.4712083,5.55999999 15.61,5.42120826 15.61,5.25 C15.61,5.07879174 15.4712083,4.94000001 15.3,4.94" id="Shape" fill="#4C97FF" fill-rule="nonzero"></path>
<path d="M15.3,4.33 C14.950262,4.29338789 14.6102464,4.45934112 14.4239421,4.75758228 C14.2376377,5.05582345 14.2376377,5.43417655 14.4239421,5.73241772 C14.6102464,6.03065888 14.950262,6.19661211 15.3,6.16 L17.58,6.16 C17.929738,6.19661211 18.2697536,6.03065888 18.4560579,5.73241772 C18.6423623,5.43417655 18.6423623,5.05582345 18.4560579,4.75758228 C18.2697536,4.45934112 17.929738,4.29338789 17.58,4.33 L15.3,4.33 M17.58,6.77 L15.3,6.77 C14.7056435,6.867381 14.1098479,6.60464376 13.7809267,6.10011072 C13.4520056,5.59557768 13.4520056,4.94442232 13.7809267,4.43988928 C14.1098479,3.93535624 14.7056435,3.672619 15.3,3.77 L17.58,3.77 C18.1743565,3.672619 18.7701521,3.93535624 19.0990733,4.43988928 C19.4279944,4.94442232 19.4279944,5.59557768 19.0990733,6.10011072 C18.7701521,6.60464376 18.1743565,6.867381 17.58,6.77" id="Shape" fill="#4C97FF" fill-rule="nonzero"></path>
<rect id="Rectangle-path" stroke="#231F20" stroke-width="0.89" fill="#231F20" fill-rule="nonzero" opacity="0.25" stroke-linecap="round" stroke-linejoin="round" x="2.22" y="10.78" width="4.44" height="4.44" rx="0.44"></rect>
<rect id="Rectangle-path" stroke="#7C87A5" stroke-width="0.22" fill="#E6E7E8" fill-rule="nonzero" stroke-linecap="round" stroke-linejoin="round" x="2.22" y="10.78" width="4.44" height="4.44" rx="0.44"></rect>
<circle id="Oval" stroke="#231F20" stroke-width="0.67" fill="#231F20" fill-rule="nonzero" opacity="0.1" cx="4.44" cy="13" r="1"></circle>
<circle id="Oval" stroke="#414757" stroke-width="0.22" fill="#414757" fill-rule="nonzero" cx="4.44" cy="13" r="1"></circle>
<rect id="Rectangle-path" stroke="#231F20" stroke-width="0.89" fill="#231F20" fill-rule="nonzero" opacity="0.25" stroke-linecap="round" stroke-linejoin="round" x="26.22" y="10.78" width="4.44" height="4.44" rx="0.44"></rect>
<rect id="Rectangle-path" stroke="#7C87A5" stroke-width="0.22" fill="#E6E7E8" fill-rule="nonzero" stroke-linecap="round" stroke-linejoin="round" x="26.22" y="10.78" width="4.44" height="4.44" rx="0.44"></rect>
<circle id="Oval" stroke="#231F20" stroke-width="0.67" fill="#231F20" fill-rule="nonzero" opacity="0.1" cx="28.44" cy="13" r="1"></circle>
<circle id="Oval" stroke="#414757" stroke-width="0.22" fill="#414757" fill-rule="nonzero" cx="28.44" cy="13" r="1"></circle>
<g id="Group" opacity="0.5" transform="translate(11.000000, 8.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.11" y="0.78" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(11.000000, 9.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.11" y="0.44" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="11.11" y="9" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(13.000000, 8.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.67" y="0.78" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(13.000000, 9.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.67" y="0.44" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="13.67" y="9" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(16.000000, 8.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.22" y="0.78" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(16.000000, 9.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.22" y="0.44" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="16.22" y="9" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(18.000000, 8.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.78" y="0.78" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(18.000000, 9.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.78" y="0.44" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="18.78" y="9" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(21.000000, 8.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.33" y="0.78" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(21.000000, 9.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.33" y="0.44" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="21.33" y="9" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(11.000000, 11.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.11" y="0.28" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(11.000000, 11.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.11" y="0.94" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="11.11" y="11.5" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(13.000000, 11.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.67" y="0.28" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(13.000000, 11.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.67" y="0.94" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="13.67" y="11.5" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(16.000000, 11.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.22" y="0.28" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(16.000000, 11.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.22" y="0.94" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="16.22" y="11.5" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(18.000000, 11.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.78" y="0.28" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(18.000000, 11.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.78" y="0.94" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="18.78" y="11.5" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(21.000000, 11.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.33" y="0.28" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(21.000000, 11.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.33" y="0.94" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="21.33" y="11.5" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(11.000000, 13.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.11" y="0.78" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(11.000000, 14.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.11" y="0.44" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="11.11" y="14" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(13.000000, 13.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.67" y="0.78" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(13.000000, 14.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.67" y="0.44" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="13.67" y="14" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(16.000000, 13.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.22" y="0.78" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(16.000000, 14.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.22" y="0.44" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="16.22" y="14" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(18.000000, 13.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.78" y="0.78" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(18.000000, 14.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.78" y="0.44" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="18.78" y="14" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(21.000000, 13.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.33" y="0.78" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(21.000000, 14.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.33" y="0.44" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="21.33" y="14" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(11.000000, 16.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.11" y="0.28" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(11.000000, 16.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.11" y="0.94" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="11.11" y="16.5" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(13.000000, 16.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.67" y="0.28" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(13.000000, 16.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.67" y="0.94" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="13.67" y="16.5" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(16.000000, 16.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.22" y="0.28" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(16.000000, 16.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.22" y="0.94" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="16.22" y="16.5" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(18.000000, 16.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.78" y="0.28" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(18.000000, 16.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.78" y="0.94" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="18.78" y="16.5" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(21.000000, 16.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.33" y="0.28" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(21.000000, 16.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.33" y="0.94" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="21.33" y="16.5" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(11.000000, 18.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.11" y="0.78" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(11.000000, 19.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.11" y="0.44" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="11.11" y="19" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(13.000000, 18.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.67" y="0.78" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(13.000000, 19.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.67" y="0.44" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="13.67" y="19" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(16.000000, 18.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.22" y="0.78" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(16.000000, 19.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.22" y="0.44" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="16.22" y="19" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(18.000000, 18.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.78" y="0.78" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(18.000000, 19.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.78" y="0.44" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="18.78" y="19" width="1" height="1" rx="0.04"></rect>
<g id="Group" opacity="0.5" transform="translate(21.000000, 18.000000)" fill="#E6E7E8" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.33" y="0.78" width="1" height="1" rx="0.04"></rect>
</g>
<g id="Group" opacity="0.25" transform="translate(21.000000, 19.000000)" fill="#231F20" fill-rule="nonzero">
<rect id="Rectangle-path" x="0.33" y="0.44" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" fill="#E6E7E8" fill-rule="nonzero" x="21.33" y="19" width="1" height="1" rx="0.04"></rect>
<path d="M1.44,23.67 L1.44,26.58 C1.23870729,26.4785354 1.05878028,26.3393466 0.91,26.17 L0.91,23.67 L1.44,23.67 Z" id="Shape" fill="#FFBF00" fill-rule="nonzero"></path>
<polygon id="Shape" fill="#FFBF00" fill-rule="nonzero" points="2.08 26.77 2.2 26.77 2.2 23.67 1.67 23.67 1.67 26.69"></polygon>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="2.46" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="3.24" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="4.03" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="4.81" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="5.59" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="6.38" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="7.16" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="7.95" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="8.73" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="9.52" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="10.3" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="11.08" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="11.87" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="12.65" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="13.44" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="14.22" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="15.01" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="15.79" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="16.58" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="17.36" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="18.14" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="18.93" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="19.71" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="20.5" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="21.28" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="22.06" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="22.85" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="23.63" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="24.42" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="25.2" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="25.99" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="26.77" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="27.56" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="28.34" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="29.12" y="23.67" width="1" height="3.1"></rect>
<rect id="Rectangle-path" fill="#FFBF00" fill-rule="nonzero" x="29.91" y="23.67" width="1" height="3.1"></rect>
<polygon id="Shape" fill="#FFBF00" fill-rule="nonzero" points="30.81 26.77 30.69 26.77 30.69 23.67 31.22 23.67 31.22 26.69"></polygon>
<path d="M32,23.67 L32,26.17 C31.8512197,26.3393466 31.6712927,26.4785354 31.47,26.58 L31.47,23.67 L32,23.67 Z" id="Shape" fill="#FFBF00" fill-rule="nonzero"></path>
<path d="M3.11,23.54 C2.71343518,23.54 2.34699416,23.3284352 2.14871175,22.985 C1.95042933,22.6415648 1.95042933,22.2184352 2.14871175,21.875 C2.34699416,21.5315648 2.71343518,21.32 3.11,21.32 C3.50656482,21.32 3.87300584,21.5315648 4.07128825,21.875 C4.26957067,22.2184352 4.26957067,22.6415648 4.07128825,22.985 C3.87300584,23.3284352 3.50656482,23.54 3.11,23.54 Z M2.22,26.77 L4.54,26.77 L4.54,22.44 C4.47765352,21.693792 3.85380806,21.1198958 3.105,21.1198958 C2.35619194,21.1198958 1.73234648,21.693792 1.67,22.44 L1.67,26.7 L2.22,26.77 Z" id="Shape" fill="#FFBF00" fill-rule="nonzero"></path>
<path d="M10.44,21.44 C10.0306806,21.0291817 9.41486744,20.9039895 8.87763309,21.1223774 C8.34039875,21.3407654 7.98659306,21.8601116 7.98,22.44 L7.98,26.78 L10.86,26.78 L10.86,22.44 C10.8567492,22.0644466 10.7058854,21.7052469 10.44,21.44 Z M9.44,23.56 C8.98730492,23.5844809 8.56521866,23.3312015 8.37382614,22.9202257 C8.18243363,22.50925 8.26020534,22.0231853 8.57027452,21.6924449 C8.88034371,21.3617044 9.36038552,21.252768 9.78284191,21.4172756 C10.2052983,21.5817832 10.4852542,21.9866683 10.49,22.44 C10.4845121,23.0491272 9.98915197,23.5400247 9.38,23.54 L9.44,23.56 Z" id="Shape" fill="#FFBF00" fill-rule="nonzero"></path>
<path d="M17.44,21.44 C17.0306806,21.0291817 16.4148674,20.9039895 15.8776331,21.1223774 C15.3403987,21.3407654 14.9865931,21.8601116 14.98,22.44 L14.98,26.78 L17.86,26.78 L17.86,22.44 C17.8567492,22.0644466 17.7058854,21.7052469 17.44,21.44 Z M16.44,23.54 C15.9901408,23.5399817 15.5847763,23.2684441 15.4135718,22.8524366 C15.2423673,22.436429 15.3391815,21.9582239 15.658716,21.641568 C15.9782506,21.3249121 16.4573123,21.2324295 16.8717535,21.4073917 C17.2861947,21.5823538 17.5540528,21.9901591 17.55,22.44 C17.5445121,23.0491272 17.049152,23.5400247 16.44,23.54 Z" id="Shape" fill="#FFBF00" fill-rule="nonzero"></path>
<path d="M24.52,21.44 C24.1106806,21.0291817 23.4948674,20.9039895 22.9576331,21.1223774 C22.4203987,21.3407654 22.0665931,21.8601116 22.06,22.44 L22.06,26.78 L24.94,26.78 L24.94,22.44 C24.9367492,22.0644466 24.7858854,21.7052469 24.52,21.44 Z M23.52,23.56 C22.9069639,23.56 22.41,23.0630361 22.41,22.45 C22.41,21.8369639 22.9069639,21.34 23.52,21.34 C24.1330361,21.34 24.63,21.8369639 24.63,22.45 C24.63,23.0630361 24.1330361,23.56 23.52,23.56 Z" id="Shape" fill="#FFBF00" fill-rule="nonzero"></path>
<path d="M30.79,21.44 C30.3798042,21.0361719 29.7685264,20.915422 29.2355905,21.1329468 C28.7026547,21.3504717 28.3504601,21.8644757 28.34,22.44 L28.34,26.78 L30.66,26.78 L31.21,26.7 L31.21,22.44 C31.2067492,22.0644466 31.0558854,21.7052469 30.79,21.44 Z M29.79,23.56 C29.3402299,23.5680864 28.9300507,23.3039154 28.7513688,22.8910822 C28.5726868,22.478249 28.6608368,21.9983918 28.9745768,21.6760167 C29.2883169,21.3536417 29.7656044,21.2524988 30.1831357,21.4199088 C30.6006671,21.5873188 30.8758747,21.9901761 30.88,22.44 C30.8745121,23.0491272 30.379152,23.5400247 29.77,23.54 L29.79,23.56 Z" id="Shape" fill="#FFBF00" fill-rule="nonzero"></path>
<text id="0" fill="#414757" font-family="HelveticaNeue-CondensedBlack, Helvetica Neue" font-size="1.11" font-style="condensed" font-weight="700">
<tspan x="2.8" y="25.22">0</tspan>
</text>
<text id="A" fill="#414757" font-family="HelveticaNeue-CondensedBlack, Helvetica Neue" font-size="1.11" font-style="condensed" font-weight="700">
<tspan x="3.46" y="18.54">A</tspan>
</text>
<text id="B" fill="#414757" font-family="HelveticaNeue-CondensedBlack, Helvetica Neue" font-size="1.11" font-style="condensed" font-weight="700">
<tspan x="28.57" y="8.27">B</tspan>
</text>
<text id="1" fill="#414757" font-family="HelveticaNeue-CondensedBlack, Helvetica Neue" font-size="1.11" font-style="condensed" font-weight="700">
<tspan x="9.02" y="25.22">1</tspan>
</text>
<text id="2" fill="#414757" font-family="HelveticaNeue-CondensedBlack, Helvetica Neue" font-size="1.11" font-style="condensed" font-weight="700">
<tspan x="16.14" y="25.22">2</tspan>
</text>
<text id="3v" fill="#414757" font-family="HelveticaNeue-CondensedBlack, Helvetica Neue" font-size="1.11" font-style="condensed" font-weight="700">
<tspan x="22.96" y="25.22">3v</tspan>
</text>
<text id="GND" fill="#414757" font-family="HelveticaNeue-CondensedBlack, Helvetica Neue" font-size="1.11" font-style="condensed" font-weight="700">
<tspan x="28.53" y="25.22">GND</tspan>
</text>
<g id="Group" opacity="0.5" transform="translate(11.000000, 9.000000)" fill="#FF4E00" fill-rule="nonzero" stroke="#FF4E00" stroke-width="1.78">
<rect id="Rectangle-path" x="2.67" y="0" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" x="7.78" y="0" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" x="0.11" y="5" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" x="10.33" y="5" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" x="2.67" y="7.5" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" x="5.22" y="7.5" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" x="5.22" y="10" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" x="7.78" y="7.5" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" x="2.67" y="5" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" x="5.22" y="5" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" x="7.78" y="5" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" x="0.11" y="2.5" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" x="10.33" y="2.5" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" x="2.67" y="2.5" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" x="5.22" y="2.5" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" x="7.78" y="2.5" width="1" height="1" rx="0.04"></rect>
</g>
<rect id="Rectangle-path" stroke="#FF4E00" stroke-width="0.44" fill="#FF4E00" fill-rule="nonzero" x="13.67" y="9" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" stroke="#FF4E00" stroke-width="0.44" fill="#FF4E00" fill-rule="nonzero" x="18.78" y="9" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" stroke="#FF4E00" stroke-width="0.44" fill="#FF4E00" fill-rule="nonzero" x="11.11" y="14" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" stroke="#FF4E00" stroke-width="0.44" fill="#FF4E00" fill-rule="nonzero" x="21.33" y="14" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" stroke="#FF4E00" stroke-width="0.44" fill="#FF4E00" fill-rule="nonzero" x="13.67" y="16.5" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" stroke="#FF4E00" stroke-width="0.44" fill="#FF4E00" fill-rule="nonzero" x="16.22" y="16.5" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" stroke="#FF4E00" stroke-width="0.44" fill="#FF4E00" fill-rule="nonzero" x="16.22" y="19" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" stroke="#FF4E00" stroke-width="0.44" fill="#FF4E00" fill-rule="nonzero" x="18.78" y="16.5" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" stroke="#FF4E00" stroke-width="0.44" fill="#FF4E00" fill-rule="nonzero" x="13.67" y="14" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" stroke="#FF4E00" stroke-width="0.44" fill="#FF4E00" fill-rule="nonzero" x="16.22" y="14" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" stroke="#FF4E00" stroke-width="0.44" fill="#FF4E00" fill-rule="nonzero" x="18.78" y="14" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" stroke="#FF4E00" stroke-width="0.44" fill="#FF4E00" fill-rule="nonzero" x="11.11" y="11.5" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" stroke="#FF4E00" stroke-width="0.44" fill="#FF4E00" fill-rule="nonzero" x="21.33" y="11.5" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" stroke="#FF4E00" stroke-width="0.44" fill="#FF4E00" fill-rule="nonzero" x="13.67" y="11.5" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" stroke="#FF4E00" stroke-width="0.44" fill="#FF4E00" fill-rule="nonzero" x="16.22" y="11.5" width="1" height="1" rx="0.04"></rect>
<rect id="Rectangle-path" stroke="#FF4E00" stroke-width="0.44" fill="#FF4E00" fill-rule="nonzero" x="18.78" y="11.5" width="1" height="1" rx="0.04"></rect>
</g>
</g>
</svg>
\ No newline at end of file
<?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>music-block-icon</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="music-block-icon" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M32.179206,25.8739208 C32.6367303,28.1567449 30.5131455,29.9998773 27.4339971,29.9998773 C24.3647303,29.9998773 21.5108854,28.1567449 21.0622546,25.8739208 C20.6037421,23.5891115 22.7273269,21.7380389 25.8054872,21.7380389 C26.4517526,21.7380389 27.0881363,21.8224042 27.695863,21.9722765 C28.0328302,22.0576343 28.3322467,22.1519249 28.6326515,22.2740063 C29.5022441,22.2541557 28.5288932,19.9802643 26.7976133,10.0450167 C24.6641469,-2.25841293 29.8579865,8.17508593 35.5656764,7.29173224 C41.2743545,6.40738602 35.6417658,12.1124613 31.9163518,11.1358096 C28.1919261,10.1492325 27.2660076,3.98262977 32.179206,25.8739208 Z M15.1819287,31.8640908 C15.6303634,34.1479226 13.5067189,36 10.4387935,36 C7.37086801,36 4.51827022,34.1479226 4.05995811,31.8640908 C3.61152342,29.580259 5.73418015,27.7291742 8.81099529,27.7291742 C9.81256529,27.7291742 10.784503,27.9256968 11.6645808,28.2730854 C12.4873696,28.2175032 11.5154318,25.895955 9.80268788,16.0450074 C7.67015372,3.74248891 12.8617237,14.1740717 18.5669193,13.2917047 C24.2731026,12.3984198 18.6429754,18.1134582 14.9191895,17.1258821 C11.1963913,16.1392986 10.2708774,9.97364751 15.1819287,31.8640908 Z" id="music" fill="#575E75"></path>
</g>
</svg>
\ No newline at end of file
<?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>pen-block-icon</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="pen-block-icon" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="pen-icon" transform="translate(4.000000, 4.000000)" stroke="#575E75">
<path d="M4.7529,30.6025 L0.5019,32.3805 L2.2859,28.1445 C3.5039,25.2535 5.1929,22.7225 7.3159,20.6075 L27.0669,0.9295 C27.9119,0.0875 29.7169,0.5205 31.0979,1.8965 C32.4789,3.2725 32.9139,5.0705 32.0679,5.9125 L12.3179,25.5905 C10.1949,27.7055 7.6539,29.3885 4.7529,30.6025" id="Fill-1" fill="#FFFFFF"></path>
<path d="M25.4092,2.1113 C25.4092,2.1113 20.9602,-0.2677 17.2082,7.8823 C15.4742,11.6483 12.8582,9.4283 12.8582,9.4283" id="Stroke-5"></path>
<path d="M32.4209,4.8251 C32.4209,5.2881 32.2799,5.6981 31.9879,5.9891 L22.6529,15.2901 C22.9349,14.9991 23.0639,14.6221 23.0639,14.1691 C23.0639,13.2961 22.5559,12.2071 21.6569,11.3021 C20.2949,9.9441 18.5099,9.5021 17.6549,10.3111 L26.9899,1.0101 C27.8339,0.1691 29.6409,0.6001 31.0249,1.9691 C31.9229,2.8741 32.4209,3.9521 32.4209,4.8251" id="Fill-7" fill="#4C97FF"></path>
<path d="M6.5146,29.7739 C5.9416,30.0759 5.3576,30.3449 4.7506,30.6039 L0.4996,32.3819 L2.2856,28.1469 C2.5436,27.5429 2.8146,26.9609 3.1186,26.3899 C3.8096,26.5729 4.5676,27.0149 5.2276,27.6719 C5.8866,28.3299 6.3296,29.0839 6.5146,29.7739" id="Fill-11" fill="#4C97FF"></path>
<path d="M32.498,4.7485 C32.498,5.2115 32.357,5.6215 32.065,5.9125 L12.323,25.5925 C10.192,27.7045 7.65,29.3855 4.751,30.6035 L0.5,32.3815 L1.474,30.0645 L3.399,29.2565 C6.298,28.0385 8.84,26.3575 10.971,24.2455 L30.713,4.5655 C31.005,4.2745 31.145,3.8645 31.145,3.4015 C31.145,2.7545 30.875,2.0005 30.366,1.2785 C30.615,1.4505 30.864,1.6555 31.102,1.8925 C32,2.7975 32.498,3.8755 32.498,4.7485" id="Fill-15" fill="#575E75" opacity="0.15"></path>
<path d="M14.4502,8.831 C14.4502,9.33 14.0462,9.735 13.5462,9.735 C13.0472,9.735 12.6412,9.33 12.6412,8.831 C12.6412,8.331 13.0472,7.927 13.5462,7.927 C14.0462,7.927 14.4502,8.331 14.4502,8.831 Z" id="Stroke-22" fill="#575E75"></path>
</g>
</g>
</svg>
\ No newline at end of file
<?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>translation-block-icon</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="translation-block-icon" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Group" transform="translate(4.000000, 4.000000)">
<path d="M29.8647,32 C31.0357,32 31.9937,31.058 31.9937,29.904 L31.9937,8.52 C31.9937,7.366 31.0357,6.424 29.8647,6.424 L9.0907,6.424 L17.4277,32 L29.8647,32 Z" id="Fill-1" fill="#D9D9D9"></path>
<polygon id="Fill-3" fill="#4352B8" points="22.8998 25.9575 17.4658 31.9995 15.4888 25.9575"></polygon>
<path d="M29.9741,15.2837 L29.9741,14.0337 L23.9481,14.0337 L23.9481,12.0137 L21.9931,12.0137 L21.9931,14.0337 L18.1461,14.0337 L18.1461,15.2837 L25.8001,15.2837 C25.3901,16.7257 24.4871,18.0887 23.6051,19.1457 C22.0401,17.2967 22.0351,16.6937 22.0351,16.6937 L20.4091,16.6937 C20.4091,16.6937 20.4761,17.5947 22.6691,20.1657 C21.9571,20.8927 21.4151,21.3257 21.4151,21.3257 L21.9151,22.8867 C21.9151,22.8867 22.6691,22.2397 23.6151,21.2367 C24.5601,22.2617 25.7821,23.4957 27.3621,24.9677 L28.3871,23.9417 C26.6951,22.4067 25.4541,21.1877 24.5441,20.2107 C25.7651,18.7647 27.0061,16.9477 27.2691,15.2807 L29.9741,15.2807 L29.9741,15.2837 Z" id="Fill-5" fill="#617D8B"></path>
<path d="M2.1284,0 C0.9584,0 0.0004,0.958 0.0004,2.132 L0.0004,23.833 C0.0004,25.002 0.9584,25.961 2.1284,25.961 L22.9024,25.961 L14.5664,0 L2.1284,0 Z" id="Fill-7" fill="#4F8BF5"></path>
<path d="M13.5268,12.0942 L12.9718,12.0942 L11.1518,12.0942 L8.9788,12.0942 L8.9788,13.9792 L11.5968,13.9792 C11.3538,15.1772 10.3318,15.8642 8.9788,15.8642 C7.3818,15.8642 6.0928,14.5752 6.0928,12.9792 C6.0928,11.3822 7.3818,10.0942 8.9788,10.0942 C9.6678,10.0942 10.2868,10.3372 10.7768,10.7382 L12.1968,9.3182 C11.3318,8.5652 10.2218,8.0972 8.9788,8.0972 C6.2698,8.0972 4.0958,10.2702 4.0958,12.9792 C4.0958,15.6872 6.2728,17.8642 8.9788,17.8642 C11.4218,17.8642 13.6388,16.0882 13.6388,12.9822 C13.6388,12.6932 13.5938,12.3832 13.5268,12.0942" id="Fill-9" fill="#FFFFFF"></path>
<path d="M17.1264,6.4238 L29.8644,6.4238 C31.0354,6.4238 31.9934,7.3658 31.9934,8.5198 L31.9934,21.3478 L17.1264,6.4238 Z" id="Fill-11" fill="#FFFFFF" opacity="0.2"></path>
</g>
</g>
</svg>
\ No newline at end of file
<?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>videomotion-block-icon</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="videomotion-block-icon" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="video" transform="translate(4.000000, 4.000000)">
<rect id="Rectangle-path" fill="#FFFFFF" fill-rule="nonzero" x="0" y="0" width="32" height="32" rx="3.05"></rect>
<g id="Group" opacity="0.1" transform="translate(5.000000, 5.000000)" fill="#414757" fill-rule="nonzero">
<path d="M11,1.56090652 C16.2130674,1.56090652 20.4390935,5.78693262 20.4390935,11 C20.4390935,16.2130674 16.2130674,20.4390935 11,20.4390935 C5.78693262,20.4390935 1.56090652,16.2130674 1.56090652,11 C1.56090652,5.78693262 5.78693262,1.56090652 11,1.56090652 Z M11,0.5 C5.20101013,0.5 0.5,5.20101013 0.5,11 C0.5,16.7989899 5.20101013,21.5 11,21.5 C16.7989899,21.5 21.5,16.7989899 21.5,11 C21.494534,5.20327574 16.7967243,0.505466033 11,0.5 Z" id="Shape"></path>
</g>
<circle id="Oval" fill="#414757" fill-rule="nonzero" cx="16" cy="16" r="10"></circle>
<circle id="Oval" stroke="#FFFFFF" stroke-width="1.5" opacity="0.1" cx="16" cy="16" r="8"></circle>
<circle id="Oval" stroke="#FFFFFF" stroke-width="0.5" opacity="0.1" cx="16" cy="16" r="6.75"></circle>
<circle id="Oval" fill="#E6E7E8" fill-rule="nonzero" opacity="0.2" cx="16" cy="16" r="6"></circle>
<path d="M21.9886164,15.9943081 C22.0037945,16.2505067 22.0037945,16.5073791 21.9886164,16.7635776 C21.5676878,13.8095327 19.0381351,11.6148707 16.0542513,11.6148707 C13.0703675,11.6148707 10.5408148,13.8095327 10.1198862,16.7635776 C10.0645983,16.5097785 10.0245646,16.2528952 10,15.9943081 C10.0000001,12.6837431 12.6837433,10 15.9943082,10 C19.3048732,10 21.9886164,12.6837431 21.9886164,15.9943081 Z" id="Shape" fill="#E6E7E8" fill-rule="nonzero" opacity="0.2"></path>
<circle id="Oval" fill="#414757" fill-rule="nonzero" cx="16" cy="16" r="3"></circle>
<circle id="Oval" fill="#E6E7E8" fill-rule="nonzero" opacity="0.2" cx="16" cy="16" r="2"></circle>
<circle id="Oval" fill="#414757" fill-rule="nonzero" cx="16" cy="16" r="1"></circle>
<path d="M14.6084924,16.39 C14.4383483,16.1563806 14.3632869,15.8668582 14.3984924,15.58 C14.4617702,14.9563903 14.9548827,14.4632779 15.5784924,14.4 C15.8653505,14.3647946 16.154873,14.4398559 16.3884924,14.61 C16.4736591,14.6957849 16.4736591,14.8342151 16.3884924,14.92 C16.3519423,14.9613974 16.302714,14.9895279 16.2484924,15 C15.5681475,15.0107161 15.0192084,15.5596551 15.0084924,16.24 C14.9928881,16.3577302 14.8866619,16.441826 14.7684924,16.43 C14.7123527,16.4330323 14.6566004,16.4190942 14.6084924,16.39 Z" id="Shape" fill="#FFFFFF" fill-rule="nonzero" opacity="0.5"></path>
<path d="M17.13,16.25 C17.238165,16.5018964 17.1819645,16.7942753 16.9881199,16.9881199 C16.7942753,17.1819645 16.5018964,17.238165 16.25,17.13 C16.2288562,17.1129132 16.2165685,17.0871849 16.2165685,17.06 C16.2165685,17.0328151 16.2288562,17.0070868 16.25,16.99 C16.25,16.99 16.25,16.99 16.31,16.94 C16.6197545,16.8616201 16.8616201,16.6197545 16.94,16.31 C16.9586027,16.2556575 17.0131051,16.2221176 17.07,16.23 C17.091637,16.23 17.1126904,16.2370178 17.13,16.25 Z" id="Shape" fill="#FFFFFF" fill-rule="nonzero" opacity="0.5"></path>
<path d="M25.41,15.89 C25.4072923,16.4125891 25.3638271,16.9341709 25.28,17.45 C24.5137287,12.860866 20.5426683,9.49789668 15.89,9.49789668 C11.2373317,9.49789668 7.26627135,12.860866 6.5,17.45 C6.41617288,16.9341709 6.37270773,16.4125891 6.37,15.89 C6.69511761,10.8822361 10.8516935,6.98676576 15.87,6.98676576 C20.8883065,6.98676576 25.0448824,10.8822361 25.37,15.89 L25.41,15.89 Z" id="Shape" fill="#414757" fill-rule="nonzero" opacity="0.75"></path>
<path d="M11.511852,19.9821248 C11.1581427,19.58376 10.8566633,19.1379766 10.6181504,18.6555112 L18.6555112,10.6181504 C19.069995,10.8230557 19.4574058,11.0744331 19.8109361,11.3654751 L19.9697189,11.5242579 L11.511852,19.9821248 Z" id="Combined-Shape" fill="#FFFFFF" fill-rule="nonzero" opacity="0.1"></path>
<path d="M11.8063413,20.291067 L20.2775364,11.8198719 L20.5719033,12.1142388 C20.6530913,12.2096696 20.7313445,12.3076744 20.8065233,12.4081138 L12.4081138,20.8065233 C12.1962546,20.6479469 11.9952272,20.4756916 11.8063413,20.291067 Z" id="Combined-Shape" fill="#FFFFFF" fill-rule="nonzero" opacity="0.1"></path>
<rect id="Rectangle-path" stroke="#414757" stroke-width="2" opacity="0.1" x="0" y="0" width="32" height="32" rx="3.05"></rect>
<circle id="Oval" fill="#FF661A" fill-rule="nonzero" cx="4" cy="4" r="2"></circle>
<circle id="Oval" stroke="#231F20" stroke-width="0.71" opacity="0.1" stroke-linecap="round" stroke-linejoin="round" cx="4" cy="4" r="2"></circle>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
<title>wedo2-block-icon</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="wedo2-block-icon">
<g transform="translate(1.000000, 8.000000)">
<path d="M34.3129805,2.46697115 L31.0885415,2.46697115 L31.0885415,0.864527244 C31.0885415,0.641530449 31.2711268,0.460921474 31.4954195,0.460921474 L33.9070293,0.460921474 C34.131322,0.460921474 34.3129805,0.641530449 34.3129805,0.864527244 L34.3129805,2.46697115 Z" id="Stroke-3" stroke="#6F7893" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M29.4774341,2.46697115 L26.2529951,2.46697115 L26.2529951,0.864527244 C26.2529951,0.641530449 26.4355805,0.460921474 26.6598732,0.460921474 L29.0714829,0.460921474 C29.2957756,0.460921474 29.4774341,0.641530449 29.4774341,0.864527244 L29.4774341,2.46697115 Z" id="Stroke-7" stroke="#6F7893" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M24.6414244,2.46697115 L21.4179122,2.46697115 L21.4179122,0.864527244 C21.4179122,0.641530449 21.5995707,0.460921474 21.8238634,0.460921474 L24.2354732,0.460921474 C24.4597659,0.460921474 24.6414244,0.641530449 24.6414244,0.864527244 L24.6414244,2.46697115 Z" id="Stroke-11" stroke="#6F7893" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M19.805878,2.46697115 L16.5823659,2.46697115 L16.5823659,0.864527244 C16.5823659,0.641530449 16.7640244,0.460921474 16.9883171,0.460921474 L19.3999268,0.460921474 C19.6242195,0.460921474 19.805878,0.641530449 19.805878,0.864527244 L19.805878,2.46697115 Z" id="Stroke-15" stroke="#6F7893" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M14.9703317,2.46697115 L11.7458927,2.46697115 L11.7458927,0.864527244 C11.7458927,0.641530449 11.928478,0.460921474 12.1527707,0.460921474 L14.5643805,0.460921474 C14.7886732,0.460921474 14.9703317,0.641530449 14.9703317,0.864527244 L14.9703317,2.46697115 Z" id="Stroke-19" stroke="#6F7893" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M10.1347854,2.46697115 L6.91034634,2.46697115 L6.91034634,0.864527244 C6.91034634,0.641530449 7.09293171,0.460921474 7.31722439,0.460921474 L9.72883415,0.460921474 C9.95312683,0.460921474 10.1347854,0.641530449 10.1347854,0.864527244 L10.1347854,2.46697115 Z" id="Stroke-23" stroke="#6F7893" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M36.7304293,2.46697115 L5.29886829,2.46697115 C2.62867317,2.46697115 0.4636,4.61953526 0.4636,7.27522436 L0.4636,10.4801122 L37.5367707,10.4801122 L37.5367707,3.26865385 C37.5367707,2.82634615 37.1753073,2.46697115 36.7304293,2.46697115 Z" id="Stroke-27" stroke="#6F7893" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M37.1336927,22.9801883 L0.866863415,22.9801883 C0.643497561,22.9801883 0.463692683,22.8005008 0.463692683,22.579347 L0.463692683,8.23659856 L33.1733512,8.23659856 C33.9027659,8.23659856 34.602522,8.52409856 35.1187659,9.03828125 C35.6350098,9.55062099 36.3347659,9.83904247 37.0641805,9.83904247 L37.5368634,9.83904247 L37.5368634,22.579347 C37.5368634,22.8005008 37.3561317,22.9801883 37.1336927,22.9801883 Z" id="Stroke-31" stroke="#6F7893" fill="#E6E7E8" stroke-linecap="round" stroke-linejoin="round"></path>
<polyline id="Stroke-33" stroke="#6F7893" stroke-linecap="round" stroke-linejoin="round" points="33.8291756 8.23659856 34.3129805 5.67213542 37.5364927 5.67213542"></polyline>
<path d="M37.5364927,3.2683774 L37.5364927,22.5797155 C37.5364927,22.8008694 37.3566878,22.9805569 37.133322,22.9805569 L0.866492683,22.9805569 C0.644980488,22.9805569 0.463321951,22.7999479 0.463321951,22.5797155 L0.463321951,21.3771915 L34.7448829,21.3771915 C35.3964439,21.3771915 35.9247366,20.8528726 35.9247366,20.2041546 L35.9247366,2.46669471 L36.7301512,2.46669471 C37.1759561,2.46669471 37.5364927,2.82606971 37.5364927,3.2683774 Z" id="Stroke-37" stroke="#6F7893" fill="#6F7893" opacity="0.15" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M10.5375854,8.27889423 L30.6859268,8.27889423 C30.9083659,8.27889423 31.0890976,8.45858173 31.0890976,8.67973558 L31.0890976,15.2074599 C31.0890976,15.4286138 30.9083659,15.6083013 30.6859268,15.6083013 L10.5375854,15.6083013 C10.3151463,15.6083013 10.1344146,15.4286138 10.1344146,15.2074599 L10.1344146,8.67973558 C10.1344146,8.45858173 10.3151463,8.27889423 10.5375854,8.27889423" id="Fill-40" fill="#E6E7E8"></path>
<path d="M10.5375854,8.27889423 L30.6859268,8.27889423 C30.9083659,8.27889423 31.0890976,8.45858173 31.0890976,8.67973558 L31.0890976,15.2074599 C31.0890976,15.4286138 30.9083659,15.6083013 30.6859268,15.6083013 L10.5375854,15.6083013 C10.3151463,15.6083013 10.1344146,15.4286138 10.1344146,15.2074599 L10.1344146,8.67973558 C10.1344146,8.45858173 10.3151463,8.27889423 10.5375854,8.27889423 Z" id="Stroke-42" stroke="#6F7893" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M31.0889122,8.27889423 L31.0889122,15.2074599 C31.0889122,15.4286138 30.9091073,15.6083013 30.6857415,15.6083013 L10.5374,15.6083013 C10.3158878,15.6083013 10.1351561,15.4276923 10.1351561,15.2074599 L10.1351561,14.0058574 L28.2973024,14.0058574 C28.9488634,14.0058574 29.4771561,13.480617 29.4771561,12.8328205 L29.4771561,8.27889423 L31.0889122,8.27889423 Z" id="Stroke-46" stroke="#6F7893" fill="#6E7792" opacity="0.15" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M29.4774341,8.27889423 L26.2529951,8.27889423 L26.2529951,6.67552885 C26.2529951,6.45253205 26.4355805,6.27192308 26.6598732,6.27192308 L29.0714829,6.27192308 C29.2957756,6.27192308 29.4774341,6.45253205 29.4774341,6.67552885 L29.4774341,8.27889423 Z" id="Stroke-51" stroke="#6F7893" fill="#E6E7E8" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M14.9703317,8.27889423 L11.7458927,8.27889423 L11.7458927,6.67552885 C11.7458927,6.45253205 11.928478,6.27192308 12.1527707,6.27192308 L14.5643805,6.27192308 C14.7886732,6.27192308 14.9703317,6.45253205 14.9703317,6.67552885 L14.9703317,8.27889423 Z" id="Stroke-55" stroke="#6F7893" fill="#E6E7E8" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M24.6414244,8.27889423 L21.4179122,8.27889423 L21.4179122,6.67552885 C21.4179122,6.45253205 21.5995707,6.27192308 21.8238634,6.27192308 L24.2354732,6.27192308 C24.4597659,6.27192308 24.6414244,6.45253205 24.6414244,6.67552885 L24.6414244,8.27889423 Z" id="Stroke-59" stroke="#6F7893" fill="#E6E7E8" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M19.805878,8.27889423 L16.5823659,8.27889423 L16.5823659,6.67552885 C16.5823659,6.45253205 16.7640244,6.27192308 16.9883171,6.27192308 L19.3999268,6.27192308 C19.6242195,6.27192308 19.805878,6.45253205 19.805878,6.67552885 L19.805878,8.27889423 Z" id="Stroke-63" stroke="#6F7893" fill="#E6E7E8" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M17.5566488,11.9094111 C17.5566488,12.935012 16.719722,13.7671034 15.688161,13.7671034 C14.6566,13.7671034 13.8196732,12.935012 13.8196732,11.9094111 C13.8196732,10.8828886 14.6566,10.0517187 15.688161,10.0517187 C16.719722,10.0517187 17.5566488,10.8828886 17.5566488,11.9094111 Z" id="Stroke-65" stroke="#6F7893" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M22.4804293,11.9094111 C22.4804293,12.935012 21.6435024,13.7671034 20.6119415,13.7671034 C19.5803805,13.7671034 18.7434537,12.935012 18.7434537,11.9094111 C18.7434537,10.8828886 19.5803805,10.0517187 20.6119415,10.0517187 C21.6435024,10.0517187 22.4804293,10.8828886 22.4804293,11.9094111 Z" id="Stroke-67" stroke="#6F7893" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M27.4042098,11.9094111 C27.4042098,12.935012 26.5672829,13.7671034 25.535722,13.7671034 C24.504161,13.7671034 23.6672341,12.935012 23.6672341,11.9094111 C23.6672341,10.8828886 24.504161,10.0517187 25.535722,10.0517187 C26.5672829,10.0517187 27.4042098,10.8828886 27.4042098,11.9094111 Z" id="Stroke-69" stroke="#6F7893" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M17.5566488,11.922496 C17.5566488,12.948097 16.719722,13.7801883 15.688161,13.7801883 C14.6566,13.7801883 13.8196732,12.948097 13.8196732,11.922496 C13.8196732,10.896895 14.6566,10.0648037 15.688161,10.0648037 C16.719722,10.0648037 17.5566488,10.896895 17.5566488,11.922496" id="Fill-71" fill="#6F7893" opacity="0.5"></path>
<path d="M22.4804293,11.922496 C22.4804293,12.948097 21.6435024,13.7801883 20.6119415,13.7801883 C19.5803805,13.7801883 18.7434537,12.948097 18.7434537,11.922496 C18.7434537,10.896895 19.5803805,10.0648037 20.6119415,10.0648037 C21.6435024,10.0648037 22.4804293,10.896895 22.4804293,11.922496" id="Fill-73" fill="#6F7893" opacity="0.5"></path>
<path d="M27.4042098,11.922496 C27.4042098,12.948097 26.5672829,13.7801883 25.535722,13.7801883 C24.504161,13.7801883 23.6672341,12.948097 23.6672341,11.922496 C23.6672341,10.896895 24.504161,10.0648037 25.535722,10.0648037 C26.5672829,10.0648037 27.4042098,10.896895 27.4042098,11.922496" id="Fill-75" fill="#6F7893" opacity="0.5"></path>
</g>
</g>
</g>
</svg>
\ No newline at end of file
var webdriver = require('selenium-webdriver');
const webdriver = require('selenium-webdriver');
const bindAll = require('lodash.bindall');
const headless = process.env.SMOKE_HEADLESS || false;
const remote = process.env.SMOKE_REMOTE || false;
const {SAUCE_USERNAME, SAUCE_ACCESS_KEY} = process.env;
const {By, until} = webdriver;
const getDriver = function () {
const chromeCapabilities = webdriver.Capabilities.chrome();
let args = [];
if (headless) {
args.push('--headless');
args.push('window-size=1024,1680');
args.push('--no-sandbox');
class SeleniumHelper {
constructor () {
bindAll(this, [
'getDriver',
'getSauceDriver',
'buildDriver',
'clickXpath',
'findByXpath',
'clickText',
'findText',
'clickButton',
'findByCss',
'clickCss',
'getLogs'
]);
}
buildDriver (name) {
if (remote === 'true'){
this.driver = this.getSauceDriver(SAUCE_USERNAME, SAUCE_ACCESS_KEY, name);
} else {
this.driver = this.getDriver();
}
return this.driver;
}
chromeCapabilities.set('chromeOptions', {args});
const newDriver = new webdriver.Builder()
.forBrowser('chrome')
.withCapabilities(chromeCapabilities)
.build();
return newDriver;
};
const driver = getDriver();
getDriver () {
const chromeCapabilities = webdriver.Capabilities.chrome();
let args = [];
if (headless) {
args.push('--headless');
args.push('window-size=1024,1680');
args.push('--no-sandbox');
}
chromeCapabilities.set('chromeOptions', {args});
let driver = new webdriver.Builder()
.forBrowser('chrome')
.withCapabilities(chromeCapabilities)
.build();
return driver;
}
const {By, until} = webdriver;
getSauceDriver (username, accessKey, name) {
// Driver configs can be generated with the Sauce Platform Configurator
// https://wiki.saucelabs.com/display/DOCS/Platform+Configurator
let driverConfig = {
browserName: 'chrome',
platform: 'macOS 10.13',
version: '67.0'
};
var driver = new webdriver.Builder()
.withCapabilities({
browserName: driverConfig.browserName,
platform: driverConfig.platform,
version: driverConfig.version,
username: username,
accessKey: accessKey,
name: name
})
.usingServer(`http://${username}:${accessKey
}@ondemand.saucelabs.com:80/wd/hub`)
.build();
return driver;
}
const findByXpath = (xpath) => {
return driver.wait(until.elementLocated(By.xpath(xpath), 5 * 1000));
};
findByXpath (xpath) {
return this.driver.wait(until.elementLocated(By.xpath(xpath), 5 * 1000));
}
const clickXpath = (xpath) => {
return findByXpath(xpath).then(el => el.click());
};
clickXpath (xpath) {
return this.findByXpath(xpath).then(el => el.click());
}
const clickText = (text) => {
return clickXpath(`//*[contains(text(), '${text}')]`);
};
clickText (text) {
return this.clickXpath(`//*[contains(text(), '${text}')]`);
}
const findText = (text) => {
return driver.wait(until.elementLocated(By.xpath(`//*[contains(text(), '${text}')]`), 5 * 1000));
};
findText (text) {
return this.driver.wait(until.elementLocated(By.xpath(`//*[contains(text(), '${text}')]`), 5 * 1000));
}
const clickButton = (text) => {
return clickXpath(`//button[contains(text(), '${text}')]`);
};
clickButton (text) {
return this.clickXpath(`//button[contains(text(), '${text}')]`);
}
const findByCss = (css) => {
return driver.wait(until.elementLocated(By.css(css), 1000 * 5));
};
findByCss (css) {
return this.driver.wait(until.elementLocated(By.css(css), 1000 * 5));
}
const clickCss = (css) => {
return findByCss(css).then(el => el.click());
};
clickCss (css) {
return this.findByCss(css).then(el => el.click());
}
const getLogs = (whitelist) => {
return driver.manage()
.logs()
.get('browser')
.then((entries) => {
return entries.filter((entry) => {
const message = entry.message;
for (let i = 0; i < whitelist.length; i++) {
if (message.indexOf(whitelist[i]) !== -1) {
// eslint-disable-next-line no-console
// console.warn('Ignoring whitelisted error: ' + whitelist[i]);
return false;
} else if (entry.level !== 'SEVERE') {
// eslint-disable-next-line no-console
// console.warn('Ignoring non-SEVERE entry: ' + message);
return false;
getLogs (whitelist) {
return this.driver.manage()
.logs()
.get('browser')
.then((entries) => {
return entries.filter((entry) => {
const message = entry.message;
for (let i = 0; i < whitelist.length; i++) {
if (message.indexOf(whitelist[i]) !== -1) {
// eslint-disable-next-line no-console
// console.warn('Ignoring whitelisted error: ' + whitelist[i]);
return false;
} else if (entry.level !== 'SEVERE') {
// eslint-disable-next-line no-console
// console.warn('Ignoring non-SEVERE entry: ' + message);
return false;
}
return true;
}
return true;
}
return true;
});
});
});
};
}
}
module.exports = {
webdriver,
By,
until,
driver,
clickXpath,
findByXpath,
clickText,
findText,
clickButton,
findByCss,
clickCss,
getLogs,
getDriver
};
module.exports = SeleniumHelper;
const SeleniumHelper = require('../selenium-helpers.js');
const helper = new SeleniumHelper();
var tap = require('tap');
const test = tap.test;
const webdriver = require('selenium-webdriver');
const driver = helper.buildDriver('www-smoke test-login-failures');
const {
driver,
findByCss,
clickCss,
until
} = require('../selenium-helpers.js');
clickCss
} = helper;
var until = webdriver.until;
var username = process.env.SMOKE_USERNAME;
var password = process.env.SMOKE_PASSWORD;
var tap = require('tap');
const test = tap.test;
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
var url = rootUrl + '/users/' + username;
......
......@@ -5,21 +5,24 @@
*
*/
const SeleniumHelper = require('../selenium-helpers.js');
const helper = new SeleniumHelper();
var tap = require('tap');
const test = tap.test;
const driver = helper.buildDriver('www-smoke test-my-stuff');
const {
clickText,
findByXpath,
clickXpath,
clickButton,
driver
} = require('../selenium-helpers.js');
clickButton
} = helper;
var username = process.env.SMOKE_USERNAME;
var password = process.env.SMOKE_PASSWORD;
var tap = require('tap');
const test = tap.test;
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
var url = rootUrl + '/users/' + username;
......
......@@ -4,15 +4,13 @@
* Test cases: https://github.com/LLK/scratch-www/wiki/Most-Important-Workflows
*/
const tap = require('tap');
const SeleniumHelper = require('../selenium-helpers.js');
const helper = new SeleniumHelper();
const {
driver,
webdriver
} = require('../selenium-helpers.js');
const tap = require('tap');
// Selenium's promise driver will be deprecated, so we should not rely on it
webdriver.SELENIUM_PROMISE_MANAGER = 0;
const webdriver = require('selenium-webdriver');
const driver = helper.buildDriver('www-smoke test_footer_links');
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
......
......@@ -4,16 +4,13 @@
* Test cases: https://github.com/LLK/scratch-www/wiki/Most-Important-Workflows
*/
require('chromedriver');
const SeleniumHelper = require('../selenium-helpers.js');
const helper = new SeleniumHelper();
const {
driver,
webdriver
} = require('../selenium-helpers.js');
var tap = require('tap');
// Selenium's promise driver will be deprecated, so we should not rely on it
webdriver.SELENIUM_PROMISE_MANAGER = 0;
const webdriver = require('selenium-webdriver');
const driver = helper.buildDriver('www-smoke test_navbar_links');
// Set test url through environment variable
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
......
......@@ -5,16 +5,13 @@
* Test cases: https://github.com/LLK/scratch-www/wiki/Most-Important-Workflows
*/
require('chromedriver');
var tap = require('tap');
var seleniumWebdriver = require('selenium-webdriver');
const SeleniumHelper = require('../selenium-helpers.js');
const helper = new SeleniumHelper();
// Selenium's promise driver will be deprecated, so we should not rely on it
seleniumWebdriver.SELENIUM_PROMISE_MANAGER = 0;
var tap = require('tap');
const {
driver
} = require('../selenium-helpers.js');
const webdriver = require('selenium-webdriver');
const driver = helper.buildDriver('www-smoke test_project_rows');
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
......@@ -34,7 +31,7 @@ tap.beforeEach(function () {
// checks that the title of the first row is Featured Projects
tap.test('checkFeaturedProjectsRowTitleWhenSignedOut', function (t) {
var xPathLink = '//div[@class="box"]/div[@class="box-header"]/h4';
driver.findElement(seleniumWebdriver.By.xpath(xPathLink))
driver.findElement(webdriver.By.xpath(xPathLink))
.then(function (element) {
element.getText('h4')
.then(function (text) {
......@@ -51,8 +48,8 @@ tap.test('checkFeaturedProjectsRowLinkWhenSignedOut', function (t) {
var xPathLink = '//div[contains(@class, "thumbnail") ' +
'and contains(@class, "project") and contains(@class, "slick-slide") ' +
'and contains(@class, "slick-active")]/a[@class="thumbnail-image"]';
driver.wait(seleniumWebdriver.until
.elementLocated(seleniumWebdriver.By.xpath(xPathLink)))
driver.wait(webdriver.until
.elementLocated(webdriver.By.xpath(xPathLink)))
.then(function (element) {
element.getAttribute('href')
.then(function (url) {
......@@ -68,7 +65,7 @@ tap.test('checkFeaturedProjectsRowLinkWhenSignedOut', function (t) {
// checks that the title of the 2nd row is Featured Studios
tap.test('checkFeaturedStudiosRowWhenSignedOut', function (t) {
var xPathLink = '//div[@class="box"][2]/div[@class="box-header"]/h4';
driver.findElement(seleniumWebdriver.By.xpath(xPathLink))
driver.findElement(webdriver.By.xpath(xPathLink))
.then(function (element) {
element.getText('h4')
.then(function (text) {
......@@ -83,7 +80,7 @@ tap.test('checkFeaturedStudiosRowWhenSignedOut', function (t) {
tap.test('checkFeaturedStudiosRowLinkWhenSignedOut', function (t) {
var xPathLink = '//div[contains(@class, "thumbnail") and contains(@class, "gallery") ' +
'and contains(@class, "slick-slide") and contains(@class, "slick-active")]/a[@class="thumbnail-image"]';
driver.findElement(seleniumWebdriver.By.xpath(xPathLink))
driver.findElement(webdriver.By.xpath(xPathLink))
.then(function (element) {
element.getAttribute('href')
.then(function (url) {
......
......@@ -5,22 +5,25 @@
*
*/
const SeleniumHelper = require('../selenium-helpers.js');
const helper = new SeleniumHelper();
var tap = require('tap');
const test = tap.test;
const driver = helper.buildDriver('www-smoke test_sign_in_out_discuss');
const {
clickText,
findByXpath,
findText,
clickXpath,
clickButton,
driver
} = require('../selenium-helpers.js');
clickButton
} = helper;
var username = process.env.SMOKE_USERNAME;
var password = process.env.SMOKE_PASSWORD;
var tap = require('tap');
const test = tap.test;
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
var url = rootUrl + '/discuss';
......
......@@ -5,20 +5,24 @@
*
*/
const SeleniumHelper = require('../selenium-helpers.js');
const helper = new SeleniumHelper();
var tap = require('tap');
const test = tap.test;
const driver = helper.buildDriver('www-smoke test_sign_in_out_homepage');
const {
clickText,
findText,
findByXpath,
clickXpath,
driver
} = require('../selenium-helpers.js');
clickXpath
} = helper;
var username = process.env.SMOKE_USERNAME;
var password = process.env.SMOKE_PASSWORD;
var tap = require('tap');
const test = tap.test;
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
tap.plan(2);
......
......@@ -5,16 +5,20 @@
*
*/
const {
clickText,
findByXpath,
findByCss,
driver
} = require('../selenium-helpers.js');
const SeleniumHelper = require('../selenium-helpers.js');
const helper = new SeleniumHelper();
var tap = require('tap');
const test = tap.test;
const driver = helper.buildDriver('www-smoke test_statistics_page');
const {
clickText,
findByXpath,
findByCss
} = helper;
tap.plan(2);
tap.tearDown(function () {
......
......@@ -79,10 +79,7 @@ module.exports = {
{
test: /\.jsx?$/,
loader: 'babel-loader',
include: path.resolve(__dirname, 'src'),
options: {
presets: ['es2015', 'react']
}
include: [path.resolve(__dirname, 'src'), /node_modules[\\/]scratch-[^\\/]+[\\/]src/]
},
{
test: /\.scss$/,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment