Merge branch 'develop' into greenkeeper/react-dom-15.6.0

This commit is contained in:
Paul Kaplan authored and GitHub committed 2017-07-11 10:39:15 -04:00
commit be5941d7cd
94 files changed
+2118 -508

No files matched your search

+7 -5
View File
@@ -20,11 +20,11 @@
"url": "git+ssh://git@github.com/LLK/scratch-gui.git"
},
"peerDependencies": {
"react": "^15",
"react": "^15.6.0",
"react-dom": "^15.6.0"
},
"devDependencies": {
"autoprefixer": "7.1.1",
"autoprefixer": "7.1.2",
"babel-core": "^6.23.1",
"babel-eslint": "^7.1.1",
"babel-loader": "^7.0.0",
@@ -55,10 +55,11 @@
"react": "15.5.4",
"react-dom": "15.5.4",
"react-draggable": "2.2.6",
"react-modal": "1.7.7",
"react-modal": "2.2.1",
"react-intl": "2.3.0",
"react-redux": "5.0.5",
"react-style-proptype": "3.0.0",
"react-tabs": "1.0.0",
"react-tabs": "1.1.0",
"redux": "3.6.0",
"redux-throttle": "0.1.1",
"rimraf": "^2.6.1",
@@ -69,7 +70,8 @@
"scratch-vm": "latest",
"style-loader": "^0.18.0",
"svg-to-image": "1.1.3",
"svg-url-loader": "2.0.2",
"svg-url-loader": "2.1.0",
"wav-encoder": "1.1.0",
"webpack": "^2.4.1",
"webpack-dev-server": "^2.4.1",
"xhr": "2.4.0"
+1 -1
View File
@@ -6,7 +6,7 @@
flex-grow: 1;
border: 1px solid $ui-pane-border;
border-top-right-radius: $space;
background: $ui-pane-gray;
background: white;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 0.85rem;
}
+5 -5
View File
@@ -1,8 +1,8 @@
const React = require('react');
import React from 'react';
const Box = require('../box/box.jsx');
const Selector = require('./selector.jsx');
const styles = require('./asset-panel.css');
import Box from '../box/box.jsx';
import Selector from './selector.jsx';
import styles from './asset-panel.css';
const AssetPanel = props => (
<Box className={styles.wrapper}>
@@ -11,7 +11,7 @@ const AssetPanel = props => (
{...props}
/>
<Box className={styles.detailArea}>
{/* @todo editor area */}
{props.children}
</Box>
</Box>
);
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="28px" height="25px" viewBox="0 0 28 25" 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>add-sprite</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="Desktop---1280x720" transform="translate(-1144.000000, -669.000000)" stroke="#4C97FF">
<g id="Sprite-Area" transform="translate(792.000000, 460.000000)">
<g id="Sprite-List" transform="translate(0.000000, 78.000000)">
<g id="add-sprite" transform="translate(345.000000, 132.000000)">
<g transform="translate(8.000000, 0.000000)">
<path d="M21,16 C21,21 16.9,23.2 11.7,23.2 C6.5,23.2 2.5,21 2.5,16 C2.5,11 5.3,6.7 11.8,6.7 C18.3,6.7 21,11 21,16 Z M11.7,23 C20.1,23 21.6,17 19.5,14.9 C17.1,12.4 14,16 11.7,16 C9.4,16 6.3,12.4 3.9,14.9 C1.8,17 3.4,23 11.7,23 Z" id="Combined-Shape" fill="#4C97FF" fill-rule="nonzero"></path>
<path d="M3.7,11.1 L3,5.8 C3,5.4 3.4,5.2 3.7,5.3 L8.3,7.8" id="Shape" fill="#4C97FF" fill-rule="nonzero"></path>
<path d="M19.9,11.1 L20.6,5.7 C20.6,5.3 20.2,5.1 19.9,5.2 L15.3,7.7" id="Shape" fill="#4C97FF" fill-rule="nonzero"></path>
<path d="M7,17.75 L0,17.75" id="Shape" stroke-width="1.5" fill="#FFFFFF" fill-rule="nonzero"></path>
<path d="M2,22 L7,20" id="Shape" stroke-width="1.5" fill="#FFFFFF" fill-rule="nonzero"></path>
<path d="M16,17.75 L23,17.75" id="Shape" stroke-width="1.5" fill="#FFFFFF" fill-rule="nonzero"></path>
<path d="M22,22 L16,20" id="Shape" stroke-width="1.5" fill="#FFFFFF" fill-rule="nonzero"></path>
<g id="+" transform="translate(22.000000, 0.000000)" stroke-width="2">
<path d="M2,0 L2,4" id="Shape"></path>
<path d="M4,2 L0,2" id="Shape"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="20px" height="19px" viewBox="0 0 20 19" 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>sound 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="Desktop---1280x720" transform="translate(-51.000000, -666.000000)">
<g id="v1-Sound-Editor" transform="translate(-1.000000, 92.000000)">
<g id="Sounds-List">
<g id="add-sound" transform="translate(41.000000, 575.000000)">
<g id="sound-icon" transform="translate(11.000000, 0.000000)">
<path d="M8.75662105,3.8617617 L8.75662105,16.5654444 C8.75662105,16.8473164 8.60383377,17.1000293 8.35555444,17.2263858 C8.10727511,17.3430225 7.81124975,17.3138633 7.59161803,17.1389082 L3.93713725,14.1413445 C3.7251449,13.9673614 3.46158684,13.8730801 3.18943449,13.8730801 L1.90888609,13.8730801 C0.858473536,13.8730801 0,12.9983047 0,11.9301067 L0,8.08887079 C0,7.02067284 0.858473536,6.14589745 1.90888609,6.14589745 L3.25914369,6.14589745 C3.50551318,6.14589745 3.74710807,6.06813964 3.9485963,5.92234374 L7.62981485,3.26885838 C7.75395452,3.18138084 7.8971926,3.1327822 8.04043067,3.1327822 C8.15502113,3.1327822 8.26961159,3.16194138 8.37465285,3.22025974 C8.61338298,3.33689646 8.75662105,3.58960935 8.75662105,3.8617617" id="Fill-1" fill="#4C97FF"></path>
<path d="M10.9556526,7.161175 C11.1074421,7.161175 11.2592316,7.224925 11.3690211,7.349425 C12.0513368,8.130925 12.4271263,9.138175 12.4271263,10.185175 C12.4271263,11.265925 12.0292316,12.297175 11.3071263,13.088425 C11.0978632,13.316425 10.7493368,13.329925 10.5253368,13.117675 C10.3020737,12.906925 10.2895474,12.550675 10.4966,12.323425 C11.0293368,11.739925 11.3218632,10.980925 11.3218632,10.185175 C11.3218632,9.414175 11.0448105,8.672425 10.5422842,8.097925 C10.3396526,7.865425 10.3602842,7.509925 10.5887053,7.302925 C10.6940737,7.207675 10.8244947,7.161175 10.9556526,7.161175" id="Fill-4" fill="#4C97FF"></path>
<path d="M12.8186105,4.7512 C12.9652421,4.7512 13.1118737,4.81045 13.2216632,4.9282 C14.5406105,6.3577 15.2671368,8.22445 15.2671368,10.18495 C15.2671368,12.1792 14.5185053,14.0692 13.1582947,15.5062 C12.9453474,15.73045 12.5953474,15.7357 12.3765053,15.5212 C12.1576632,15.30595 12.1510316,14.9497 12.3625053,14.72545 C13.5230316,13.49995 14.1618737,11.8867 14.1618737,10.18495 C14.1618737,8.5117 13.5414526,6.9187 12.4155579,5.6992 C12.2070316,5.4727 12.2180842,5.11645 12.4398737,4.9042 C12.5467158,4.80145 12.6822947,4.7512 12.8186105,4.7512" id="Fill-6" fill="#4C97FF"></path>
<g id="+" transform="translate(15.000000, 0.000000)" stroke="#4C97FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M2,0 L2,4" id="Shape"></path>
<path d="M4,2 L0,2" id="Shape"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="18px" height="23px" viewBox="0 0 18 23" 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>record 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="Desktop---1280x720" transform="translate(-53.000000, -609.000000)">
<g id="v1-Sound-Editor" transform="translate(-1.000000, 92.000000)">
<g id="Sounds-List">
<g id="record-sound" transform="translate(34.000000, 518.000000)">
<g id="record-icon" transform="translate(20.000000, 0.000000)">
<path d="M10.9805,3.7742 L10.9805,10.8442 C10.9805,12.8742 9.2805,14.5142 7.2195,14.4042 C5.2995,14.3042 3.8495,12.5742 3.8495,10.6442 L3.8495,4.5742 C3.8495,2.5342 5.5495,0.9042 7.6095,1.0042 C9.5405,1.1142 10.9805,2.8442 10.9805,4.7742" id="Fill-1" fill="#4C97FF"></path>
<path d="M8.4199,17.9842 L8.4199,19.7242 L10.0799,19.7242 C10.6409,19.7242 11.0799,20.1742 11.0799,20.7242 C11.0799,21.2842 10.6409,21.7242 10.0799,21.7242 L4.7499,21.7242 C4.1999,21.7242 3.7499,21.2842 3.7499,20.7242 C3.7499,20.1742 4.1999,19.7242 4.7499,19.7242 L6.4199,19.7242 L6.4199,17.9842 C2.7999,17.5042 -0.0001,14.3942 -0.0001,10.6442 C-0.0001,10.0942 0.4499,9.6442 0.9999,9.6442 C1.5499,9.6442 1.9999,10.0942 1.9999,10.6442 C1.9999,13.6342 4.4299,16.0642 7.4199,16.0642 C10.3999,16.0642 12.8299,13.6342 12.8299,10.6442 C12.8299,10.0942 13.2799,9.6442 13.8299,9.6442 C14.3909,9.6442 14.8299,10.0942 14.8299,10.6442 C14.8299,14.3942 12.0399,17.5042 8.4199,17.9842" id="Fill-4" fill="#4C97FF"></path>
<g id="+" transform="translate(13.000000, 0.000000)" stroke="#4C97FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M2,0 L2,4" id="Shape"></path>
<path d="M4,2 L0,2" id="Shape"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

+34 -19
View File
@@ -1,27 +1,48 @@
/* Need to use a fixed height for the new container to make list scrollable */
$new-height: 60px;
@import "../../css/colors.css";
.wrapper {
width: 250px;
width: 150px;
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
background: $ui-background-blue;
}
.new-item {
background: white;
border-bottom: 1px solid #ddd;
height: $new-height;
.new-buttons {
display: flex;
flex-direction: column;
align-items: center;
padding-left: 1.75rem;
justify-content: space-around;
margin: 1.25rem 0;
color: $motion-primary;
}
.new-button {
display: flex;
flex-direction: column;
align-items: center;
font-size: 0.75rem;
cursor: pointer;
font-size: 0.85rem;
transition: 0.2s;
}
.new-button:hover {
transform: scale(1.2);
}
.new-button + .new-button {
margin-top: 1.25rem;
}
.new-button-icon + .new-button-label {
margin-top: 0.5rem;
}
.list-area {
position: absolute;
width: 100%;
top: 60px;
height: calc(100% - $new-height);
/* Must have some height (recalculated by flex-grow) in order to scroll */
height: 0;
flex-grow: 1;
overflow-y: scroll;
}
@@ -30,9 +51,3 @@ $new-height: 60px;
min-height: 5rem;
margin: 1rem auto;
}
.delete-button {
position: absolute;
top: 2px;
right: 2px;
}
+34 -16
View File
@@ -1,29 +1,23 @@
const PropTypes = require('prop-types');
const React = require('react');
import PropTypes from 'prop-types';
import React from 'react';
import {FormattedMessage} from 'react-intl';
const SpriteSelectorItem = require('../../containers/sprite-selector-item.jsx');
import SpriteSelectorItem from '../../containers/sprite-selector-item.jsx';
const Box = require('../box/box.jsx');
const styles = require('./selector.css');
import Box from '../box/box.jsx';
import styles from './selector.css';
const Selector = props => {
const {
buttons,
items,
newText,
selectedItemIndex,
onDeleteClick,
onItemClick,
onNewClick
onItemClick
} = props;
return (
<Box className={styles.wrapper}>
<Box
className={styles.newItem}
onClick={onNewClick}
>
{newText}
</Box>
<Box className={styles.listArea}>
{items.map((item, index) => (
<SpriteSelectorItem
@@ -39,19 +33,43 @@ const Selector = props => {
/>
))}
</Box>
<Box className={styles.newButtons}>
{buttons.map(({message, img, onClick}, index) => (
<Box
className={styles.newButton}
key={index}
onClick={onClick}
>
<img
className={styles.newButtonIcon}
src={img}
/>
<Box className={styles.newButtonLabel}>
<FormattedMessage {...message} />
</Box>
</Box>
))}
</Box>
</Box>
);
};
Selector.propTypes = {
buttons: PropTypes.arrayOf(PropTypes.shape({
message: PropTypes.shape({
id: PropTypes.string.isRequired,
defaultMessage: PropTypes.string,
description: PropTypes.string
}),
img: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired
})),
items: PropTypes.arrayOf(PropTypes.shape({
url: PropTypes.string,
name: PropTypes.string.isRequired
})),
newText: PropTypes.string.isRequired,
onDeleteClick: PropTypes.func.isRequired,
onItemClick: PropTypes.func.isRequired,
onNewClick: PropTypes.func,
selectedItemIndex: PropTypes.number.isRequired
};
@@ -0,0 +1,103 @@
@import "../../css/colors.css";
$border-radius: 4px;
$trim-handle-width: 12px;
$trim-handle-height: 14px;
$stripe-size: 10px;
$hover-scale: 2;
.absolute {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.trim-background {
cursor: pointer;
}
.trim-background-mask {
border: 1px solid $red-tertiary;
opacity: 0.5;
background: repeating-linear-gradient(
45deg,
$red-primary,
$red-primary $stripe-size,
$red-tertiary $stripe-size,
$red-tertiary calc(2 * $stripe-size)
);
}
.start-trim-background .trim-background-mask {
border-top-left-radius: $border-radius;
border-bottom-left-radius: $border-radius;
}
.end-trim-background .trim-background-mask {
border-top-right-radius: $border-radius;
border-bottom-right-radius: $border-radius;
}
.trim-line {
position: absolute;
top: 0;
width: 0px;
height: 100%;
border: 1px solid $red-tertiary;
}
.playhead {
border: 1px solid $motion-primary;
}
.start-trim-line {
right: 0;
}
.end-trim-line {
left: 0;
}
.trim-handle {
position: absolute;
left: calc(-$trim-handle-width / 2);
width: $trim-handle-width;
height: $trim-handle-height;
}
.trim-handle img {
position: absolute;
width: $trim-handle-width;
height: $trim-handle-height;
/* Make sure image dragging isn't triggered */
user-select: none;
user-drag: none;
-webkit-user-drag: none; /* Autoprefixer doesn't seem to work for this */
transition: 0.2s;
}
.top-trim-handle {
top: -$trim-handle-height;
}
.bottom-trim-handle {
bottom: -$trim-handle-height;
}
.top-trim-handle img {
transform: rotate(180deg);
}
/* Increase handle size when anywhere on draggable area is hovered */
.trim-background:hover img {
transform: scale($hover-scale);
}
.trim-background:hover .top-trim-handle img {
transform: rotate(180deg) scale($hover-scale);
}
@@ -0,0 +1,70 @@
import PropTypes from 'prop-types';
import React from 'react';
import classNames from 'classnames';
import Box from '../box/box.jsx';
import styles from './audio-trimmer.css';
import handleIcon from './icon--handle.svg';
const AudioTrimmer = props => (
<div
className={styles.absolute}
ref={props.containerRef}
>
<Box
className={classNames(styles.absolute, styles.trimBackground, styles.startTrimBackground)}
style={{
width: `${100 * props.trimStart}%`
}}
onMouseDown={props.onTrimStartMouseDown}
>
<Box className={classNames(styles.absolute, styles.trimBackgroundMask)} />
<Box className={classNames(styles.trimLine, styles.startTrimLine)}>
<Box className={classNames(styles.trimHandle, styles.topTrimHandle, styles.startTrimHandle)}>
<img src={handleIcon} />
</Box>
<Box className={classNames(styles.trimHandle, styles.bottomTrimHandle, styles.startTrimHandle)}>
<img src={handleIcon} />
</Box>
</Box>
</Box>
{props.playhead ? (
<Box
className={classNames(styles.trimLine, styles.playhead)}
style={{
left: `${100 * props.playhead}%`
}}
/>
) : null}
<Box
className={classNames(styles.absolute, styles.trimBackground, styles.endTrimBackground)}
style={{
left: `${100 * props.trimEnd}%`,
width: `${100 - 100 * props.trimEnd}%`
}}
onMouseDown={props.onTrimEndMouseDown}
>
<Box className={classNames(styles.absolute, styles.trimBackgroundMask)} />
<Box className={classNames(styles.trimLine, styles.endTrimLine)}>
<Box className={classNames(styles.trimHandle, styles.topTrimHandle, styles.endTrimHandle)}>
<img src={handleIcon} />
</Box>
<Box className={classNames(styles.trimHandle, styles.bottomTrimHandle, styles.endTrimHandle)}>
<img src={handleIcon} />
</Box>
</Box>
</Box>
</div>
);
AudioTrimmer.propTypes = {
containerRef: PropTypes.func,
onTrimEndMouseDown: PropTypes.func.isRequired,
onTrimStartMouseDown: PropTypes.func.isRequired,
playhead: PropTypes.number,
trimEnd: PropTypes.number.isRequired,
trimStart: PropTypes.number.isRequired
};
module.exports = AudioTrimmer;
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="8px" height="10px" viewBox="0 0 8 10" 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>handle</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="Desktop---1280x720" transform="translate(-461.000000, -329.000000)" stroke="#E64D00" fill="#FF661A">
<g id="Step-5---Confirming-Trim" transform="translate(0.000000, 42.000000)">
<g id="Sound-Sketches" transform="translate(427.000000, 167.000000)">
<g id="start-playhead" transform="translate(35.000000, 0.000000)">
<path d="M0,122.213854 C0,121.658366 0.45097518,121.208054 0.990777969,121.208054 L5.00922203,121.208054 C5.55641359,121.208054 6,121.659838 6,122.213854 L6,126.298963 L3,129.208054 L0,126.298963 L0,122.213854 Z" id="handle" transform="translate(3.000000, 125.208054) scale(1, -1) translate(-3.000000, -125.208054) "></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+4 -4
View File
@@ -1,7 +1,7 @@
const PropTypes = require('prop-types');
const React = require('react');
const Box = require('../box/box.jsx');
const styles = require('./blocks.css');
import PropTypes from 'prop-types';
import React from 'react';
import Box from '../box/box.jsx';
import styles from './blocks.css';
const BlocksComponent = props => {
const {
+24 -5
View File
@@ -1,8 +1,8 @@
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
const stylePropType = require('react-style-proptype');
const styles = require('./box.css');
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import stylePropType from 'react-style-proptype';
import styles from './box.css';
const getRandomColor = (function () {
// In "DEBUG" mode this is used to output a random background color for each
@@ -75,40 +75,59 @@ const Box = props => {
}, children);
};
Box.propTypes = {
/** Defines how the browser distributes space between and around content items vertically within this box. */
alignContent: PropTypes.oneOf([
'flex-start', 'flex-end', 'center', 'space-between', 'space-around', 'stretch'
]),
/** Defines how the browser distributes space between and around flex items horizontally within this box. */
alignItems: PropTypes.oneOf([
'flex-start', 'flex-end', 'center', 'baseline', 'stretch'
]),
/** Specifies how this box should be aligned inside of its container (requires the container to be flexable). */
alignSelf: PropTypes.oneOf([
'auto', 'flex-start', 'flex-end', 'center', 'baseline', 'stretch'
]),
/** Specifies the initial length of this box */
basis: PropTypes.oneOfType([
PropTypes.number,
PropTypes.oneOf(['auto'])
]),
/** Specifies the the HTML nodes which will be child elements of this box. */
children: PropTypes.node,
/** Specifies the class name that will be set on this box */
className: PropTypes.string,
/**
* A callback function whose first parameter is the underlying dom elements.
* This call back will be executed immediately after the component is mounted or unmounted
*/
componentRef: PropTypes.func,
/** https://developer.mozilla.org/en-US/docs/Web/CSS/flex-direction */
direction: PropTypes.oneOf([
'row', 'row-reverse', 'column', 'column-reverse'
]),
/** Specifies the type of HTML element of this box. Defaults to div. */
element: PropTypes.string,
/** Specifies the flex grow factor of a flex item. */
grow: PropTypes.number,
/** The height in pixels (if specified as a number) or a string if different units are required. */
height: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
]),
/** https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content */
justifyContent: PropTypes.oneOf([
'flex-start', 'flex-end', 'center', 'space-between', 'space-around'
]),
/** Specifies the flex shrink factor of a flex item. */
shrink: PropTypes.number,
/** An object whose keys are css property names and whose values correspond the the css property. */
style: stylePropType,
/** The width in pixels (if specified as a number) or a string if different units are required. */
width: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
]),
/** How whitespace should wrap within this block. */
wrap: PropTypes.oneOf([
'nowrap', 'wrap', 'wrap-reverse'
])
@@ -0,0 +1,52 @@
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
class BufferedInput extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleChange',
'handleKeyPress',
'handleFlush'
]);
this.state = {
value: null
};
}
handleKeyPress (e) {
if (e.key === 'Enter') {
this.handleFlush();
e.target.blur();
}
}
handleFlush () {
const isNumeric = typeof this.props.value === 'number';
const validatesNumeric = isNumeric ? !isNaN(this.state.value) : true;
if (this.state.value !== null && validatesNumeric) {
this.props.onSubmit(isNumeric ? Number(this.state.value) : this.state.value);
}
this.setState({value: null});
}
handleChange (e) {
this.setState({value: e.target.value});
}
render () {
const bufferedValue = this.state.value === null ? this.props.value : this.state.value;
return (
<input
{...this.props}
value={bufferedValue}
onBlur={this.handleFlush}
onChange={this.handleChange}
onKeyPress={this.handleKeyPress}
/>
);
}
}
BufferedInput.propTypes = {
onSubmit: PropTypes.func.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
};
module.exports = BufferedInput;
+4 -4
View File
@@ -1,8 +1,8 @@
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
const styles = require('./button.css');
import styles from './button.css';
const ButtonComponent = ({
className,
+1 -1
View File
@@ -25,7 +25,7 @@
width: 1rem;
height: 1rem;
color: #FFF;
background-color: $blue;
background-color: $motion-primary;
}
.large {
+5 -5
View File
@@ -1,9 +1,9 @@
const PropTypes = require('prop-types');
const React = require('react');
const classNames = require('classnames');
import PropTypes from 'prop-types';
import React from 'react';
import classNames from 'classnames';
const styles = require('./close-button.css');
const closeIcon = require('./icon--close.svg');
import styles from './close-button.css';
import closeIcon from './icon--close.svg';
const CloseButton = props => (
<div
@@ -1,7 +1,7 @@
const PropTypes = require('prop-types');
const React = require('react');
const svgToImage = require('svg-to-image');
const xhr = require('xhr');
import PropTypes from 'prop-types';
import React from 'react';
import svgToImage from 'svg-to-image';
import xhr from 'xhr';
/**
* @fileoverview
+6 -6
View File
@@ -1,10 +1,10 @@
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
const filterIcon = require('./icon--filter.svg');
const xIcon = require('./icon--x.svg');
const styles = require('./filter.css');
import filterIcon from './icon--filter.svg';
import xIcon from './icon--x.svg';
import styles from './filter.css';
const FilterComponent = props => {
const {
+5 -5
View File
@@ -1,9 +1,9 @@
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
const greenFlagIcon = require('./icon--green-flag.svg');
const styles = require('./green-flag.css');
import greenFlagIcon from './icon--green-flag.svg';
import styles from './green-flag.css';
const GreenFlagComponent = function (props) {
const {
+2 -1
View File
@@ -1,4 +1,5 @@
@import "../../css/units.css";
@import "../../css/colors.css";
.page-wrapper {
height: 100%;
@@ -6,7 +7,7 @@
.body-wrapper {
height: calc(100% - $menu-bar-height);
background-color: #e8edf1;
background-color: $ui-background-blue;
}
.flex-wrapper {
+18 -18
View File
@@ -1,22 +1,22 @@
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
const {Tab, Tabs, TabList, TabPanel} = require('react-tabs');
const tabStyles = require('react-tabs/style/react-tabs.css');
const VM = require('scratch-vm');
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
import tabStyles from 'react-tabs/style/react-tabs.css';
import VM from 'scratch-vm';
const Blocks = require('../../containers/blocks.jsx');
const CostumeTab = require('../../containers/costume-tab.jsx');
const GreenFlag = require('../../containers/green-flag.jsx');
const TargetPane = require('../../containers/target-pane.jsx');
const SoundTab = require('../../containers/sound-tab.jsx');
const Stage = require('../../containers/stage.jsx');
const StopAll = require('../../containers/stop-all.jsx');
import Blocks from '../../containers/blocks.jsx';
import CostumeTab from '../../containers/costume-tab.jsx';
import GreenFlag from '../../containers/green-flag.jsx';
import TargetPane from '../../containers/target-pane.jsx';
import SoundTab from '../../containers/sound-tab.jsx';
import Stage from '../../containers/stage.jsx';
import StopAll from '../../containers/stop-all.jsx';
const Box = require('../box/box.jsx');
const MenuBar = require('../menu-bar/menu-bar.jsx');
import Box from '../box/box.jsx';
import MenuBar from '../menu-bar/menu-bar.jsx';
const styles = require('./gui.css');
import styles from './gui.css';
const GUIComponent = props => {
@@ -62,7 +62,7 @@ const GUIComponent = props => {
onSelect={onTabSelect}
>
<TabList className={tabClassNames.tabList}>
<Tab className={tabClassNames.tab}>Scripts</Tab>
<Tab className={tabClassNames.tab}>Blocks</Tab>
<Tab className={tabClassNames.tab}>Costumes</Tab>
<Tab className={tabClassNames.tab}>Sounds</Tab>
</TabList>
@@ -70,7 +70,7 @@ const GUIComponent = props => {
<Box className={styles.blocksWrapper}>
<Blocks
grow={1}
isVisible={tabIndex === 0} // Scripts tab
isVisible={tabIndex === 0} // Blocks tab
options={{
media: `${basePath}static/blocks-media/`
}}
+5 -5
View File
@@ -1,9 +1,9 @@
const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types');
const React = require('react');
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
const Box = require('../box/box.jsx');
const styles = require('./library-item.css');
import Box from '../box/box.jsx';
import styles from './library-item.css';
class LibraryItem extends React.PureComponent {
constructor (props) {
+17
View File
@@ -1,6 +1,23 @@
@import "../../css/units.css";
@import "../../css/colors.css";
.modal-content {
position: absolute;
display: flex;
height: 100%;
width: 100%;
overflow-y: auto;
-webkit-overflow-scrolling: 'touch';
user-select: none;
/* Default modal resets */
margin: 0;
border: none;
border-radius: 0;
}
.library-scroll-grid {
display: flex;
justify-content: center;
+7 -6
View File
@@ -1,11 +1,11 @@
const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types');
const React = require('react');
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
const LibraryItem = require('../library-item/library-item.jsx');
const ModalComponent = require('../modal/modal.jsx');
import LibraryItem from '../library-item/library-item.jsx';
import ModalComponent from '../modal/modal.jsx';
const styles = require('./library.css');
import styles from './library.css';
class LibraryComponent extends React.Component {
constructor (props) {
@@ -46,6 +46,7 @@ class LibraryComponent extends React.Component {
if (!this.props.visible) return null;
return (
<ModalComponent
className={styles.modalContent}
contentLabel={this.props.title}
filterQuery={this.state.filterQuery}
visible={this.props.visible}
+4 -4
View File
@@ -1,9 +1,9 @@
const PropTypes = require('prop-types');
const React = require('react');
import PropTypes from 'prop-types';
import React from 'react';
const ButtonComponent = require('../button/button.jsx');
import ButtonComponent from '../button/button.jsx';
const styles = require('./load-button.css');
import styles from './load-button.css';
const LoadButtonComponent = ({
inputRef,
+7 -7
View File
@@ -1,12 +1,12 @@
const classNames = require('classnames');
const React = require('react');
import classNames from 'classnames';
import React from 'react';
const Box = require('../box/box.jsx');
const LoadButton = require('../../containers/load-button.jsx');
const SaveButton = require('../../containers/save-button.jsx');
import Box from '../box/box.jsx';
import LoadButton from '../../containers/load-button.jsx';
import SaveButton from '../../containers/save-button.jsx';
const styles = require('./menu-bar.css');
const scratchLogo = require('./scratch-logo.svg');
import styles from './menu-bar.css';
import scratchLogo from './scratch-logo.svg';
const MenuBar = function MenuBar () {
return (
+14
View File
@@ -0,0 +1,14 @@
.green {
fill: rgb(171, 220, 170);
stroke: rgb(174, 211, 168);
}
.yellow {
fill: rgb(251, 219, 130);
stroke: rgb(239, 212, 134);
}
.red {
fill: rgb(251, 194, 142);
stroke: rgb(235, 189, 142);
}
+62
View File
@@ -0,0 +1,62 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './meter.css';
const Meter = props => {
const {
level,
width,
height
} = props;
const nGreen = 11;
const nYellow = 5;
const nRed = 3;
const nBars = nGreen + nYellow + nRed;
const barSpacing = 2.5;
const barRounding = 3;
const barHeight = (height - barSpacing * (nBars + 1)) / nBars;
const nBarsToMask = nBars - Math.floor(level * nBars);
return (
<svg
className={styles.container}
height={height}
width={width}
>
{Array(nBars).fill(0)
.map((value, index) => (
<rect
className={index < nGreen ? styles.green :
(index < nGreen + nYellow ? styles.yellow : styles.red)}
height={barHeight}
key={index}
rx={barRounding}
ry={barRounding}
width={width - 2}
x={1}
y={height - (barSpacing + barHeight) * (index + 1)}
/>
))}
<rect
fill="white"
height={nBarsToMask * (barHeight + barSpacing) + barSpacing / 2}
opacity="0.75"
width={width}
x={0}
y={0}
/>
</svg>
);
};
Meter.propTypes = {
height: PropTypes.number,
level: PropTypes.number,
width: PropTypes.number
};
module.exports = Meter;
+10 -10
View File
@@ -11,16 +11,17 @@
background-color: rgba(0, 0, 0, .75);
}
/* @todo: extract to type: full ? */
.full-modal-content {
position: absolute;
.modal-content {
margin: 100px auto;
outline: none;
overflow-y: auto;
-webkit-overflow-scrolling: 'touch';
border: 3px solid rgb(126, 133, 151);
padding: 0;
border-radius: $space;
user-select: none;
height: 100%;
width: 100%;
display: flex;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #5d647a;
overflow: hidden;
}
/*
@@ -41,7 +42,7 @@ $sides: 20rem;
box-sizing: border-box;
width: 100%;
background-color: $blue;
background-color: $motion-primary;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 1rem;
@@ -76,4 +77,3 @@ $sides: 20rem;
flex-basis: $sides;
justify-content: flex-end;
}
+17 -14
View File
@@ -1,19 +1,19 @@
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
const ReactModal = require('react-modal');
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import ReactModal from 'react-modal';
const Box = require('../box/box.jsx');
const CloseButton = require('../close-button/close-button.jsx');
const Filter = require('../filter/filter.jsx');
import Box from '../box/box.jsx';
import CloseButton from '../close-button/close-button.jsx';
import Filter from '../filter/filter.jsx';
const styles = require('./modal.css');
import styles from './modal.css';
class ModalComponent extends React.Component {
render () {
return (
<ReactModal
className={styles.fullModalContent}
className={classNames(styles.modalContent, this.props.className)}
contentLabel={this.props.contentLabel}
isOpen={this.props.visible}
overlayClassName={styles.modalOverlay}
@@ -26,11 +26,13 @@ class ModalComponent extends React.Component {
>
<div className={styles.header}>
<div className={classNames(styles.headerItem, styles.headerItemFilter)}>
<Filter
filterQuery={this.props.filterQuery}
onChange={this.props.onFilterChange}
onClear={this.props.onFilterClear}
/>
{this.props.onFilterChange ? (
<Filter
filterQuery={this.props.filterQuery}
onChange={this.props.onFilterChange}
onClear={this.props.onFilterClear}
/>
) : null}
</div>
<div
className={classNames(
@@ -61,6 +63,7 @@ class ModalComponent extends React.Component {
ModalComponent.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
contentLabel: PropTypes.string.isRequired,
filterQuery: PropTypes.string,
onFilterChange: PropTypes.func,
+6 -6
View File
@@ -1,11 +1,11 @@
const React = require('react');
const Box = require('../box/box.jsx');
const Monitor = require('../../containers/monitor.jsx');
const PropTypes = require('prop-types');
const {OrderedMap} = require('immutable');
import React from 'react';
import Box from '../box/box.jsx';
import Monitor from '../../containers/monitor.jsx';
import PropTypes from 'prop-types';
import {OrderedMap} from 'immutable';
const styles = require('./monitor-list.css');
import styles from './monitor-list.css';
const MonitorList = props => (
<Box
+5 -5
View File
@@ -1,8 +1,8 @@
const React = require('react');
const PropTypes = require('prop-types');
const Draggable = require('react-draggable');
const Box = require('../box/box.jsx');
const styles = require('./monitor.css');
import React from 'react';
import PropTypes from 'prop-types';
import Draggable from 'react-draggable';
import Box from '../box/box.jsx';
import styles from './monitor.css';
const categories = {
data: '#FF8C1A',
+2 -32
View File
@@ -1,38 +1,8 @@
@import "../../css/colors.css";
@import "../../css/units.css";
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
background-color: rgba(94, 102, 125, .95);
}
.modal-content {
width: 360px;
margin: 100px auto;
outline: none;
border: 3px solid rgb(126, 133, 151);
padding: 0;
border-radius: $space;
user-select: none;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #5d647a;
overflow: hidden;
}
.header {
font-size: 1.25rem;
height: 3.25rem;
background-color: $blue;
font-weight: 500;
color: #fff;
text-align: center;
line-height: 3.25rem;
}
.body {
@@ -71,8 +41,8 @@
}
.button-row button.ok-button {
background: $blue;
border: $blue;
background: $motion-primary;
border: $motion-primary;
color: white;
}
+8 -12
View File
@@ -1,21 +1,17 @@
const PropTypes = require('prop-types');
const React = require('react');
const ReactModal = require('react-modal');
const Box = require('../box/box.jsx');
import PropTypes from 'prop-types';
import React from 'react';
import Modal from '../modal/modal.jsx';
import Box from '../box/box.jsx';
const styles = require('./prompt.css');
import styles from './prompt.css';
const PromptComponent = props => (
<ReactModal
isOpen
<Modal
visible
className={styles.modalContent}
contentLabel={props.title}
overlayClassName={styles.modalOverlay}
onRequestClose={props.onCancel}
>
<Box className={styles.header}>
{props.title}
</Box>
<Box className={styles.body}>
<Box className={styles.label}>
{props.label}
@@ -44,7 +40,7 @@ const PromptComponent = props => (
</button>
</Box>
</Box>
</ReactModal>
</Modal>
);
PromptComponent.propTypes = {
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="18px" height="8px" viewBox="0 0 18 8" 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>re-record</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Desktop---1280x720" transform="translate(-446.000000, -454.000000)" fill="#4C97FF">
<g id="Step-4---Playing-Trim" transform="translate(0.000000, 42.000000)">
<g id="Re-record" transform="translate(434.000000, 400.000000)">
<path d="M23.6951773,18.0886272 L20.5085204,21.2806841 C20.2691162,21.5146883 19.9451104,21.6466907 19.6085044,21.6466907 C19.2730984,21.6466907 18.9490926,21.5146883 18.7084883,21.2806841 L15.5230315,18.0886272 C15.157025,17.7226207 15.049023,17.1766109 15.2470266,16.7026025 C15.4450301,16.228594 15.9010382,15.9225886 16.4170474,15.9225886 L17.5630679,15.9225886 C17.5330673,15.6465836 17.455066,15.3345781 17.3230636,15.0045722 C17.2816629,14.9085705 17.2390621,14.8125688 17.1910612,14.716567 C17.1250601,14.6085651 17.1316602,14.5785646 17.0410586,14.4465622 C16.897056,14.2305584 16.7650536,14.0685555 16.6084509,13.8825522 C16.2910452,13.5405461 15.9070383,13.2465408 15.4990311,13.030537 C15.0850237,12.8145331 14.653016,12.6825308 14.2690091,12.6105295 C13.8910024,12.5445283 13.5429962,12.5385282 13.3389925,12.5385282 C13.2369907,12.5325281 13.1049884,12.5565285 13.0389872,12.5625286 C12.9669859,12.5685287 12.9249852,12.5745288 12.9249852,12.5745288 C12.4989776,12.6165296 12.1149707,12.304524 12.07297,11.8785164 C12.0369693,11.51851 12.2469731,11.1945042 12.5649787,11.0745021 C12.5649787,11.0745021 12.6069795,11.0565018 12.6729807,11.0325013 C12.750982,11.0085009 12.8229833,10.9665001 12.9909863,10.9184993 C13.3269923,10.8164975 13.7529999,10.7084955 14.3110099,10.6544946 C14.8630197,10.6064937 15.5290316,10.6184939 16.2316441,10.7624965 C16.9330566,10.9124992 17.6710698,11.1945042 18.355082,11.6025115 C18.6790878,11.8125152 19.027094,12.0585196 19.2970988,12.2985239 C19.417101,12.3885255 19.6211046,12.5925292 19.7411067,12.7185314 C19.8791092,12.862534 20.0051114,13.0065365 20.1317137,13.1565392 C20.6171224,13.7565499 20.9771288,14.4045615 21.211133,14.992572 C21.3491354,15.328578 21.4451371,15.6465836 21.5171384,15.9225886 L22.8011613,15.9225886 C23.3171705,15.9225886 23.7731787,16.228594 23.9711822,16.7026025 C24.1691857,17.1766109 24.0611838,17.7226207 23.6951773,18.0886272" id="re-record" transform="translate(18.068963, 16.137926) scale(-1, 1) rotate(-45.000000) translate(-18.068963, -16.137926) "></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="44px" height="52px" viewBox="0 0 44 48" 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>play</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M17.2214651,8.34499443 C19.7766419,4.08636653 23.9234057,4.09303946 26.4745787,8.34499443 L40.1520974,31.140859 C42.7072741,35.3994869 40.7489672,38.8517869 35.7726637,38.8517869 L7.92338006,38.8517869 C2.9495089,38.8517869 0.992773429,35.392814 3.54394641,31.140859 L17.2214651,8.34499443 Z" id="path-1"></path>
<filter x="-16.7%" y="-19.3%" width="133.5%" height="138.6%" filterUnits="objectBoundingBox" id="filter-2">
<feMorphology radius="2.5" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1"></feMorphology>
<feOffset dx="0" dy="0" in="shadowSpreadOuter1" result="shadowOffsetOuter1"></feOffset>
<feComposite in="shadowOffsetOuter1" in2="SourceAlpha" operator="out" result="shadowOffsetOuter1"></feComposite>
<feColorMatrix values="0 0 0 0 0.298039216 0 0 0 0 0.592156863 0 0 0 0 1 0 0 0 0.1 0" type="matrix" in="shadowOffsetOuter1"></feColorMatrix>
</filter>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Desktop---1280x720" transform="translate(-623.000000, -347.000000)">
<g id="Step-3---Altering-Suggested-Trim" transform="translate(0.000000, 42.000000)">
<g id="Play-/-Record-/-Stop" transform="translate(623.000000, 307.000000)">
<g id="play" transform="translate(21.849131, 22.002656) rotate(90.000000) translate(-21.849131, -22.002656) ">
<use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
<use stroke="#4280D7" stroke-width="1" fill="#4C97FF" fill-rule="evenodd" xlink:href="#path-1"></use>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="52px" height="52px" viewBox="0 0 52 52" 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>stop-playback</title>
<desc>Created with Sketch.</desc>
<defs>
<rect id="path-1" x="0" y="0" width="44" height="44" rx="4"></rect>
<filter x="-13.6%" y="-13.6%" width="127.3%" height="127.3%" filterUnits="objectBoundingBox" id="filter-2">
<feMorphology radius="2" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1"></feMorphology>
<feOffset dx="0" dy="0" in="shadowSpreadOuter1" result="shadowOffsetOuter1"></feOffset>
<feComposite in="shadowOffsetOuter1" in2="SourceAlpha" operator="out" result="shadowOffsetOuter1"></feComposite>
<feColorMatrix values="0 0 0 0 0.298039216 0 0 0 0 0.592156863 0 0 0 0 1 0 0 0 0.1 0" type="matrix" in="shadowOffsetOuter1"></feColorMatrix>
</filter>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Desktop---1280x720" transform="translate(-619.000000, -345.000000)">
<g id="Step-4---Playing-Trim" transform="translate(0.000000, 42.000000)">
<g id="Play-/-Record-/-Stop" transform="translate(623.000000, 307.000000)">
<g id="stop-playback">
<use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
<use fill="#4C97FF" fill-rule="evenodd" xlink:href="#path-1"></use>
<rect stroke="#4280D7" stroke-width="1" x="0.5" y="0.5" width="43" height="43" rx="4"></rect>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="52px" height="52px" viewBox="0 0 52 52" 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>stop-recording</title>
<desc>Created with Sketch.</desc>
<defs>
<rect id="path-1" x="20" y="0" width="44" height="44" rx="4"></rect>
<filter x="-13.6%" y="-13.6%" width="127.3%" height="127.3%" filterUnits="objectBoundingBox" id="filter-2">
<feMorphology radius="2" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1"></feMorphology>
<feOffset dx="0" dy="0" in="shadowSpreadOuter1" result="shadowOffsetOuter1"></feOffset>
<feComposite in="shadowOffsetOuter1" in2="SourceAlpha" operator="out" result="shadowOffsetOuter1"></feComposite>
<feColorMatrix values="0 0 0 0 1 0 0 0 0 0.4 0 0 0 0 0.101960784 0 0 0 0.1 0" type="matrix" in="shadowOffsetOuter1"></feColorMatrix>
</filter>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Desktop---1280x720" transform="translate(-619.000000, -345.000000)">
<g id="Step-2---Recording" transform="translate(0.000000, 42.000000)">
<g id="Play-/-Record-/-Stop" transform="translate(603.000000, 307.000000)">
<g id="stop-recording">
<use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
<use fill="#FF661A" fill-rule="evenodd" xlink:href="#path-1"></use>
<rect stroke="#E64D00" stroke-width="1" x="20.5" y="0.5" width="43" height="43" rx="4"></rect>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,86 @@
import PropTypes from 'prop-types';
import React from 'react';
import Box from '../box/box.jsx';
import Waveform from '../waveform/waveform.jsx';
import Meter from '../meter/meter.jsx';
import AudioTrimmer from '../../containers/audio-trimmer.jsx';
import styles from './record-modal.css';
import backIcon from './icon--back.svg';
import stopIcon from './icon--stop-playback.svg';
import playIcon from './icon--play.svg';
const PlaybackStep = props => (
<Box>
<Box className={styles.visualizationContainer}>
<Box className={styles.meterContainer}>
<Meter
height={172}
level={0}
width={20}
/>
</Box>
<Box className={styles.waveformContainer}>
<Waveform
data={props.levels}
height={150}
level={0}
width={480}
/>
<AudioTrimmer
playhead={props.playhead}
trimEnd={props.trimEnd}
trimStart={props.trimStart}
onSetTrimEnd={props.onSetTrimEnd}
onSetTrimStart={props.onSetTrimStart}
/>
</Box>
</Box>
<Box className={styles.mainButtonRow}>
<button
className={styles.mainButton}
onClick={props.playing ? props.onStopPlaying : props.onPlay}
>
<img src={props.playing ? stopIcon : playIcon} />
<div className={styles.helpText}>
<span className={styles.playingText}>
{props.playing ? 'Stop' : 'Play'}
</span>
</div>
</button>
</Box>
<Box className={styles.buttonRow}>
<button
className={styles.cancelButton}
onClick={props.onBack}
>
<img src={backIcon} /> Re-record
</button>
<button
className={styles.okButton}
disabled={props.encoding}
onClick={props.onSubmit}
>
{props.encoding ? 'Loading...' : 'Save'}
</button>
</Box>
</Box>
);
PlaybackStep.propTypes = {
encoding: PropTypes.bool.isRequired,
levels: PropTypes.arrayOf(PropTypes.number).isRequired,
onBack: PropTypes.func.isRequired,
onPlay: PropTypes.func.isRequired,
onSetTrimEnd: PropTypes.func.isRequired,
onSetTrimStart: PropTypes.func.isRequired,
onStopPlaying: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
playhead: PropTypes.number,
playing: PropTypes.bool.isRequired,
trimEnd: PropTypes.number.isRequired,
trimStart: PropTypes.number.isRequired
};
module.exports = PlaybackStep;
@@ -0,0 +1,120 @@
@import "../../css/colors.css";
@import "../../css/units.css";
.modal-content {
width: 600px;
}
.body {
background: white;
padding: 1.5rem 2.25rem;
}
.visualization-container {
display: flex;
justify-content: space-around;
}
.meter-container, .waveform-container {
background: $ui-pane-gray;
border: 1px solid $ui-pane-border;
border-radius: 5px;
padding: 3px;
}
.meter-container {
margin-right: 5px;
height: 180px;
}
.waveform-container {
display: flex;
justify-content: space-around;
align-items: center;
width: 100%;
height: 180px;
position: relative;
}
.help-text {
margin: 10px auto 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: rgb(167, 170, 181);
font-size: 0.95rem;
font-weight: 500;
}
.playing-text {
color: $motion-primary;
}
.recording-text {
color: $red-primary;
}
.main-button-row {
display: flex;
justify-content: space-around;
margin-top: 15px;
}
.main-button-row button {
padding: 0.5rem 0.75rem;
border-radius: 0.25rem;
background: transparent;
border: none;
}
.main-button-row button:disabled {
opacity: 0.25;
}
.main-button-row button:active, .main-button-row button:focus {
outline: none;
}
.button-row {
font-weight: bolder;
text-align: right;
display: flex;
justify-content: space-between;
}
.button-row button {
padding: 0.75rem 1rem;
border-radius: 0.25rem;
background: white;
border: 1px solid $ui-pane-border;
font-weight: 600;
font-size: 0.85rem;
color: $motion-primary;
}
.button-row button.ok-button {
background: $motion-primary;
border: $motion-primary;
color: white;
}
.button-row button + button {
margin-left: 0.5rem;
}
.main-button {
text-align: center;
}
.record-button {
overflow: visible;
}
.record-button-circle {
fill: $red-primary;
stroke: $red-tertiary;
}
.record-button-circle-outline {
fill: $red-primary;
opacity: 0.2;
transition: 0.1s;
}
@@ -0,0 +1,66 @@
import PropTypes from 'prop-types';
import React from 'react';
import Box from '../box/box.jsx';
import RecordingStep from '../../containers/recording-step.jsx';
import PlaybackStep from '../../containers/playback-step.jsx';
import Modal from '../modal/modal.jsx';
import styles from './record-modal.css';
const RecordModal = props => (
<Modal
visible
className={styles.modalContent}
contentLabel={'Record Sound'}
onRequestClose={props.onCancel}
>
<Box className={styles.body}>
{props.samples ? (
<PlaybackStep
encoding={props.encoding}
levels={props.levels}
playhead={props.playhead}
playing={props.playing}
samples={props.samples}
trimEnd={props.trimEnd}
trimStart={props.trimStart}
onBack={props.onBack}
onPlay={props.onPlay}
onSetPlayhead={props.onSetPlayhead}
onSetTrimEnd={props.onSetTrimEnd}
onSetTrimStart={props.onSetTrimStart}
onStopPlaying={props.onStopPlaying}
onSubmit={props.onSubmit}
/>
) : (
<RecordingStep
recording={props.recording}
onRecord={props.onRecord}
onStopRecording={props.onStopRecording}
/>
)}
</Box>
</Modal>
);
RecordModal.propTypes = {
encoding: PropTypes.bool.isRequired,
levels: PropTypes.arrayOf(PropTypes.number),
onBack: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
onPlay: PropTypes.func.isRequired,
onRecord: PropTypes.func.isRequired,
onSetPlayhead: PropTypes.func.isRequired,
onSetTrimEnd: PropTypes.func.isRequired,
onSetTrimStart: PropTypes.func.isRequired,
onStopPlaying: PropTypes.func.isRequired,
onStopRecording: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
playhead: PropTypes.number,
playing: PropTypes.bool,
recording: PropTypes.bool,
samples: PropTypes.instanceOf(Float32Array),
trimEnd: PropTypes.number.isRequired,
trimStart: PropTypes.number.isRequired
};
module.exports = RecordModal;
@@ -0,0 +1,83 @@
import PropTypes from 'prop-types';
import React from 'react';
import Box from '../box/box.jsx';
import Meter from '../meter/meter.jsx';
import Waveform from '../waveform/waveform.jsx';
import styles from './record-modal.css';
import stopIcon from './icon--stop-recording.svg';
const RecordingStep = props => (
<Box>
<Box className={styles.visualizationContainer}>
<Box className={styles.meterContainer}>
<Meter
height={172}
level={props.level}
width={20}
/>
</Box>
<Box className={styles.waveformContainer}>
{props.levels ? (
<Waveform
data={props.levels}
height={150}
level={0}
width={440}
/>
) : (
<span className={styles.helpText}>
{props.listening ? 'Begin recording by clicking the button below' :
'↖️ \u00A0We need your permission to use your microphone'}
</span>
)}
</Box>
</Box>
<Box className={styles.mainButtonRow}>
<button
className={styles.mainButton}
disabled={!props.listening}
onClick={props.recording ? props.onStopRecording : props.onRecord}
>
{props.recording ? (
<img src={stopIcon} />
) : (
<svg
className={styles.recordButton}
height="52"
width="52"
>
<circle
className={styles.recordButtonCircle}
cx="26"
cy="26"
r="25"
/>
<circle
className={styles.recordButtonCircleOutline}
cx="26"
cy="26"
r={27 + props.level * 5}
/>
</svg>
)}
<div className={styles.helpText}>
<span className={styles.recordingText}>
{props.recording ? 'Stop recording' : 'Record'}
</span>
</div>
</button>
</Box>
</Box>
);
RecordingStep.propTypes = {
level: PropTypes.number,
levels: PropTypes.arrayOf(PropTypes.number),
listening: PropTypes.bool,
onRecord: PropTypes.func.isRequired,
onStopRecording: PropTypes.func.isRequired,
recording: PropTypes.bool
};
module.exports = RecordingStep;
+18 -30
View File
@@ -1,14 +1,15 @@
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
const Box = require('../box/box.jsx');
const styles = require('./sprite-info.css');
import Box from '../box/box.jsx';
import BufferedInput from '../buffered-input/buffered-input.jsx';
import styles from './sprite-info.css';
const xIcon = require('./icon--x.svg');
const yIcon = require('./icon--y.svg');
const showIcon = require('./icon--show.svg');
const hideIcon = require('./icon--hide.svg');
import xIcon from './icon--x.svg';
import yIcon from './icon--y.svg';
import showIcon from './icon--show.svg';
import hideIcon from './icon--hide.svg';
const ROTATION_STYLES = ['left-right', 'don\'t rotate', 'all around'];
@@ -32,16 +33,14 @@ class SpriteInfo extends React.Component {
<div className={classNames(styles.row, styles.rowPrimary)}>
<div className={styles.group}>
<span className={styles.inputLabel}>Sprite</span>
<input
<BufferedInput
className={classNames(styles.inputForm, styles.spriteName)}
disabled={this.props.disabled}
placeholder="Name"
tabIndex="1"
type="text"
value={this.props.disabled ? '' : this.props.name}
onBlur={this.props.onBlurName}
onChange={this.props.onChangeName}
onKeyPress={this.props.onKeyPress}
onSubmit={this.props.onChangeName}
/>
</div>
@@ -53,16 +52,14 @@ class SpriteInfo extends React.Component {
/>
</div>
<span className={styles.inputLabel}>x</span>
<input
<BufferedInput
className={classNames(styles.inputForm, styles.x)}
disabled={this.props.disabled}
placeholder="x"
tabIndex="2"
type="text"
value={this.props.disabled ? '' : this.props.x}
onBlur={this.props.onBlurX}
onChange={this.props.onChangeX}
onKeyPress={this.props.onKeyPress}
onSubmit={this.props.onChangeX}
/>
</div>
@@ -74,16 +71,14 @@ class SpriteInfo extends React.Component {
/>
</div>
<span className={styles.inputLabel}>y</span>
<input
<BufferedInput
className={classNames(styles.inputForm, styles.y)}
disabled={this.props.disabled}
placeholder="y"
tabIndex="3"
type="text"
value={this.props.disabled ? '' : this.props.y}
onBlur={this.props.onBlurY}
onChange={this.props.onChangeY}
onKeyPress={this.props.onKeyPress}
onSubmit={this.props.onChangeY}
/>
</div>
</div>
@@ -134,15 +129,13 @@ class SpriteInfo extends React.Component {
</div>
<div className={styles.group}>
<span className={styles.inputLabelSecondary}>Direction</span>
<input
<BufferedInput
className={classNames(styles.inputForm, styles.direction)}
disabled={this.props.disabled}
tabIndex="5"
type="text"
value={this.props.disabled ? '' : this.props.direction}
onBlur={this.props.onBlurDirection}
onChange={this.props.onChangeDirection}
onKeyPress={this.props.onKeyPress}
onSubmit={this.props.onChangeDirection}
/>
</div>
<div className={styles.group}>
@@ -178,10 +171,6 @@ SpriteInfo.propTypes = {
]),
disabled: PropTypes.bool,
name: PropTypes.string,
onBlurDirection: PropTypes.func,
onBlurName: PropTypes.func,
onBlurX: PropTypes.func,
onBlurY: PropTypes.func,
onChangeDirection: PropTypes.func,
onChangeName: PropTypes.func,
onChangeRotationStyle: PropTypes.func,
@@ -189,7 +178,6 @@ SpriteInfo.propTypes = {
onChangeY: PropTypes.func,
onClickNotVisible: PropTypes.func,
onClickVisible: PropTypes.func,
onKeyPress: PropTypes.func,
rotationStyle: PropTypes.oneOf(ROTATION_STYLES),
visible: PropTypes.bool,
x: PropTypes.oneOfType([
@@ -1,11 +1,11 @@
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
const Box = require('../box/box.jsx');
const CostumeCanvas = require('../costume-canvas/costume-canvas.jsx');
const CloseButton = require('../close-button/close-button.jsx');
const styles = require('./sprite-selector-item.css');
import Box from '../box/box.jsx';
import CostumeCanvas from '../costume-canvas/costume-canvas.jsx';
import CloseButton from '../close-button/close-button.jsx';
import styles from './sprite-selector-item.css';
const SpriteSelectorItem = props => (
<Box
@@ -1,10 +1,10 @@
const PropTypes = require('prop-types');
const React = require('react');
import PropTypes from 'prop-types';
import React from 'react';
const Box = require('../box/box.jsx');
const SpriteInfo = require('../../containers/sprite-info.jsx');
const SpriteSelectorItem = require('../../containers/sprite-selector-item.jsx');
const styles = require('./sprite-selector.css');
import Box from '../box/box.jsx';
import SpriteInfo from '../../containers/sprite-info.jsx';
import SpriteSelectorItem from '../../containers/sprite-selector-item.jsx';
import styles from './sprite-selector.css';
const SpriteSelectorComponent = function (props) {
const {
@@ -1,10 +1,10 @@
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
const Box = require('../box/box.jsx');
const CostumeCanvas = require('../costume-canvas/costume-canvas.jsx');
const styles = require('./stage-selector.css');
import Box from '../box/box.jsx';
import CostumeCanvas from '../costume-canvas/costume-canvas.jsx';
import styles from './stage-selector.css';
const StageSelector = props => {
const {
+5 -5
View File
@@ -1,9 +1,9 @@
const PropTypes = require('prop-types');
const React = require('react');
import PropTypes from 'prop-types';
import React from 'react';
const Box = require('../box/box.jsx');
const MonitorList = require('../../containers/monitor-list.jsx');
const styles = require('./stage.css');
import Box from '../box/box.jsx';
import MonitorList from '../../containers/monitor-list.jsx';
import styles from './stage.css';
const StageComponent = props => {
const {
+5 -5
View File
@@ -1,9 +1,9 @@
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
const stopAllIcon = require('./icon--stop-all.svg');
const styles = require('./stop-all.css');
import stopAllIcon from './icon--stop-all.svg';
import styles from './stop-all.css';
const StopAllComponent = function (props) {
const {
@@ -44,6 +44,7 @@
.add-button {
height: 0.8rem;
margin: auto;
}
/* @todo: This is hacky. Better: move buttons in their corresponding panes, and set values relatively */
+15 -13
View File
@@ -1,19 +1,19 @@
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
const VM = require('scratch-vm');
import VM from 'scratch-vm';
const Box = require('../box/box.jsx');
const BackdropLibrary = require('../../containers/backdrop-library.jsx');
const CostumeLibrary = require('../../containers/costume-library.jsx');
const SoundLibrary = require('../../containers/sound-library.jsx');
const SpriteLibrary = require('../../containers/sprite-library.jsx');
const SpriteSelectorComponent = require('../sprite-selector/sprite-selector.jsx');
const StageSelector = require('../../containers/stage-selector.jsx');
import Box from '../box/box.jsx';
import BackdropLibrary from '../../containers/backdrop-library.jsx';
import CostumeLibrary from '../../containers/costume-library.jsx';
import SoundLibrary from '../../containers/sound-library.jsx';
import SpriteLibrary from '../../containers/sprite-library.jsx';
import SpriteSelectorComponent from '../sprite-selector/sprite-selector.jsx';
import StageSelector from '../../containers/stage-selector.jsx';
const styles = require('./target-pane.css');
const addIcon = require('./icon--add.svg');
import styles from './target-pane.css';
import addIcon from './icon--add.svg';
/*
* Pane that contains the sprite selector, sprite info, stage selector,
@@ -78,6 +78,7 @@ const TargetPane = ({
<button
className={classNames(styles.addButtonWrapper, styles.addButtonWrapperSprite)}
title="Add sprite"
onClick={onNewSpriteClick}
>
<img
@@ -88,6 +89,7 @@ const TargetPane = ({
<button
className={classNames(styles.addButtonWrapper, styles.addButtonWrapperStage)}
title="Add backdrop"
onClick={onNewBackdropClick}
>
<img
+9
View File
@@ -0,0 +1,9 @@
@import "../../css/colors.css";
.container {
width: 100%;
}
.waveform-path {
fill: $sound-primary;
stroke: $sound-tertiary;
}
+51
View File
@@ -0,0 +1,51 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './waveform.css';
const Waveform = props => {
const {
width,
height,
data
} = props;
const cappedData = [0, ...data, 0];
const points = [
...cappedData.map((v, i) =>
[width * i / cappedData.length, height * v / 2]
),
...cappedData.reverse().map((v, i) =>
[width * (cappedData.length - i - 1) / cappedData.length, -height * v / 2]
)
];
const pathComponents = points.map(([x, y], i) => {
const [nx, ny] = points[i < points.length - 1 ? i + 1 : 0];
return `Q${x} ${y} ${(x + nx) / 2} ${(y + ny) / 2}`;
});
return (
<svg
className={styles.container}
viewBox={`0 0 ${width} ${height}`}
>
<g transform={`scale(1, -1) translate(0, -${height / 2})`}>
<path
className={styles.waveformPath}
d={`M0 0${pathComponents.join()}Z`}
strokeLinejoin={'round'}
strokeWidth={2}
/>
</g>
</svg>
);
};
Waveform.propTypes = {
data: PropTypes.arrayOf(PropTypes.number),
height: PropTypes.number,
width: PropTypes.number
};
module.exports = Waveform;
+72
View File
@@ -0,0 +1,72 @@
import React from 'react';
import PropTypes from 'prop-types';
import bindAll from 'lodash.bindall';
import AudioTrimmerComponent from '../components/audio-trimmer/audio-trimmer.jsx';
class AudioTrimmer extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleTrimStartMouseDown',
'handleTrimEndMouseDown',
'handleTrimStartMouseMove',
'handleTrimEndMouseMove',
'handleTrimStartMouseUp',
'handleTrimEndMouseUp'
]);
}
handleTrimStartMouseMove (e) {
const containerSize = this.containerElement.getBoundingClientRect().width;
const dx = (e.clientX - this.initialX) / containerSize;
const newTrim = Math.max(0, Math.min(this.props.trimEnd, this.initialTrim + dx));
this.props.onSetTrimStart(newTrim);
}
handleTrimEndMouseMove (e) {
const containerSize = this.containerElement.getBoundingClientRect().width;
const dx = (e.clientX - this.initialX) / containerSize;
const newTrim = Math.min(1, Math.max(this.props.trimStart, this.initialTrim + dx));
this.props.onSetTrimEnd(newTrim);
}
handleTrimStartMouseUp () {
window.removeEventListener('mousemove', this.handleTrimStartMouseMove);
window.removeEventListener('mouseup', this.handleTrimStartMouseUp);
}
handleTrimEndMouseUp () {
window.removeEventListener('mousemove', this.handleTrimEndMouseMove);
window.removeEventListener('mouseup', this.handleTrimEndMouseUp);
}
handleTrimStartMouseDown (e) {
this.initialX = e.clientX;
this.initialTrim = this.props.trimStart;
window.addEventListener('mousemove', this.handleTrimStartMouseMove);
window.addEventListener('mouseup', this.handleTrimStartMouseUp);
}
handleTrimEndMouseDown (e) {
this.initialX = e.clientX;
this.initialTrim = this.props.trimEnd;
window.addEventListener('mousemove', this.handleTrimEndMouseMove);
window.addEventListener('mouseup', this.handleTrimEndMouseUp);
}
render () {
return (
<AudioTrimmerComponent
containerRef={el => (this.containerElement = el)}
playhead={this.props.playhead}
trimEnd={this.props.trimEnd}
trimStart={this.props.trimStart}
onTrimEndMouseDown={this.handleTrimEndMouseDown}
onTrimStartMouseDown={this.handleTrimStartMouseDown}
/>
);
}
}
AudioTrimmer.propTypes = {
onSetTrimEnd: PropTypes.func,
onSetTrimStart: PropTypes.func,
playhead: PropTypes.number,
trimEnd: PropTypes.number,
trimStart: PropTypes.number
};
module.exports = AudioTrimmer;
+6 -6
View File
@@ -1,10 +1,10 @@
const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types');
const React = require('react');
const VM = require('scratch-vm');
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
import VM from 'scratch-vm';
const backdropLibraryContent = require('../lib/libraries/backdrops.json');
const LibraryComponent = require('../components/library/library.jsx');
import backdropLibraryContent from '../lib/libraries/backdrops.json';
import LibraryComponent from '../components/library/library.jsx';
class BackdropLibrary extends React.Component {
+12 -10
View File
@@ -1,12 +1,12 @@
const bindAll = require('lodash.bindall');
const debounce = require('lodash.debounce');
const defaultsDeep = require('lodash.defaultsdeep');
const PropTypes = require('prop-types');
const React = require('react');
const VMScratchBlocks = require('../lib/blocks');
const VM = require('scratch-vm');
const Prompt = require('./prompt.jsx');
const BlocksComponent = require('../components/blocks/blocks.jsx');
import bindAll from 'lodash.bindall';
import debounce from 'lodash.debounce';
import defaultsDeep from 'lodash.defaultsdeep';
import PropTypes from 'prop-types';
import React from 'react';
import VMScratchBlocks from '../lib/blocks';
import VM from 'scratch-vm';
import Prompt from './prompt.jsx';
import BlocksComponent from '../components/blocks/blocks.jsx';
const addFunctionListener = (object, property, callback) => {
const oldFn = object[property];
@@ -63,8 +63,10 @@ class Blocks extends React.Component {
}
// @todo hack to resize blockly manually in case resize happened while hidden
// @todo hack to reload the workspace due to gui bug #413
if (this.props.isVisible) { // Scripts tab
this.workspace.setVisible(true);
this.props.vm.refreshWorkspace();
window.dispatchEvent(new Event('resize'));
this.workspace.toolbox_.refreshSelection();
} else {
@@ -148,6 +150,7 @@ class Blocks extends React.Component {
if (this.props.vm.editingTarget && !this.state.workspaceMetrics[this.props.vm.editingTarget.id]) {
this.onWorkspaceMetricsChange();
}
this.ScratchBlocks.Events.disable();
this.workspace.clear();
@@ -172,7 +175,6 @@ class Blocks extends React.Component {
}
handlePromptCallback (data) {
this.state.prompt.callback(data);
this.props.vm.createVariable(data);
this.handlePromptClose();
}
handlePromptClose () {
+6 -6
View File
@@ -1,10 +1,10 @@
const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types');
const React = require('react');
const VM = require('scratch-vm');
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
import VM from 'scratch-vm';
const costumeLibraryContent = require('../lib/libraries/costumes.json');
const LibraryComponent = require('../components/library/library.jsx');
import costumeLibraryContent from '../lib/libraries/costumes.json';
import LibraryComponent from '../components/library/library.jsx';
class CostumeLibrary extends React.PureComponent {
+28 -10
View File
@@ -1,12 +1,13 @@
const PropTypes = require('prop-types');
const React = require('react');
const bindAll = require('lodash.bindall');
import PropTypes from 'prop-types';
import React from 'react';
import bindAll from 'lodash.bindall';
import {defineMessages} from 'react-intl';
import VM from 'scratch-vm';
const VM = require('scratch-vm');
import AssetPanel from '../components/asset-panel/asset-panel.jsx';
import addCostumeIcon from '../components/asset-panel/icon--add-costume-lib.svg';
const AssetPanel = require('../components/asset-panel/asset-panel.jsx');
const {connect} = require('react-redux');
import {connect} from 'react-redux';
const {
openCostumeLibrary,
@@ -37,6 +38,7 @@ class CostumeTab extends React.Component {
}
handleSelectCostume (costumeIndex) {
this.props.vm.editingTarget.setCostume(costumeIndex);
this.setState({selectedCostumeIndex: costumeIndex});
}
@@ -59,17 +61,33 @@ class CostumeTab extends React.Component {
return null;
}
const addText = target.isStage ? 'Add Backdrop' : 'Add Costume';
const messages = defineMessages({
addBackdrop: {
id: 'action.addBackdrop',
defaultMessage: 'Add Backdrop',
description: 'Button to add a backdrop in the editor tab'
},
addCostume: {
id: 'action.addCostume',
defaultMessage: 'Add Costume',
description: 'Button to add a costume in the editor tab'
}
});
const addMessage = target.isStage ? messages.addBackdrop : messages.addCostume;
const addFunc = target.isStage ? onNewBackdropClick : onNewCostumeClick;
return (
<AssetPanel
buttons={[{
message: addMessage,
img: addCostumeIcon,
onClick: addFunc
}]}
items={target.costumes || []}
newText={addText}
selectedItemIndex={this.state.selectedCostumeIndex}
onDeleteClick={this.handleDeleteCostume}
onItemClick={this.handleSelectCostume}
onNewClick={addFunc}
/>
);
}
+5 -5
View File
@@ -1,10 +1,10 @@
const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types');
const React = require('react');
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
const VM = require('scratch-vm');
import VM from 'scratch-vm';
const GreenFlagComponent = require('../components/green-flag/green-flag.jsx');
import GreenFlagComponent from '../components/green-flag/green-flag.jsx';
class GreenFlag extends React.Component {
constructor (props) {
+6 -6
View File
@@ -1,11 +1,11 @@
const PropTypes = require('prop-types');
const React = require('react');
const VM = require('scratch-vm');
const bindAll = require('lodash.bindall');
import PropTypes from 'prop-types';
import React from 'react';
import VM from 'scratch-vm';
import bindAll from 'lodash.bindall';
const vmListenerHOC = require('../lib/vm-listener-hoc.jsx');
import vmListenerHOC from '../lib/vm-listener-hoc.jsx';
const GUIComponent = require('../components/gui/gui.jsx');
import GUIComponent from '../components/gui/gui.jsx';
class GUI extends React.Component {
constructor (props) {
+5 -5
View File
@@ -1,9 +1,9 @@
const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types');
const React = require('react');
const {connect} = require('react-redux');
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
import {connect} from 'react-redux';
const LoadButtonComponent = require('../components/load-button/load-button.jsx');
import LoadButtonComponent from '../components/load-button/load-button.jsx';
class LoadButton extends React.Component {
constructor (props) {
+4 -4
View File
@@ -1,9 +1,9 @@
const bindAll = require('lodash.bindall');
const React = require('react');
import bindAll from 'lodash.bindall';
import React from 'react';
const {connect} = require('react-redux');
import {connect} from 'react-redux';
const MonitorListComponent = require('../components/monitor-list/monitor-list.jsx');
import MonitorListComponent from '../components/monitor-list/monitor-list.jsx';
class MonitorList extends React.Component {
constructor (props) {
+5 -5
View File
@@ -1,9 +1,9 @@
const bindAll = require('lodash.bindall');
const React = require('react');
const PropTypes = require('prop-types');
import bindAll from 'lodash.bindall';
import React from 'react';
import PropTypes from 'prop-types';
const monitorAdapter = require('../lib/monitor-adapter.js');
const MonitorComponent = require('../components/monitor/monitor.jsx');
import monitorAdapter from '../lib/monitor-adapter.js';
import MonitorComponent from '../components/monitor/monitor.jsx';
class Monitor extends React.Component {
constructor (props) {
+56
View File
@@ -0,0 +1,56 @@
import React from 'react';
import PropTypes from 'prop-types';
import bindAll from 'lodash.bindall';
import PlaybackStepComponent from '../components/record-modal/playback-step.jsx';
import AudioBufferPlayer from '../lib/audio/audio-buffer-player.js';
class PlaybackStep extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handlePlay',
'handleStopPlaying'
]);
}
componentDidMount () {
this.audioBufferPlayer = new AudioBufferPlayer(this.props.samples);
}
componentWillUnmount () {
this.audioBufferPlayer.stop();
}
handlePlay () {
this.audioBufferPlayer.play(
this.props.trimStart,
this.props.trimEnd,
this.props.onSetPlayhead,
this.props.onStopPlaying
);
this.props.onPlay();
}
handleStopPlaying () {
this.audioBufferPlayer.stop();
this.props.onStopPlaying();
}
render () {
const {
onPlay, // eslint-disable-line no-unused-vars
onStopPlaying, // eslint-disable-line no-unused-vars
onSetPlayhead, // eslint-disable-line no-unused-vars
...componentProps
} = this.props;
return (
<PlaybackStepComponent
onPlay={this.handlePlay}
onStopPlaying={this.handleStopPlaying}
{...componentProps}
/>
);
}
}
PlaybackStep.propTypes = {
samples: PropTypes.instanceOf(Float32Array).isRequired,
...PlaybackStepComponent.propTypes
};
module.exports = PlaybackStep;
+4 -4
View File
@@ -1,7 +1,7 @@
const PropTypes = require('prop-types');
const React = require('react');
const bindAll = require('lodash.bindall');
const PromptComponent = require('../components/prompt/prompt.jsx');
import PropTypes from 'prop-types';
import React from 'react';
import bindAll from 'lodash.bindall';
import PromptComponent from '../components/prompt/prompt.jsx';
class Prompt extends React.Component {
constructor (props) {
+145
View File
@@ -0,0 +1,145 @@
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
import VM from 'scratch-vm';
import WavEncoder from 'wav-encoder';
import {connect} from 'react-redux';
import RecordModalComponent from '../components/record-modal/record-modal.jsx';
const {
closeSoundRecorder
} = require('../reducers/modals');
class RecordModal extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleRecord',
'handleStopRecording',
'handlePlay',
'handleStopPlaying',
'handleBack',
'handleSubmit',
'handleCancel',
'handleSetPlayhead',
'handleSetTrimStart',
'handleSetTrimEnd'
]);
this.state = {
samples: null,
encoding: false,
levels: null,
playhead: null,
playing: false,
recording: false,
sampleRate: null,
trimStart: 0,
trimEnd: 1
};
}
handleRecord () {
this.setState({recording: true});
}
handleStopRecording (samples, sampleRate, levels, trimStart, trimEnd) {
this.setState({samples, sampleRate, levels, trimStart, trimEnd, recording: false});
}
handlePlay () {
this.setState({playing: true});
}
handleStopPlaying () {
this.setState({playing: false, playhead: null});
}
handleBack () {
this.setState({playing: false, samples: null});
}
handleSetTrimEnd (trimEnd) {
this.setState({trimEnd});
}
handleSetTrimStart (trimStart) {
this.setState({trimStart});
}
handleSetPlayhead (playhead) {
this.setState({playhead});
}
handleSubmit () {
this.setState({encoding: true}, () => {
const sampleCount = this.state.samples.length;
const startIndex = Math.floor(this.state.trimStart * sampleCount);
const endIndex = Math.floor(this.state.trimEnd * sampleCount);
const clippedSamples = this.state.samples.slice(startIndex, endIndex);
WavEncoder.encode({
sampleRate: this.state.sampleRate,
channelData: [clippedSamples]
}).then(wavBuffer => {
const md5 = String(Math.floor(100000 * Math.random()));
const vmSound = {
format: '',
md5: `${md5}.wav`,
name: `recording ${this.props.vm.editingTarget.sprite.sounds.length}`
};
// Load the encoded .wav into the storage cache
const storage = this.props.vm.runtime.storage;
storage.builtinHelper.cache(
storage.AssetType.Sound,
storage.DataFormat.WAV,
new Uint8Array(wavBuffer),
md5
);
this.props.vm.addSound(vmSound);
this.handleCancel();
});
});
}
handleCancel () {
this.props.onClose();
}
render () {
return (
<RecordModalComponent
encoding={this.state.encoding}
levels={this.state.levels}
playhead={this.state.playhead}
playing={this.state.playing}
recording={this.state.recording}
samples={this.state.samples}
trimEnd={this.state.trimEnd}
trimStart={this.state.trimStart}
onBack={this.handleBack}
onCancel={this.handleCancel}
onPlay={this.handlePlay}
onRecord={this.handleRecord}
onSetPlayhead={this.handleSetPlayhead}
onSetTrimEnd={this.handleSetTrimEnd}
onSetTrimStart={this.handleSetTrimStart}
onStopPlaying={this.handleStopPlaying}
onStopRecording={this.handleStopRecording}
onSubmit={this.handleSubmit}
/>
);
}
}
RecordModal.propTypes = {
onClose: PropTypes.func,
vm: PropTypes.instanceOf(VM)
};
const mapStateToProps = state => ({
visible: state.modals.soundRecorder,
vm: state.vm
});
const mapDispatchToProps = dispatch => ({
onClose: () => {
dispatch(closeSoundRecorder());
}
});
module.exports = connect(
mapStateToProps,
mapDispatchToProps
)(RecordModal);
+71
View File
@@ -0,0 +1,71 @@
import React from 'react';
import bindAll from 'lodash.bindall';
import RecordingStepComponent from '../components/record-modal/recording-step.jsx';
import AudioRecorder from '../lib/audio/audio-recorder.js';
class RecordingStep extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleRecord',
'handleStopRecording',
'handleStarted',
'handleLevelUpdate',
'handleRecordingError'
]);
this.state = {
listening: false,
level: 0,
levels: null
};
}
componentDidMount () {
this.audioRecorder = new AudioRecorder();
this.audioRecorder.startListening(this.handleStarted, this.handleLevelUpdate, this.handleRecordingError);
}
componentWillUnmount () {
this.audioRecorder.dispose();
}
handleStarted () {
this.setState({listening: true});
}
handleRecordingError () {
alert('Could not start recording'); // eslint-disable-line no-alert
}
handleLevelUpdate (level) {
this.setState({level});
if (this.props.recording) {
this.setState({levels: (this.state.levels || []).concat([level])});
}
}
handleRecord () {
this.audioRecorder.startRecording();
this.props.onRecord();
}
handleStopRecording () {
const {samples, sampleRate, levels, trimStart, trimEnd} = this.audioRecorder.stop();
this.props.onStopRecording(samples, sampleRate, levels, trimStart, trimEnd);
}
render () {
const {
onRecord, // eslint-disable-line no-unused-vars
onStopRecording, // eslint-disable-line no-unused-vars
...componentProps
} = this.props;
return (
<RecordingStepComponent
level={this.state.level}
levels={this.state.levels}
listening={this.state.listening}
onRecord={this.handleRecord}
onStopRecording={this.handleStopRecording}
{...componentProps}
/>
);
}
}
RecordingStep.propTypes = RecordingStepComponent.propTypes;
module.exports = RecordingStep;
+5 -5
View File
@@ -1,9 +1,9 @@
const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types');
const React = require('react');
const {connect} = require('react-redux');
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
import {connect} from 'react-redux';
const ButtonComponent = require('../components/button/button.jsx');
import ButtonComponent from '../components/button/button.jsx';
class SaveButton extends React.Component {
constructor (props) {
+8 -8
View File
@@ -1,13 +1,13 @@
const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types');
const React = require('react');
const VM = require('scratch-vm');
const AudioEngine = require('scratch-audio');
const LibraryComponent = require('../components/library/library.jsx');
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
import VM from 'scratch-vm';
import AudioEngine from 'scratch-audio';
import LibraryComponent from '../components/library/library.jsx';
const soundIcon = require('../components/asset-panel/icon--sound.svg');
import soundIcon from '../components/asset-panel/icon--sound.svg';
const soundLibraryContent = require('../lib/libraries/sounds.json');
import soundLibraryContent from '../lib/libraries/sounds.json';
class SoundLibrary extends React.PureComponent {
constructor (props) {
+50 -15
View File
@@ -1,16 +1,21 @@
const PropTypes = require('prop-types');
const React = require('react');
const bindAll = require('lodash.bindall');
import PropTypes from 'prop-types';
import React from 'react';
import bindAll from 'lodash.bindall';
import {defineMessages} from 'react-intl';
import VM from 'scratch-vm';
const VM = require('scratch-vm');
import AssetPanel from '../components/asset-panel/asset-panel.jsx';
import soundIcon from '../components/asset-panel/icon--sound.svg';
import addSoundFromLibraryIcon from '../components/asset-panel/icon--add-sound-lib.svg';
import addSoundFromRecordingIcon from '../components/asset-panel/icon--add-sound-record.svg';
const AssetPanel = require('../components/asset-panel/asset-panel.jsx');
const soundIcon = require('../components/asset-panel/icon--sound.svg');
import RecordModal from './record-modal.jsx';
const {connect} = require('react-redux');
import {connect} from 'react-redux';
const {
openSoundLibrary
openSoundLibrary,
openSoundRecorder
} = require('../reducers/modals');
class SoundTab extends React.Component {
@@ -52,7 +57,8 @@ class SoundTab extends React.Component {
editingTarget,
sprites,
stage,
onNewSoundClick
onNewSoundFromLibraryClick,
onNewSoundFromRecordingClick
} = this.props;
const target = editingTarget && sprites[editingTarget] ? sprites[editingTarget] : stage;
@@ -68,25 +74,51 @@ class SoundTab extends React.Component {
}
)) : [];
const messages = defineMessages({
recordSound: {
id: 'action.recordSound',
defaultMessage: 'Record Sound',
description: 'Button to record a sound in the editor tab'
},
addSound: {
id: 'action.addSound',
defaultMessage: 'Add Sound',
description: 'Button to add a sound in the editor tab'
}
});
return (
<AssetPanel
buttons={[{
message: messages.recordSound,
img: addSoundFromRecordingIcon,
onClick: onNewSoundFromRecordingClick
}, {
message: messages.addSound,
img: addSoundFromLibraryIcon,
onClick: onNewSoundFromLibraryClick
}]}
items={sounds.map(sound => ({
url: soundIcon,
...sound
}))}
newText={'Add Sound'}
selectedItemIndex={this.state.selectedSoundIndex}
onDeleteClick={this.handleDeleteSound}
onItemClick={this.handleSelectSound}
onNewClick={onNewSoundClick}
/>
>
{this.props.soundRecorderVisible ? (
<RecordModal />
) : null}
</AssetPanel>
);
}
}
SoundTab.propTypes = {
editingTarget: PropTypes.string,
onNewSoundClick: PropTypes.func.isRequired,
onNewSoundFromLibraryClick: PropTypes.func.isRequired,
onNewSoundFromRecordingClick: PropTypes.func.isRequired,
soundRecorderVisible: PropTypes.bool,
sprites: PropTypes.shape({
id: PropTypes.shape({
sounds: PropTypes.arrayOf(PropTypes.shape({
@@ -106,13 +138,16 @@ const mapStateToProps = state => ({
editingTarget: state.targets.editingTarget,
sprites: state.targets.sprites,
stage: state.targets.stage,
soundLibraryVisible: state.modals.soundLibrary
soundRecorderVisible: state.modals.soundRecorder
});
const mapDispatchToProps = dispatch => ({
onNewSoundClick: e => {
onNewSoundFromLibraryClick: e => {
e.preventDefault();
dispatch(openSoundLibrary());
},
onNewSoundFromRecordingClick: () => {
dispatch(openSoundRecorder());
}
});
+5 -71
View File
@@ -1,71 +1,21 @@
const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types');
const React = require('react');
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
const SpriteInfoComponent = require('../components/sprite-info/sprite-info.jsx');
import SpriteInfoComponent from '../components/sprite-info/sprite-info.jsx';
class SpriteInfo extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleChangeName',
'handleChangeRotationStyle',
'handleChangeDirection',
'handleChangeX',
'handleChangeY',
'handleClickVisible',
'handleClickNotVisible',
'handleFlush',
'handleKeyPress'
'handleClickNotVisible'
]);
this.state = {
direction: null,
name: null,
x: null,
y: null
};
}
handleKeyPress (e) {
if (e.key === 'Enter') {
this.handleFlush();
e.target.blur();
}
}
handleFlush () {
if (this.state.direction !== null && !isNaN(this.state.direction)) {
this.props.onChangeDirection(this.state.direction);
}
if (this.state.name !== null) {
this.props.onChangeName(this.state.name);
}
if (this.state.x !== null && !isNaN(this.state.x)) {
this.props.onChangeX(this.state.x);
}
if (this.state.y !== null && !isNaN(this.state.y)) {
this.props.onChangeY(this.state.y);
}
this.setState({
direction: null,
name: null,
x: null,
y: null
});
}
handleChangeDirection (e) {
this.setState({direction: e.target.value});
}
handleChangeName (e) {
this.setState({name: e.target.value});
}
handleChangeRotationStyle (e) {
this.props.onChangeRotationStyle(e.target.value);
}
handleChangeX (e) {
this.setState({x: e.target.value});
}
handleChangeY (e) {
this.setState({y: e.target.value});
}
handleClickVisible (e) {
e.preventDefault();
this.props.onChangeVisibility(true);
@@ -75,28 +25,12 @@ class SpriteInfo extends React.Component {
this.props.onChangeVisibility(false);
}
render () {
const bufferedInputs = {
direction: this.state.direction === null ? this.props.direction : this.state.direction,
name: this.state.name === null ? this.props.name : this.state.name,
x: this.state.x === null ? this.props.x : this.state.x,
y: this.state.y === null ? this.props.y : this.state.y
};
return (
<SpriteInfoComponent
{...this.props}
{...bufferedInputs}
onBlurDirection={this.handleFlush}
onBlurName={this.handleFlush}
onBlurX={this.handleFlush}
onBlurY={this.handleFlush}
onChangeDirection={this.handleChangeDirection}
onChangeName={this.handleChangeName}
onChangeRotationStyle={this.handleChangeRotationStyle}
onChangeX={this.handleChangeX}
onChangeY={this.handleChangeY}
onClickNotVisible={this.handleClickNotVisible}
onClickVisible={this.handleClickVisible}
onKeyPress={this.handleKeyPress}
/>
);
}
+6 -6
View File
@@ -1,11 +1,11 @@
const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types');
const React = require('react');
const VM = require('scratch-vm');
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
import VM from 'scratch-vm';
const spriteLibraryContent = require('../lib/libraries/sprites.json');
import spriteLibraryContent from '../lib/libraries/sprites.json';
const LibraryComponent = require('../components/library/library.jsx');
import LibraryComponent from '../components/library/library.jsx';
class SpriteLibrary extends React.PureComponent {
constructor (props) {
+5 -5
View File
@@ -1,10 +1,10 @@
const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types');
const React = require('react');
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
const {connect} = require('react-redux');
import {connect} from 'react-redux';
const SpriteSelectorItemComponent = require('../components/sprite-selector-item/sprite-selector-item.jsx');
import SpriteSelectorItemComponent from '../components/sprite-selector-item/sprite-selector-item.jsx';
class SpriteSelectorItem extends React.Component {
constructor (props) {
+5 -5
View File
@@ -1,10 +1,10 @@
const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types');
const React = require('react');
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
const {connect} = require('react-redux');
import {connect} from 'react-redux';
const StageSelectorComponent = require('../components/stage-selector/stage-selector.jsx');
import StageSelectorComponent from '../components/stage-selector/stage-selector.jsx';
class StageSelector extends React.Component {
constructor (props) {
+7 -7
View File
@@ -1,11 +1,11 @@
const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types');
const React = require('react');
const Renderer = require('scratch-render');
const AudioEngine = require('scratch-audio');
const VM = require('scratch-vm');
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
import Renderer from 'scratch-render';
import AudioEngine from 'scratch-audio';
import VM from 'scratch-vm';
const StageComponent = require('../components/stage/stage.jsx');
import StageComponent from '../components/stage/stage.jsx';
class Stage extends React.Component {
constructor (props) {
+5 -5
View File
@@ -1,9 +1,9 @@
const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types');
const React = require('react');
const VM = require('scratch-vm');
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
import VM from 'scratch-vm';
const StopAllComponent = require('../components/stop-all/stop-all.jsx');
import StopAllComponent from '../components/stop-all/stop-all.jsx';
class StopAll extends React.Component {
constructor (props) {
+4 -4
View File
@@ -1,7 +1,7 @@
const bindAll = require('lodash.bindall');
const React = require('react');
import bindAll from 'lodash.bindall';
import React from 'react';
const {connect} = require('react-redux');
import {connect} from 'react-redux';
const {
openBackdropLibrary,
@@ -12,7 +12,7 @@ const {
closeSpriteLibrary
} = require('../reducers/modals');
const TargetPaneComponent = require('../components/target-pane/target-pane.jsx');
import TargetPaneComponent from '../components/target-pane/target-pane.jsx';
class TargetPane extends React.Component {
constructor (props) {
+10 -1
View File
@@ -1,3 +1,12 @@
$ui-pane-border: #D9D9D9;
$ui-pane-gray: #F9F9F9;
$blue: #4C97FF;
$ui-background-blue: #e8edf1;
$motion-primary: #4C97FF;
$motion-tertiary: #3373CC;
$red-primary: #FF661A;
$red-tertiary: #E64D00;
$sound-primary: #CF63CF;
$sound-tertiary: #A63FA6;
+14 -11
View File
@@ -1,15 +1,16 @@
const React = require('react');
const ReactDOM = require('react-dom');
const {Provider} = require('react-redux');
const {createStore, applyMiddleware} = require('redux');
const throttle = require('redux-throttle').default;
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import {createStore, applyMiddleware} from 'redux';
import throttle from 'redux-throttle';
import {IntlProvider} from 'react-intl';
const GUI = require('./containers/gui.jsx');
const log = require('./lib/log');
const ProjectLoader = require('./lib/project-loader');
const reducer = require('./reducers/gui');
import GUI from './containers/gui.jsx';
import log from './lib/log';
import ProjectLoader from './lib/project-loader';
import reducer from './reducers/gui';
const styles = require('./index.css');
import styles from './index.css';
class App extends React.Component {
constructor (props) {
@@ -68,6 +69,8 @@ const store = applyMiddleware(
);
ReactDOM.render((
<Provider store={store}>
<App />
<IntlProvider locale="en">
<App />
</IntlProvider>
</Provider>
), appTarget);
+53
View File
@@ -0,0 +1,53 @@
import SharedAudioContext from './shared-audio-context.js';
class AudioBufferPlayer {
constructor (samples) {
this.audioContext = new SharedAudioContext();
this.buffer = this.audioContext.createBuffer(1, samples.length, this.audioContext.sampleRate);
this.buffer.getChannelData(0).set(samples);
this.source = null;
this.startTime = null;
this.updateCallback = null;
this.trimStart = null;
this.trimEnd = null;
}
play (trimStart, trimEnd, onUpdate, onEnded) {
this.updateCallback = onUpdate;
this.trimStart = trimStart;
this.trimEnd = trimEnd;
this.startTime = Date.now();
const trimStartTime = this.buffer.duration * trimStart;
const trimmedDuration = this.buffer.duration * trimEnd - trimStartTime;
this.source = this.audioContext.createBufferSource();
this.source.onended = onEnded;
this.source.buffer = this.buffer;
this.source.connect(this.audioContext.destination);
this.source.start(0, trimStartTime, trimmedDuration);
this.update();
}
update () {
const timeSinceStart = (Date.now() - this.startTime) / 1000;
const percentage = timeSinceStart / this.buffer.duration;
if (percentage + this.trimStart < this.trimEnd && this.source.onended) {
requestAnimationFrame(this.update.bind(this));
this.updateCallback(percentage + this.trimStart);
} else {
this.updateCallback = null;
}
}
stop () {
if (this.source) {
this.source.onended = null; // Do not call onEnded callback if manually stopped
this.source.stop();
}
}
}
module.exports = AudioBufferPlayer;
+139
View File
@@ -0,0 +1,139 @@
import SharedAudioContext from './shared-audio-context.js';
class AudioRecorder {
constructor () {
this.audioContext = new SharedAudioContext();
this.bufferLength = 1024;
this.userMediaStream = null;
this.mediaStreamSource = null;
this.sourceNode = null;
this.scriptProcessorNode = null;
this.recordedSamples = 0;
this.recording = false;
this.started = false;
this.buffers = [];
this.disposed = false;
}
startListening (onStarted, onUpdate, onError) {
try {
navigator.getUserMedia({audio: true}, userMediaStream => {
if (!this.disposed) {
this.started = true;
onStarted();
this.attachUserMediaStream(userMediaStream, onUpdate);
}
}, e => {
if (!this.disposed) {
onError(e);
}
});
} catch (e) {
if (!this.disposed) {
onError(e);
}
}
}
startRecording () {
this.recording = true;
}
calculateRMS (samples) {
// Calculate RMS, adapted from https://github.com/Tonejs/Tone.js/blob/master/Tone/component/Meter.js#L88
const sum = samples.reduce((acc, v) => acc + Math.pow(v, 2), 0);
const rms = Math.sqrt(sum / samples.length);
// Scale it
const unity = 0.35;
const val = rms / unity;
// Scale the output curve
return Math.sqrt(val);
}
attachUserMediaStream (userMediaStream, onUpdate) {
this.userMediaStream = userMediaStream;
this.mediaStreamSource = this.audioContext.createMediaStreamSource(userMediaStream);
this.sourceNode = this.audioContext.createGain();
this.scriptProcessorNode = this.audioContext.createScriptProcessor(this.bufferLength, 2, 2);
this.scriptProcessorNode.onaudioprocess = processEvent => {
if (this.recording && !this.disposed) {
this.buffers.push(new Float32Array(processEvent.inputBuffer.getChannelData(0)));
}
};
this.analyserNode = this.audioContext.createAnalyser();
this.analyserNode.fftSize = 2048;
const bufferLength = this.analyserNode.frequencyBinCount;
const dataArray = new Float32Array(bufferLength);
const update = () => {
if (this.disposed) return;
requestAnimationFrame(update);
this.analyserNode.getFloatTimeDomainData(dataArray);
onUpdate(this.calculateRMS(dataArray));
};
requestAnimationFrame(update);
// Wire everything together, ending in the destination
this.mediaStreamSource.connect(this.sourceNode);
this.sourceNode.connect(this.analyserNode);
this.analyserNode.connect(this.scriptProcessorNode);
this.scriptProcessorNode.connect(this.audioContext.destination);
}
stop () {
const chunkLevels = this.buffers.map(buffer => this.calculateRMS(buffer));
const maxRMS = Math.max.apply(null, chunkLevels);
const threshold = maxRMS / 8;
let firstChunkAboveThreshold = null;
let lastChunkAboveThreshold = null;
for (let i = 0; i < chunkLevels.length; i++) {
if (chunkLevels[i] > threshold) {
if (firstChunkAboveThreshold === null) firstChunkAboveThreshold = i + 1;
lastChunkAboveThreshold = i + 1;
}
}
const trimStart = Math.max(2, firstChunkAboveThreshold - 2) / this.buffers.length;
const trimEnd = Math.min(this.buffers.length - 2, lastChunkAboveThreshold + 2) / this.buffers.length;
const buffer = new Float32Array(this.buffers.length * this.bufferLength);
let offset = 0;
for (let i = 0; i < this.buffers.length; i++) {
const bufferChunk = this.buffers[i];
buffer.set(bufferChunk, offset);
offset += bufferChunk.length;
}
return {
levels: chunkLevels,
samples: buffer,
sampleRate: this.audioContext.sampleRate,
trimStart: trimStart,
trimEnd: trimEnd
};
}
dispose () {
if (this.started) {
this.scriptProcessorNode.onaudioprocess = null;
this.scriptProcessorNode.disconnect();
this.analyserNode.disconnect();
this.sourceNode.disconnect();
this.mediaStreamSource.disconnect();
this.userMediaStream.getAudioTracks()[0].stop();
}
this.disposed = true;
}
}
module.exports = AudioRecorder;
+6
View File
@@ -0,0 +1,6 @@
// Wrap browser AudioContext because we shouldn't create more than one
const AUDIO_CONTEXT = new (window.AudioContext || window.webkitAudioContext)();
module.exports = function () {
return AUDIO_CONTEXT;
};
+1 -1
View File
@@ -1,4 +1,4 @@
const ScratchBlocks = require('scratch-blocks');
import ScratchBlocks from 'scratch-blocks';
module.exports = function (vm) {
+125 -13
View File
@@ -1231,7 +1231,17 @@
"isDraggable": false,
"indexInLibrary": 100000,
"visible": true,
"spriteInfo": {}
"spriteInfo": {},
"sounds": [
{
"soundName": "pop",
"soundID": -1,
"md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav",
"sampleCount": 258,
"rate": 11025,
"format": ""
}
]
}
},
{
@@ -1318,7 +1328,17 @@
"isDraggable": false,
"indexInLibrary": 100000,
"visible": true,
"spriteInfo": {}
"spriteInfo": {},
"sounds": [
{
"soundName": "pop",
"soundID": -1,
"md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav",
"sampleCount": 258,
"rate": 11025,
"format": ""
}
]
}
},
{
@@ -2667,7 +2687,17 @@
"isDraggable": false,
"indexInLibrary": 100000,
"visible": true,
"spriteInfo": {}
"spriteInfo": {},
"sounds": [
{
"soundName": "pop",
"soundID": -1,
"md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav",
"sampleCount": 258,
"rate": 11025,
"format": ""
}
]
}
},
{
@@ -4728,7 +4758,8 @@
"format": ""
}
],
"costumes": [{
"costumes": [
{
"costumeName": "Dinosaur4-16",
"baseLayerID": -1,
"baseLayerMD5": "4e330592c1f1b80a4b182cc00cf8d6bf.png",
@@ -4855,7 +4886,8 @@
"bitmapResolution": 2,
"rotationCenterX": 82,
"rotationCenterY": 134
}],
}
],
"currentCostumeIndex": 0,
"scratchX": 0,
"scratchY": 0,
@@ -5366,7 +5398,17 @@
"isDraggable": false,
"indexInLibrary": 100000,
"visible": true,
"spriteInfo": {}
"spriteInfo": {},
"sounds": [
{
"soundName": "pop",
"soundID": -1,
"md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav",
"sampleCount": 258,
"rate": 11025,
"format": ""
}
]
}
},
{
@@ -6698,7 +6740,17 @@
"isDraggable": false,
"indexInLibrary": 100000,
"visible": true,
"spriteInfo": {}
"spriteInfo": {},
"sounds": [
{
"soundName": "pop",
"soundID": -1,
"md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav",
"sampleCount": 258,
"rate": 11025,
"format": ""
}
]
}
},
{
@@ -7045,7 +7097,17 @@
"isDraggable": false,
"indexInLibrary": 100000,
"visible": true,
"spriteInfo": {}
"spriteInfo": {},
"sounds": [
{
"soundName": "pop",
"soundID": -1,
"md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav",
"sampleCount": 258,
"rate": 11025,
"format": ""
}
]
}
},
{
@@ -10594,7 +10656,17 @@
"isDraggable": false,
"indexInLibrary": 100000,
"visible": true,
"spriteInfo": {}
"spriteInfo": {},
"sounds": [
{
"soundName": "pop",
"soundID": -1,
"md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav",
"sampleCount": 258,
"rate": 11025,
"format": ""
}
]
}
},
{
@@ -12588,7 +12660,17 @@
"isDraggable": true,
"indexInLibrary": 100000,
"visible": true,
"spriteInfo": {}
"spriteInfo": {},
"sounds": [
{
"soundName": "pop",
"soundID": -1,
"md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav",
"sampleCount": 258,
"rate": 11025,
"format": ""
}
]
}
},
{
@@ -12692,7 +12774,17 @@
"isDraggable": true,
"indexInLibrary": 100000,
"visible": true,
"spriteInfo": {}
"spriteInfo": {},
"sounds": [
{
"soundName": "pop",
"soundID": -1,
"md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav",
"sampleCount": 258,
"rate": 11025,
"format": ""
}
]
}
},
{
@@ -13725,7 +13817,17 @@
"isDraggable": false,
"indexInLibrary": 100000,
"visible": true,
"spriteInfo": {}
"spriteInfo": {},
"sounds": [
{
"soundName": "pop",
"soundID": -1,
"md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav",
"sampleCount": 258,
"rate": 11025,
"format": ""
}
]
}
},
{
@@ -13818,7 +13920,17 @@
"isDraggable": false,
"indexInLibrary": 100000,
"visible": true,
"spriteInfo": {}
"spriteInfo": {},
"sounds": [
{
"soundName": "pop",
"soundID": -1,
"md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav",
"sampleCount": 258,
"rate": 11025,
"format": ""
}
]
}
},
{
+1 -1
View File
@@ -1,4 +1,4 @@
const minilog = require('minilog');
import minilog from 'minilog';
minilog.enable();
module.exports = minilog('gui');
+1 -1
View File
@@ -3,7 +3,7 @@
* - Convert opcode to a label and a category
* - Add missing XY position data if needed
*/
const OpcodeLabels = require('./opcode-labels.js');
import OpcodeLabels from './opcode-labels.js';
const PADDING = 5;
const MONITOR_HEIGHT = 23;
+2 -2
View File
@@ -1,6 +1,6 @@
const xhr = require('xhr');
import xhr from 'xhr';
const log = require('./log');
import log from './log';
class ProjectLoader {
constructor () {
+1 -1
View File
@@ -1,4 +1,4 @@
const ScratchStorage = require('scratch-storage');
import ScratchStorage from 'scratch-storage';
const PROJECT_SERVER = 'https://cdn.projects.scratch.mit.edu';
const ASSET_SERVER = 'https://cdn.assets.scratch.mit.edu';
+7 -7
View File
@@ -1,12 +1,12 @@
const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types');
const React = require('react');
const VM = require('scratch-vm');
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
import VM from 'scratch-vm';
const {connect} = require('react-redux');
import {connect} from 'react-redux';
const targets = require('../reducers/targets');
const monitors = require('../reducers/monitors');
import targets from '../reducers/targets';
import monitors from '../reducers/monitors';
/*
* Higher Order Component to manage events emitted by the VM
+1 -1
View File
@@ -1,4 +1,4 @@
const {combineReducers} = require('redux');
import {combineReducers} from 'redux';
module.exports = combineReducers({
modals: require('./modals'),
+9 -1
View File
@@ -5,12 +5,14 @@ const MODAL_BACKDROP_LIBRARY = 'backdropLibrary';
const MODAL_COSTUME_LIBRARY = 'costumeLibrary';
const MODAL_SOUND_LIBRARY = 'soundLibrary';
const MODAL_SPRITE_LIBRARY = 'spriteLibrary';
const MODAL_SOUND_RECORDER = 'soundRecorder';
const initialState = {
[MODAL_BACKDROP_LIBRARY]: false,
[MODAL_COSTUME_LIBRARY]: false,
[MODAL_SOUND_LIBRARY]: false,
[MODAL_SPRITE_LIBRARY]: false
[MODAL_SPRITE_LIBRARY]: false,
[MODAL_SOUND_RECORDER]: false
};
const reducer = function (state, action) {
@@ -52,6 +54,9 @@ reducer.openSoundLibrary = function () {
reducer.openSpriteLibrary = function () {
return reducer.openModal(MODAL_SPRITE_LIBRARY);
};
reducer.openSoundRecorder = function () {
return reducer.openModal(MODAL_SOUND_RECORDER);
};
reducer.closeBackdropLibrary = function () {
return reducer.closeModal(MODAL_BACKDROP_LIBRARY);
};
@@ -64,4 +69,7 @@ reducer.closeSpriteLibrary = function () {
reducer.closeSoundLibrary = function () {
return reducer.closeModal(MODAL_SOUND_LIBRARY);
};
reducer.closeSoundRecorder = function () {
return reducer.closeModal(MODAL_SOUND_RECORDER);
};
module.exports = reducer;
+1 -1
View File
@@ -1,5 +1,5 @@
const UPDATE_MONITORS = 'scratch-gui/monitors/UPDATE_MONITORS';
const {OrderedMap} = require('immutable');
import {OrderedMap} from 'immutable';
const initialState = OrderedMap();
+2 -2
View File
@@ -1,5 +1,5 @@
const VM = require('scratch-vm');
const Storage = require('../lib/storage');
import VM from 'scratch-vm';
import Storage from '../lib/storage';
const SET_VM = 'scratch-gui/vm/SET_VM';
const defaultVM = new VM();
+1 -1
View File
@@ -36,7 +36,7 @@ module.exports = {
include: path.resolve(__dirname, 'src'),
options: {
plugins: ['transform-object-rest-spread'],
presets: [['es2015', {modules: false}], 'react']
presets: ['es2015', 'react']
}
},
{