Merge branch 'develop' into attachSvgRenderer

This commit is contained in:
DD committed 2018-05-10 17:28:39 -04:00
commit bee4ffb8a5
17 files changed
+408 -5896

No files matched your search

File diff suppressed because it is too large. Load diff

Before

Width:  |  Height:  |  Size: 425 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

+4
View File
@@ -11,6 +11,10 @@
background-color: $ui-modal-overlay;
}
.modal-content * {
box-sizing: border-box;
}
.modal-content {
margin: 100px auto;
outline: none;
+19 -11
View File
@@ -12,17 +12,25 @@ const MonitorList = props => (
// Use static `monitor-overlay` class for bounds of draggables
className={classNames(styles.monitorList, 'monitor-overlay')}
>
{props.monitors.valueSeq().map(monitorData => (
<Monitor
id={monitorData.id}
key={monitorData.id}
opcode={monitorData.opcode}
params={monitorData.params}
spriteName={monitorData.spriteName}
value={monitorData.value}
onDragEnd={props.onMonitorChange}
/>
))}
{props.monitors.valueSeq().filter(m => m.visible)
.map(monitorData => (
<Monitor
height={monitorData.height}
id={monitorData.id}
key={monitorData.id}
max={monitorData.sliderMax}
min={monitorData.sliderMin}
mode={monitorData.mode}
opcode={monitorData.opcode}
params={monitorData.params}
spriteName={monitorData.spriteName}
value={monitorData.value}
width={monitorData.width}
x={monitorData.x}
y={monitorData.y}
onDragEnd={props.onMonitorChange}
/>
))}
</Box>
);
+10 -8
View File
@@ -4,14 +4,16 @@ import styles from './monitor.css';
const DefaultMonitor = ({categoryColor, label, value}) => (
<div className={styles.defaultMonitor}>
<div className={styles.label}>
{label}
</div>
<div
className={styles.value}
style={{background: categoryColor}}
>
{value}
<div className={styles.row}>
<div className={styles.label}>
{label}
</div>
<div
className={styles.value}
style={{background: categoryColor}}
>
{value}
</div>
</div>
</div>
);
+68
View File
@@ -0,0 +1,68 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './monitor.css';
const ListMonitor = ({categoryColor, label, width, height, value}) => (
<div
className={styles.listMonitor}
style={{
width: `${width || 80}px`,
height: `${height || 200}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>
))}
</div>
<div className={styles.listFooter}>
<div className={styles.footerButton}>
{/* @todo add button here */}
</div>
<div className={styles.footerLength}>
<span className={styles.lengthNumber}>
{value.length}
</span>
</div>
<div className={styles.resizeHandle}>
{/* @todo resize handle */}
</div>
</div>
</div>
);
ListMonitor.propTypes = {
categoryColor: PropTypes.string.isRequired,
height: PropTypes.number,
label: PropTypes.string.isRequired,
value: PropTypes.arrayOf(PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
])),
width: PropTypes.number
};
ListMonitor.defaultProps = {
width: 80,
height: 200
};
export default ListMonitor;
+86 -2
View File
@@ -25,6 +25,7 @@
.default-monitor {
display: flex;
flex-direction: column;
padding: 3px;
}
@@ -45,11 +46,94 @@
}
.large-value {
min-height: 1.5rem;
min-height: 1.4rem;
min-width: 3rem;
padding: 0.25rem;
padding: 0.1rem 0.25rem;
text-align: center;
color: white;
font-size: 1rem;
transform: translateZ(0); /* Fixes flickering in Safari */
}
.row {
display: flex;
flex-direction: row;
}
.list-monitor {
min-width: 80px;
display: flex;
flex-direction: column;
}
.list-header {
background: $ui-white;
border-bottom: 1px solid $ui-black-transparent;
text-align: center;
padding: 3px;
font-size: 0.75rem;
font-weight: bold;
color: $text-primary;
width: 100%;
}
.list-body {
background: $ui-primary;
width: 100%;
display: flex;
flex-direction: column;
overflow-y: scroll;
height: calc(100% - 44px);
}
.list-row {
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
padding: 2px;
flex-shrink: 0;
}
.list-index {
font-weight: bold;
color: $text-primary;
margin: 0 3px;
}
.list-value {
min-width: 40px;
text-align: left;
color: white;
margin: 0 3px;
border-radius: calc($space / 2);
border: 1px solid $ui-black-transparent;
flex-grow: 1;
}
.list-footer {
background: $ui-white;
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 3px;
font-size: 0.75rem;
font-weight: bold;
color: $text-primary;
}
.list-empty {
text-align: center;
width: 100%;
padding: 5px;
}
.input-wrapper {
position: relative;
overflow: hidden;
}
.value-inner {
padding: 3px 5px;
min-height: 22px;
}
+59 -29
View File
@@ -7,6 +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 styles from './monitor.css';
@@ -19,74 +21,102 @@ const categories = {
list: '#FC662C'
};
const types = {
const modes = {
default: DefaultMonitor,
large: LargeMonitor
large: LargeMonitor,
slider: SliderMonitor,
list: ListMonitor
};
const MonitorComponent = props => (
<ContextMenuTrigger id={`monitor-${props.label}`}>
<Draggable
bounds=".monitor-overlay" // Class for monitor container
cancel=".no-drag" // Class used for slider input to prevent drag
defaultClassNameDragging={styles.dragging}
onStop={props.onDragEnd}
>
<Box
className={styles.monitorContainer}
componentRef={props.componentRef}
onDoubleClick={props.onNextType}
onDoubleClick={props.mode === 'list' ? null : props.onNextMode}
>
{types[props.type]({
{(modes[props.mode] || modes.default)({ // Use default until other modes arrive
categoryColor: categories[props.category],
label: props.label,
value: props.value
value: props.value,
width: props.width,
height: props.height,
min: props.min,
max: props.max
})}
</Box>
</Draggable>
<ContextMenu id={`monitor-${props.label}`}>
<MenuItem onClick={props.onSetTypeToDefault}>
<FormattedMessage
defaultMessage="normal readout"
description="Menu item to switch to the default monitor"
id="gui.monitor.contextMenu.default"
/>
</MenuItem>
<MenuItem onClick={props.onSetTypeToLarge}>
<FormattedMessage
defaultMessage="large readout"
description="Menu item to switch to the large monitor"
id="gui.monitor.contextMenu.large"
/>
</MenuItem>
</ContextMenu>
{props.mode === 'list' ? null : (
<ContextMenu id={`monitor-${props.label}`}>
<MenuItem onClick={props.onSetModeToDefault}>
<FormattedMessage
defaultMessage="normal readout"
description="Menu item to switch to the default monitor"
id="gui.monitor.contextMenu.default"
/>
</MenuItem>
<MenuItem onClick={props.onSetModeToLarge}>
<FormattedMessage
defaultMessage="large readout"
description="Menu item to switch to the large monitor"
id="gui.monitor.contextMenu.large"
/>
</MenuItem>
{props.onSetModeToSlider ? (
<MenuItem onClick={props.onSetModeToSlider}>
<FormattedMessage
defaultMessage="slider"
description="Menu item to switch to the slider monitor"
id="gui.monitor.contextMenu.slider"
/>
</MenuItem>
) : null}
</ContextMenu>
)}
</ContextMenuTrigger>
);
MonitorComponent.categories = categories;
const monitorTypes = Object.keys(types);
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,
onNextType: PropTypes.func.isRequired,
onSetTypeToDefault: PropTypes.func.isRequired,
onSetTypeToLarge: PropTypes.func.isRequired,
type: PropTypes.oneOf(monitorTypes),
onNextMode: PropTypes.func.isRequired,
onSetModeToDefault: PropTypes.func.isRequired,
onSetModeToLarge: PropTypes.func.isRequired,
onSetModeToSlider: PropTypes.func,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number])
PropTypes.number,
PropTypes.arrayOf(PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]))
]),
width: PropTypes.number
};
MonitorComponent.defaultProps = {
category: 'data',
type: 'default'
mode: 'default'
};
export {
MonitorComponent as default,
monitorTypes
monitorModes
};
+49
View File
@@ -0,0 +1,49 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './monitor.css';
const SliderMonitor = ({categoryColor, label, min, max, value}) => (
<div className={styles.defaultMonitor}>
<div className={styles.row}>
<div className={styles.label}>
{label}
</div>
<div
className={styles.value}
style={{background: categoryColor}}
>
{value}
</div>
</div>
<div className={styles.row}>
<input
className="no-drag" // Class used on parent Draggable to prevent drags
max={max}
min={min}
type="range"
value={value}
// @todo onChange callback
/>
</div>
</div>
);
SliderMonitor.propTypes = {
categoryColor: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
max: PropTypes.number,
min: PropTypes.number,
// @todo callback for change events
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
])
};
SliderMonitor.defaultProps = {
min: 0,
max: 100
};
export default SliderMonitor;
+5 -1
View File
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import VM from 'scratch-vm';
import {connect} from 'react-redux';
import ReactModal from 'react-modal';
import {openExtensionLibrary} from '../reducers/modals';
import {
@@ -120,4 +121,7 @@ const ConnectedGUI = connect(
mapDispatchToProps,
)(GUI);
export default ProjectLoaderHOC(AppStateHOC(vmListenerHOC(ConnectedGUI)));
const WrappedGui = ProjectLoaderHOC(AppStateHOC(vmListenerHOC(ConnectedGUI)));
WrappedGui.setAppElement = ReactModal.setAppElement;
export default WrappedGui;
+52 -20
View File
@@ -3,25 +3,37 @@ import React from 'react';
import PropTypes from 'prop-types';
import monitorAdapter from '../lib/monitor-adapter.js';
import MonitorComponent, {monitorTypes} from '../components/monitor/monitor.jsx';
import MonitorComponent, {monitorModes} from '../components/monitor/monitor.jsx';
import {addMonitorRect, getInitialPosition, resizeMonitorRect, removeMonitorRect} from '../reducers/monitor-layout';
import {connect} from 'react-redux';
const availableModes = opcode => (
monitorModes.filter(t => {
if (opcode === 'data_variable') {
return t !== 'list';
} else if (opcode === 'data_listcontents') {
return t === 'list';
}
return t !== 'slider' && t !== 'list';
})
);
class Monitor extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleDragEnd',
'handleNextType',
'handleSetTypeToDefault',
'handleSetTypeToLarge',
'handleNextMode',
'handleSetModeToDefault',
'handleSetModeToLarge',
'handleSetModeToSlider',
'setElement'
]);
// @todo this eventually will be stored in the VM
// @todo consume from VM, but need to store until there are APIs to update vm
this.state = {
type: 'default'
mode: props.mode || 'default'
};
}
componentDidMount () {
@@ -67,32 +79,40 @@ class Monitor extends React.Component {
parseInt(this.element.style.top, 10) + y
);
}
handleNextType () {
// @todo the type list needs to be filtered for current available types
// i.e. no sliders for read-only monitors, only list type for list vars.
const typeIndex = monitorTypes.indexOf(this.state.type);
this.setState({type: monitorTypes[(typeIndex + 1) % monitorTypes.length]});
handleNextMode () {
const modes = availableModes(this.props.opcode);
const modeIndex = modes.indexOf(this.state.mode);
this.setState({mode: modes[(modeIndex + 1) % modes.length]});
}
handleSetTypeToDefault () {
this.setState({type: 'default'});
handleSetModeToDefault () {
this.setState({mode: 'default'});
}
handleSetTypeToLarge () {
this.setState({type: 'large'});
handleSetModeToLarge () {
this.setState({mode: 'large'});
}
handleSetModeToSlider () {
this.setState({mode: 'slider'});
}
setElement (monitorElt) {
this.element = monitorElt;
}
render () {
const monitorProps = monitorAdapter(this.props);
const showSliderOption = availableModes(this.props.opcode).indexOf('slider') !== -1;
return (
<MonitorComponent
componentRef={this.setElement}
{...monitorProps}
type={this.state.type}
height={this.props.height}
max={this.props.max}
min={this.props.min}
mode={this.state.mode}
width={this.props.width}
onDragEnd={this.handleDragEnd}
onNextType={this.handleNextType}
onSetTypeToDefault={this.handleSetTypeToDefault}
onSetTypeToLarge={this.handleSetTypeToLarge}
onNextMode={this.handleNextMode}
onSetModeToDefault={this.handleSetModeToDefault}
onSetModeToLarge={this.handleSetModeToLarge}
onSetModeToSlider={showSliderOption ? this.handleSetModeToSlider : null}
/>
);
}
@@ -100,7 +120,11 @@ class Monitor extends React.Component {
Monitor.propTypes = {
addMonitorRect: PropTypes.func.isRequired,
height: PropTypes.number,
id: PropTypes.string.isRequired,
max: PropTypes.number,
min: PropTypes.number,
mode: PropTypes.oneOf(['default', 'slider', 'large', 'list']),
monitorLayout: PropTypes.shape({
monitors: PropTypes.object,
savedMonitorPositions: PropTypes.object
@@ -111,7 +135,15 @@ Monitor.propTypes = {
removeMonitorRect: PropTypes.func.isRequired,
resizeMonitorRect: PropTypes.func.isRequired,
spriteName: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
value: PropTypes.string.isRequired, // eslint-disable-line react/no-unused-prop-types
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.arrayOf(PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]))
]), // eslint-disable-line react/no-unused-prop-types
width: PropTypes.number,
x: PropTypes.number,
y: PropTypes.number
};
+3 -1
View File
@@ -64,7 +64,9 @@ export default function (vm) {
const backdropsMenu = function () {
if (vm.runtime.targets[0] && vm.runtime.targets[0].getCostumes().length > 0) {
return vm.runtime.targets[0].getCostumes().map(costume => [costume.name, costume.name])
.concat([['next backdrop', 'next backdrop'], ['previous backdrop', 'previous backdrop']]);
.concat([['next backdrop', 'next backdrop'],
['previous backdrop', 'previous backdrop'],
['random backdrop', 'random backdrop']]);
}
return [['', '']];
};
+6 -6
View File
@@ -2,9 +2,9 @@ import {TextEncoder} from 'text-encoding';
import projectJson from './project.json';
/* eslint-disable import/no-unresolved */
import popWav from '!buffer-loader!./83a9787d4cb6f3b7632b4ddfebf74367.wav';
import meowWav from '!buffer-loader!./83c36d806dc92327b9e7049a565c6bff.wav';
import backdrop from '!buffer-loader!./739b5e2a2435f6e1ec2993791b423146.png';
import popWav from '!arraybuffer-loader!./83a9787d4cb6f3b7632b4ddfebf74367.wav';
import meowWav from '!arraybuffer-loader!./83c36d806dc92327b9e7049a565c6bff.wav';
import backdrop from '!arraybuffer-loader!./739b5e2a2435f6e1ec2993791b423146.png';
import costume1 from '!raw-loader!./09dc888b0b7df19f70d81588ae73420e.svg';
import costume2 from '!raw-loader!./3696356a03a8d938318876a593572843.svg';
/* eslint-enable import/no-unresolved */
@@ -19,17 +19,17 @@ export default [{
id: '83a9787d4cb6f3b7632b4ddfebf74367',
assetType: 'Sound',
dataFormat: 'WAV',
data: popWav
data: new Uint8Array(popWav)
}, {
id: '83c36d806dc92327b9e7049a565c6bff',
assetType: 'Sound',
dataFormat: 'WAV',
data: meowWav
data: new Uint8Array(meowWav)
}, {
id: '739b5e2a2435f6e1ec2993791b423146',
assetType: 'ImageBitmap',
dataFormat: 'PNG',
data: backdrop
data: new Uint8Array(backdrop)
}, {
id: '09dc888b0b7df19f70d81588ae73420e',
assetType: 'ImageVector',
+3 -3
View File
@@ -10,7 +10,7 @@ const isUndefined = a => typeof a === 'undefined';
* with given target ID. The name of the target sprite when the monitor was created
* @param {string} block.opcode - The opcode of the monitor
* @param {object} block.params - Extra params to the monitor block
* @param {string} block.value - The monitor value
* @param {string|number|Array} block.value - The monitor value
* @return {object} The adapted monitor with label and category
*/
export default function ({id, spriteName, opcode, params, value}) {
@@ -25,8 +25,8 @@ export default function ({id, spriteName, opcode, params, value}) {
}
// If value is a number, round it to six decimal places
if (typeof value === 'number' || (typeof value === 'string' && String(parseFloat(value)) === value)) {
value = Number(Number(value).toFixed(6));
if (typeof value === 'number') {
value = Number(value.toFixed(6));
}
return {id, label, category, value};
+1 -2
View File
@@ -1,7 +1,6 @@
import 'es6-object-assign/auto';
import React from 'react';
import ReactDOM from 'react-dom';
import Modal from 'react-modal';
import analytics from '../lib/analytics';
import GUI from '../containers/gui.jsx';
@@ -20,6 +19,6 @@ const appTarget = document.createElement('div');
appTarget.className = styles.app;
document.body.appendChild(appTarget);
Modal.setAppElement(appTarget);
GUI.setAppElement(appTarget);
ReactDOM.render(<GUI />, appTarget);