Merge branch 'develop' into feature/scroll-by-id
This commit is contained in:
17 files changed
+483
-68
No files matched your search
@@ -6,5 +6,5 @@
|
||||
["react-intl", {
|
||||
"messagesDir": "./translations/messages/"
|
||||
}]],
|
||||
"presets": [["env", {"targets": {browsers: ["last 3 versions", "Safari >= 8", "iOS >= 8"]}}], "react"]
|
||||
"presets": [["env", {"targets": {"browsers": ["last 3 versions", "Safari >= 8", "iOS >= 8"]}}], "react"]
|
||||
}
|
||||
@@ -15,6 +15,8 @@ _Explain why these changes should be made_
|
||||
_Please show how you have added tests to cover your changes_
|
||||
|
||||
### Browser Coverage
|
||||
Check the OS/browser combinations tested (At least 2)
|
||||
|
||||
Mac
|
||||
* [ ] Chrome
|
||||
* [ ] Firefox
|
||||
|
||||
+4
-3
@@ -84,6 +84,7 @@
|
||||
"react-tabs": "2.2.1",
|
||||
"react-test-renderer": "16.2.0",
|
||||
"react-tooltip": "3.4.0",
|
||||
"react-virtualized": "9.18.5",
|
||||
"redux": "3.7.2",
|
||||
"redux-mock-store": "^1.2.3",
|
||||
"redux-throttle": "0.1.1",
|
||||
@@ -91,10 +92,10 @@
|
||||
"scratch-audio": "0.1.0-prerelease.1523977528",
|
||||
"scratch-blocks": "0.1.0-prerelease.1526329848",
|
||||
"scratch-l10n": "2.0.20180108132626",
|
||||
"scratch-paint": "0.2.0-prerelease.20180504180528",
|
||||
"scratch-render": "0.1.0-prerelease.20180508205430",
|
||||
"scratch-paint": "0.2.0-prerelease.20180514184134",
|
||||
"scratch-render": "0.1.0-prerelease.20180514172756",
|
||||
"scratch-storage": "0.4.1",
|
||||
"scratch-svg-renderer": "0.1.0-prerelease.20180423193917",
|
||||
"scratch-svg-renderer": "0.1.0-prerelease.20180514170126",
|
||||
"scratch-vm": "0.1.0-prerelease.1526320682",
|
||||
"selenium-webdriver": "3.6.0",
|
||||
"startaudiocontext": "1.2.1",
|
||||
|
||||
@@ -13,5 +13,10 @@ module.exports = {
|
||||
'import/no-amd': 'error',
|
||||
'import/no-nodejs-modules': 'error',
|
||||
'react/jsx-no-literals': 'error'
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: '16.2' // Prevent 16.3 lifecycle method errors
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -24,6 +24,7 @@ const MonitorList = props => (
|
||||
opcode={monitorData.opcode}
|
||||
params={monitorData.params}
|
||||
spriteName={monitorData.spriteName}
|
||||
targetId={monitorData.targetId}
|
||||
value={monitorData.value}
|
||||
width={monitorData.width}
|
||||
x={monitorData.x}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import bindAll from 'lodash.bindall';
|
||||
|
||||
import styles from './monitor.css';
|
||||
import {List} from 'react-virtualized';
|
||||
|
||||
class ListMonitorScroller extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'rowRenderer',
|
||||
'noRowsRenderer',
|
||||
'handleEventFactory'
|
||||
]);
|
||||
}
|
||||
handleEventFactory (index) {
|
||||
return () => this.props.onActivate(index);
|
||||
}
|
||||
noRowsRenderer () {
|
||||
return (
|
||||
<div className={styles.listEmpty}>
|
||||
{'(empty)' /* TODO waiting for design before translation */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
rowRenderer ({index, key, style}) {
|
||||
return (
|
||||
<div
|
||||
className={styles.listRow}
|
||||
key={key}
|
||||
style={style}
|
||||
>
|
||||
<div className={styles.listIndex}>{index + 1 /* one indexed */}</div>
|
||||
<div
|
||||
className={styles.listValue}
|
||||
dataIndex={index}
|
||||
style={{background: this.props.categoryColor}}
|
||||
onClick={this.handleEventFactory(index)}
|
||||
>
|
||||
{this.props.activeIndex === index ? (
|
||||
<div className={styles.inputWrapper}>
|
||||
<input
|
||||
autoFocus
|
||||
autoComplete={false}
|
||||
className={classNames(styles.listInput, 'no-drag')}
|
||||
spellCheck={false}
|
||||
type="text"
|
||||
value={this.props.activeValue}
|
||||
onBlur={this.props.onDeactivate}
|
||||
onChange={this.props.onInput}
|
||||
onFocus={this.props.onFocus}
|
||||
onKeyDown={this.props.onKeyPress} // key down to get ahead of blur
|
||||
/>
|
||||
<div
|
||||
className={styles.removeButton}
|
||||
onMouseDown={this.props.onRemove} // mousedown to get ahead of blur
|
||||
>
|
||||
{'✖︎'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
) : (
|
||||
<div className={styles.valueInner}>{this.props.values[index]}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
render () {
|
||||
const {height, values, width, activeIndex, activeValue} = this.props;
|
||||
// Keep the active index in view if defined, else must be undefined for List component
|
||||
const scrollToIndex = activeIndex === null ? undefined : activeIndex; /* eslint-disable-line no-undefined */
|
||||
return (
|
||||
<List
|
||||
activeIndex={activeIndex}
|
||||
activeValue={activeValue}
|
||||
height={(height) - 44 /* Header/footer size, approx */}
|
||||
noRowsRenderer={this.noRowsRenderer}
|
||||
rowCount={values.length}
|
||||
rowHeight={24 /* Row size is same for all rows */}
|
||||
rowRenderer={this.rowRenderer}
|
||||
scrollToIndex={scrollToIndex} /* eslint-disable-line no-undefined */
|
||||
values={values}
|
||||
width={width}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ListMonitorScroller.propTypes = {
|
||||
activeIndex: PropTypes.number,
|
||||
activeValue: PropTypes.string,
|
||||
categoryColor: PropTypes.string,
|
||||
height: PropTypes.number,
|
||||
onActivate: PropTypes.func,
|
||||
onDeactivate: PropTypes.func,
|
||||
onFocus: PropTypes.func,
|
||||
onInput: PropTypes.func,
|
||||
onKeyPress: PropTypes.func,
|
||||
onRemove: PropTypes.func,
|
||||
values: PropTypes.arrayOf(PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number
|
||||
])),
|
||||
width: PropTypes.number
|
||||
};
|
||||
export default ListMonitorScroller;
|
||||
@@ -1,67 +1,69 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import styles from './monitor.css';
|
||||
import ListMonitorScroller from './list-monitor-scroller.jsx';
|
||||
|
||||
const ListMonitor = ({categoryColor, label, width, height, value}) => (
|
||||
const ListMonitor = ({label, width, height, value, onResizeMouseDown, onAdd, ...rowProps}) => (
|
||||
<div
|
||||
className={styles.listMonitor}
|
||||
style={{
|
||||
width: `${width || 80}px`,
|
||||
height: `${height || 200}px`
|
||||
width: `${width}px`,
|
||||
height: `${height}px`
|
||||
}}
|
||||
>
|
||||
<div className={styles.listHeader}>
|
||||
{label}
|
||||
</div>
|
||||
<div className={styles.listBody}>
|
||||
{!value || value.length === 0 ? (
|
||||
<div className={styles.listEmpty}>
|
||||
{'(empty)' /* @todo not translating, awaiting design */}
|
||||
</div>
|
||||
) : value.map((v, i) => (
|
||||
<div
|
||||
className={styles.listRow}
|
||||
key={`label-${i}`}
|
||||
>
|
||||
<div className={styles.listIndex}>{i + 1 /* one indexed */}</div>
|
||||
<div
|
||||
className={styles.listValue}
|
||||
style={{background: categoryColor}}
|
||||
>
|
||||
<div className={styles.valueInner}>{v}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<ListMonitorScroller
|
||||
height={height}
|
||||
values={value}
|
||||
width={width}
|
||||
{...rowProps}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.listFooter}>
|
||||
<div className={styles.footerButton}>
|
||||
{/* @todo add button here */}
|
||||
<div
|
||||
className={styles.addButton}
|
||||
onClick={onAdd}
|
||||
>
|
||||
{'+' /* TODO waiting on asset */}
|
||||
</div>
|
||||
<div className={styles.footerLength}>
|
||||
<span className={styles.lengthNumber}>
|
||||
{value.length}
|
||||
</span>
|
||||
{`length ${value.length}`}
|
||||
</div>
|
||||
<div className={styles.resizeHandle}>
|
||||
{/* @todo resize handle */}
|
||||
<div
|
||||
className={classNames(styles.resizeHandle, 'no-drag')}
|
||||
onMouseDown={onResizeMouseDown}
|
||||
>
|
||||
{'=' /* TODO waiting on asset */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
ListMonitor.propTypes = {
|
||||
activeIndex: PropTypes.number,
|
||||
categoryColor: PropTypes.string.isRequired,
|
||||
height: PropTypes.number,
|
||||
label: PropTypes.string.isRequired,
|
||||
value: PropTypes.arrayOf(PropTypes.oneOfType([
|
||||
onActivate: PropTypes.func,
|
||||
onAdd: PropTypes.func,
|
||||
onResizeMouseDown: PropTypes.func,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number
|
||||
])),
|
||||
PropTypes.number,
|
||||
PropTypes.arrayOf(PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number
|
||||
]))
|
||||
]),
|
||||
width: PropTypes.number
|
||||
};
|
||||
|
||||
ListMonitor.defaultProps = {
|
||||
width: 80,
|
||||
width: 110,
|
||||
height: 200
|
||||
};
|
||||
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
}
|
||||
|
||||
.list-monitor {
|
||||
min-width: 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -83,6 +82,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
height: calc(100% - 44px);
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
border-radius: calc($space / 2);
|
||||
border: 1px solid $ui-black-transparent;
|
||||
flex-grow: 1;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.list-footer {
|
||||
@@ -137,3 +138,35 @@
|
||||
padding: 3px 5px;
|
||||
min-height: 22px;
|
||||
}
|
||||
|
||||
.list-input {
|
||||
padding: 3px 5px;
|
||||
border: 0;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
color: $ui-white;
|
||||
outline: none;
|
||||
font-size: 0.75rem;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.remove-button {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 3px;
|
||||
cursor: pointer;
|
||||
color: $ui-white;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
cursor: pointer;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.resize-handle {
|
||||
cursor: nwse-resize;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.footer-length {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -7,8 +7,8 @@ import {ContextMenu, MenuItem} from '../context-menu/context-menu.jsx';
|
||||
import Box from '../box/box.jsx';
|
||||
import DefaultMonitor from './default-monitor.jsx';
|
||||
import LargeMonitor from './large-monitor.jsx';
|
||||
import SliderMonitor from './slider-monitor.jsx';
|
||||
import ListMonitor from './list-monitor.jsx';
|
||||
import SliderMonitor from '../../containers/slider-monitor.jsx';
|
||||
import ListMonitor from '../../containers/list-monitor.jsx';
|
||||
|
||||
import styles from './monitor.css';
|
||||
|
||||
@@ -29,7 +29,10 @@ const modes = {
|
||||
};
|
||||
|
||||
const MonitorComponent = props => (
|
||||
<ContextMenuTrigger id={`monitor-${props.label}`}>
|
||||
<ContextMenuTrigger
|
||||
holdToDisplay={props.mode === 'slider' ? -1 : 1000}
|
||||
id={`monitor-${props.label}`}
|
||||
>
|
||||
<Draggable
|
||||
bounds=".monitor-overlay" // Class for monitor container
|
||||
cancel=".no-drag" // Class used for slider input to prevent drag
|
||||
@@ -41,14 +44,9 @@ const MonitorComponent = props => (
|
||||
componentRef={props.componentRef}
|
||||
onDoubleClick={props.mode === 'list' ? null : props.onNextMode}
|
||||
>
|
||||
{(modes[props.mode] || modes.default)({ // Use default until other modes arrive
|
||||
{React.createElement(modes[props.mode], {
|
||||
categoryColor: categories[props.category],
|
||||
label: props.label,
|
||||
value: props.value,
|
||||
width: props.width,
|
||||
height: props.height,
|
||||
min: props.min,
|
||||
max: props.max
|
||||
...props
|
||||
})}
|
||||
</Box>
|
||||
</Draggable>
|
||||
@@ -90,25 +88,13 @@ const monitorModes = Object.keys(modes);
|
||||
MonitorComponent.propTypes = {
|
||||
category: PropTypes.oneOf(Object.keys(categories)),
|
||||
componentRef: PropTypes.func.isRequired,
|
||||
height: PropTypes.number,
|
||||
label: PropTypes.string.isRequired,
|
||||
max: PropTypes.number,
|
||||
min: PropTypes.number,
|
||||
mode: PropTypes.oneOf(monitorModes),
|
||||
onDragEnd: PropTypes.func.isRequired,
|
||||
onNextMode: PropTypes.func.isRequired,
|
||||
onSetModeToDefault: PropTypes.func.isRequired,
|
||||
onSetModeToLarge: PropTypes.func.isRequired,
|
||||
onSetModeToSlider: PropTypes.func,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
PropTypes.arrayOf(PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number
|
||||
]))
|
||||
]),
|
||||
width: PropTypes.number
|
||||
onSetModeToSlider: PropTypes.func
|
||||
};
|
||||
|
||||
MonitorComponent.defaultProps = {
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './monitor.css';
|
||||
|
||||
const SliderMonitor = ({categoryColor, label, min, max, value}) => (
|
||||
const SliderMonitor = ({categoryColor, label, min, max, value, onSliderUpdate}) => (
|
||||
<div className={styles.defaultMonitor}>
|
||||
<div className={styles.row}>
|
||||
<div className={styles.label}>
|
||||
@@ -22,6 +22,7 @@ const SliderMonitor = ({categoryColor, label, min, max, value}) => (
|
||||
min={min}
|
||||
type="range"
|
||||
value={value}
|
||||
onChange={onSliderUpdate}
|
||||
// @todo onChange callback
|
||||
/>
|
||||
</div>
|
||||
@@ -34,7 +35,7 @@ SliderMonitor.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
max: PropTypes.number,
|
||||
min: PropTypes.number,
|
||||
// @todo callback for change events
|
||||
onSliderUpdate: PropTypes.func.isRequired,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
import bindAll from 'lodash.bindall';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import VM from 'scratch-vm';
|
||||
import {connect} from 'react-redux';
|
||||
import {getEventXY} from '../lib/touch-utils';
|
||||
import {getVariableValue, setVariableValue} from '../lib/variable-utils';
|
||||
import ListMonitorComponent from '../components/monitor/list-monitor.jsx';
|
||||
|
||||
class ListMonitor extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleActivate',
|
||||
'handleDeactivate',
|
||||
'handleInput',
|
||||
'handleRemove',
|
||||
'handleKeyPress',
|
||||
'handleFocus',
|
||||
'handleAdd',
|
||||
'handleResizeMouseDown'
|
||||
]);
|
||||
|
||||
this.state = {
|
||||
activeIndex: null,
|
||||
activeValue: null,
|
||||
// TODO These will need to be sent back to the VM for saving
|
||||
width: props.width || 100,
|
||||
height: props.height || 200
|
||||
};
|
||||
}
|
||||
|
||||
handleActivate (index) {
|
||||
this.setState({
|
||||
activeIndex: index,
|
||||
activeValue: this.props.value[index]
|
||||
});
|
||||
}
|
||||
|
||||
handleDeactivate () {
|
||||
// Submit any in-progress value edits on blur
|
||||
if (this.state.activeIndex !== null) {
|
||||
const {vm, targetId, id: variableId} = this.props;
|
||||
const newListValue = getVariableValue(vm, targetId, variableId);
|
||||
newListValue[this.state.activeIndex] = this.state.activeValue;
|
||||
setVariableValue(vm, targetId, variableId, newListValue);
|
||||
this.setState({activeIndex: null, activeValue: null});
|
||||
}
|
||||
}
|
||||
|
||||
handleFocus (e) {
|
||||
// Select all the text in the input when it is focused.
|
||||
e.target.select();
|
||||
}
|
||||
|
||||
handleKeyPress (e) {
|
||||
// Special case for tab, arrow keys and enter.
|
||||
// Tab / shift+tab navigate down / up the list.
|
||||
// Arrow down / arrow up navigate down / up the list.
|
||||
// Enter / shift+enter insert new blank item below / above.
|
||||
const previouslyActiveIndex = this.state.activeIndex;
|
||||
const {vm, targetId, id: variableId} = this.props;
|
||||
|
||||
let navigateDirection = 0;
|
||||
if (e.key === 'Tab') navigateDirection = e.shiftKey ? -1 : 1;
|
||||
else if (e.key === 'ArrowUp') navigateDirection = -1;
|
||||
else if (e.key === 'ArrowDown') navigateDirection = 1;
|
||||
if (navigateDirection) {
|
||||
this.handleDeactivate(); // Submit in-progress edits
|
||||
const newIndex = (previouslyActiveIndex + navigateDirection) % this.props.value.length;
|
||||
this.setState({
|
||||
activeIndex: newIndex,
|
||||
activeValue: this.props.value[newIndex]
|
||||
});
|
||||
e.preventDefault(); // Stop default tab behavior, handled by this state change
|
||||
} else if (e.key === 'Enter') {
|
||||
this.handleDeactivate(); // Submit in-progress edits
|
||||
const newListItemValue = ''; // Enter adds a blank item
|
||||
const newValueOffset = e.shiftKey ? 0 : 1; // Shift-enter inserts above
|
||||
const listValue = getVariableValue(vm, targetId, variableId);
|
||||
const newListValue = listValue.slice(0, previouslyActiveIndex + newValueOffset)
|
||||
.concat([newListItemValue])
|
||||
.concat(listValue.slice(previouslyActiveIndex + newValueOffset));
|
||||
setVariableValue(vm, targetId, variableId, newListValue);
|
||||
const newIndex = (previouslyActiveIndex + newValueOffset) % newListValue.length;
|
||||
this.setState({
|
||||
activeIndex: newIndex,
|
||||
activeValue: newListItemValue
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleInput (e) {
|
||||
this.setState({activeValue: e.target.value});
|
||||
}
|
||||
|
||||
handleRemove (e) {
|
||||
e.preventDefault(); // Default would blur input, prevent that.
|
||||
e.stopPropagation(); // Bubbling would activate, which will be handled here
|
||||
const {vm, targetId, id: variableId} = this.props;
|
||||
const listValue = getVariableValue(vm, targetId, variableId);
|
||||
const newListValue = listValue.slice(0, this.state.activeIndex)
|
||||
.concat(listValue.slice(this.state.activeIndex + 1));
|
||||
setVariableValue(vm, targetId, variableId, newListValue);
|
||||
this.handleActivate(Math.min(newListValue.length - 1, this.state.activeIndex));
|
||||
}
|
||||
|
||||
handleAdd () {
|
||||
// Add button appends a blank value and switches to it
|
||||
const {vm, targetId, id: variableId} = this.props;
|
||||
const newListValue = getVariableValue(vm, targetId, variableId).concat(['']);
|
||||
setVariableValue(vm, targetId, variableId, newListValue);
|
||||
this.setState({activeIndex: newListValue.length - 1, activeValue: ''});
|
||||
}
|
||||
|
||||
handleResizeMouseDown (e) {
|
||||
this.initialPosition = getEventXY(e);
|
||||
this.initialWidth = this.state.width;
|
||||
this.initialHeight = this.state.height;
|
||||
|
||||
const onMouseMove = ev => {
|
||||
const newPosition = getEventXY(ev);
|
||||
const dx = newPosition.x - this.initialPosition.x;
|
||||
const dy = newPosition.y - this.initialPosition.y;
|
||||
this.setState({
|
||||
width: Math.max(Math.min(this.initialWidth + dx, 480), 100),
|
||||
height: Math.max(Math.min(this.initialHeight + dy, 360), 60)
|
||||
});
|
||||
};
|
||||
|
||||
const onMouseUp = ev => {
|
||||
onMouseMove(ev); // Make sure width/height are up-to-date
|
||||
// TODO send these new sizes to the VM for saving
|
||||
window.removeEventListener('mousemove', onMouseMove);
|
||||
window.removeEventListener('mouseup', onMouseUp);
|
||||
};
|
||||
|
||||
window.addEventListener('mousemove', onMouseMove);
|
||||
window.addEventListener('mouseup', onMouseUp);
|
||||
|
||||
}
|
||||
render () {
|
||||
const {
|
||||
vm, // eslint-disable-line no-unused-vars
|
||||
...props
|
||||
} = this.props;
|
||||
return (
|
||||
<ListMonitorComponent
|
||||
{...props}
|
||||
activeIndex={this.state.activeIndex}
|
||||
activeValue={this.state.activeValue}
|
||||
height={this.state.height}
|
||||
width={this.state.width}
|
||||
onActivate={this.handleActivate}
|
||||
onAdd={this.handleAdd}
|
||||
onDeactivate={this.handleDeactivate}
|
||||
onFocus={this.handleFocus}
|
||||
onInput={this.handleInput}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
onRemove={this.handleRemove}
|
||||
onResizeMouseDown={this.handleResizeMouseDown}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ListMonitor.propTypes = {
|
||||
height: PropTypes.number,
|
||||
id: PropTypes.string,
|
||||
targetId: PropTypes.string,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.number,
|
||||
PropTypes.string
|
||||
]),
|
||||
vm: PropTypes.instanceOf(VM),
|
||||
width: PropTypes.number,
|
||||
x: PropTypes.number,
|
||||
y: PropTypes.number
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({vm: state.vm});
|
||||
|
||||
export default connect(mapStateToProps)(ListMonitor);
|
||||
@@ -5,6 +5,8 @@ import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import {moveMonitorRect} from '../reducers/monitor-layout';
|
||||
|
||||
import errorBoundaryHOC from '../lib/error-boundary-hoc.jsx';
|
||||
|
||||
import MonitorListComponent from '../components/monitor-list/monitor-list.jsx';
|
||||
|
||||
class MonitorList extends React.Component {
|
||||
@@ -37,7 +39,9 @@ const mapDispatchToProps = dispatch => ({
|
||||
moveMonitorRect: (id, x, y) => dispatch(moveMonitorRect(id, x, y))
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(MonitorList);
|
||||
export default errorBoundaryHOC('Monitors')(
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(MonitorList)
|
||||
);
|
||||
@@ -107,6 +107,7 @@ class Monitor extends React.Component {
|
||||
max={this.props.max}
|
||||
min={this.props.min}
|
||||
mode={this.state.mode}
|
||||
targetId={this.props.targetId}
|
||||
width={this.props.width}
|
||||
onDragEnd={this.handleDragEnd}
|
||||
onNextMode={this.handleNextMode}
|
||||
@@ -135,6 +136,7 @@ Monitor.propTypes = {
|
||||
removeMonitorRect: PropTypes.func.isRequired,
|
||||
resizeMonitorRect: PropTypes.func.isRequired,
|
||||
spriteName: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
|
||||
targetId: PropTypes.string,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import bindAll from 'lodash.bindall';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import VM from 'scratch-vm';
|
||||
import {setVariableValue} from '../lib/variable-utils';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import SliderMonitorComponent from '../components/monitor/slider-monitor.jsx';
|
||||
|
||||
class SliderMonitor extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleSliderUpdate'
|
||||
]);
|
||||
|
||||
this.state = {
|
||||
value: Number(props.value)
|
||||
};
|
||||
}
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (this.state.value !== nextProps.value) {
|
||||
this.setState({value: nextProps.value});
|
||||
}
|
||||
}
|
||||
handleSliderUpdate (e) {
|
||||
this.setState({value: Number(e.target.value)});
|
||||
const {vm, targetId, id: variableId} = this.props;
|
||||
setVariableValue(vm, targetId, variableId, Number(e.target.value));
|
||||
}
|
||||
render () {
|
||||
const {
|
||||
vm, // eslint-disable-line no-unused-vars
|
||||
value, // eslint-disable-line no-unused-vars
|
||||
...props
|
||||
} = this.props;
|
||||
return (
|
||||
<SliderMonitorComponent
|
||||
{...props}
|
||||
value={this.state.value}
|
||||
onSliderUpdate={this.handleSliderUpdate}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SliderMonitor.propTypes = {
|
||||
id: PropTypes.string,
|
||||
targetId: PropTypes.string,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.number,
|
||||
PropTypes.string
|
||||
]),
|
||||
vm: PropTypes.instanceOf(VM)
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({vm: state.vm});
|
||||
|
||||
export default connect(mapStateToProps)(SliderMonitor);
|
||||
@@ -7,6 +7,7 @@ import {connect} from 'react-redux';
|
||||
|
||||
import {getEventXY} from '../lib/touch-utils';
|
||||
import VideoProvider from '../lib/video/video-provider';
|
||||
import {SVGRenderer as V2SVGAdapter} from 'scratch-svg-renderer';
|
||||
|
||||
import StageComponent from '../components/stage/stage.jsx';
|
||||
|
||||
@@ -57,6 +58,7 @@ class Stage extends React.Component {
|
||||
this.updateRect();
|
||||
this.renderer = new Renderer(this.canvas);
|
||||
this.props.vm.attachRenderer(this.renderer);
|
||||
this.props.vm.attachV2SVGAdapter(new V2SVGAdapter());
|
||||
this.props.vm.runtime.addListener('QUESTION', this.questionListener);
|
||||
this.props.vm.setVideoProvider(new VideoProvider());
|
||||
}
|
||||
@@ -81,6 +83,7 @@ class Stage extends React.Component {
|
||||
this.detachMouseEvents(this.canvas);
|
||||
this.detachRectEvents();
|
||||
this.stopColorPickingLoop();
|
||||
this.props.vm.runtime.removeListener('QUESTION', this.questionListener);
|
||||
}
|
||||
questionListener (question) {
|
||||
this.setState({question: question});
|
||||
|
||||
@@ -19,12 +19,12 @@ export default [{
|
||||
id: '83a9787d4cb6f3b7632b4ddfebf74367',
|
||||
assetType: 'Sound',
|
||||
dataFormat: 'WAV',
|
||||
data: new Uint16Array(popWav)
|
||||
data: new Uint8Array(popWav)
|
||||
}, {
|
||||
id: '83c36d806dc92327b9e7049a565c6bff',
|
||||
assetType: 'Sound',
|
||||
dataFormat: 'WAV',
|
||||
data: new Uint16Array(meowWav)
|
||||
data: new Uint8Array(meowWav)
|
||||
}, {
|
||||
id: '739b5e2a2435f6e1ec2993791b423146',
|
||||
assetType: 'ImageBitmap',
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Utility functions for updating variables in the VM
|
||||
// TODO (VM#1145) these should be moved to top-level VM API
|
||||
const getVariable = (vm, targetId, variableId) => {
|
||||
const target = targetId ?
|
||||
vm.runtime.getTargetById(targetId) :
|
||||
vm.runtime.getTargetForStage();
|
||||
return target.variables[variableId];
|
||||
};
|
||||
|
||||
const getVariableValue = (vm, targetId, variableId) => {
|
||||
const variable = getVariable(vm, targetId, variableId);
|
||||
// If array, return a new copy for mutating, ensuring that updates stay immutable.
|
||||
if (variable.value instanceof Array) return variable.value.slice();
|
||||
return variable.value;
|
||||
};
|
||||
|
||||
const setVariableValue = (vm, targetId, variableId, value) => {
|
||||
getVariable(vm, targetId, variableId).value = value;
|
||||
};
|
||||
|
||||
export {
|
||||
getVariableValue,
|
||||
setVariableValue
|
||||
};
|
||||
Reference in new issue
Block a user