Merge branch 'develop' into greenkeeper/scratch-paint-0.2.0-prerelease.20180618172346

This commit is contained in:
Paul Kaplan authored and GitHub committed 2018-06-19 10:56:34 -04:00
commit 31276d2e19
7 files changed
+169 -20

No files matched your search

+2
View File
@@ -2,6 +2,8 @@
_What Github issue does this resolve (please include link)?_
- Resolves #
### Proposed Changes
_Describe what this Pull Request does_
+4 -4
View File
@@ -93,13 +93,13 @@
"redux-mock-store": "^1.2.3",
"redux-throttle": "0.1.1",
"rimraf": "^2.6.1",
"scratch-audio": "0.1.0-prerelease.1528996828",
"scratch-blocks": "0.1.0-prerelease.1529329760",
"scratch-audio": "0.1.0-prerelease.20180618171838",
"scratch-blocks": "0.1.0-prerelease.1529342508",
"scratch-l10n": "3.0.20180611175036",
"scratch-paint": "0.2.0-prerelease.20180618172346",
"scratch-render": "0.1.0-prerelease.20180615131212",
"scratch-render": "0.1.0-prerelease.20180618173030",
"scratch-storage": "0.5.1",
"scratch-svg-renderer": "0.2.0-prerelease.20180613184320",
"scratch-svg-renderer": "0.2.0-prerelease.20180618172917",
"scratch-vm": "0.1.0-prerelease.1529017807",
"selenium-webdriver": "3.6.0",
"startaudiocontext": "1.2.1",
+20 -2
View File
@@ -26,12 +26,30 @@
flex-direction: row;
align-items: center;
border-right: 1px solid $ui-black-transparent;
min-height: 6rem;
min-height: 5.5rem;
}
.empty-message {
/* Absolute position the inner list to allow scrolling inside flex sized container */
.backpack-list-inner {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
align-items: center;
overflow-x: auto;
}
.status-message {
width: 100%;
text-align: center;
font-size: 0.85rem;
color: $text-primary;
}
.backpack-item {
min-width: 4rem;
margin: 0 0.25rem;
}
+58 -9
View File
@@ -2,10 +2,13 @@ import React from 'react';
import PropTypes from 'prop-types';
import {FormattedMessage} from 'react-intl';
import {ComingSoonTooltip} from '../coming-soon/coming-soon.jsx';
import SpriteSelectorItem from '../../containers/sprite-selector-item.jsx';
import styles from './backpack.css';
const Backpack = ({expanded, onToggle}) => (
// TODO make sprite selector item not require onClick
const noop = () => {};
const Backpack = ({contents, error, expanded, loading, onToggle}) => (
<div className={styles.backpackContainer}>
<div
className={styles.backpackHeader}
@@ -32,25 +35,71 @@ const Backpack = ({expanded, onToggle}) => (
</div>
{expanded ? (
<div className={styles.backpackList}>
<div className={styles.emptyMessage}>
<FormattedMessage
defaultMessage="Backpack is empty"
description="Empty backpack message"
id="gui.backpack.emptyBackpack"
/>
</div>
{error ? (
<div className={styles.statusMessage}>
<FormattedMessage
defaultMessage="Error loading backpack"
description="Error backpack message"
id="gui.backpack.errorBackpack"
/>
</div>
) : (
loading ? (
<div className={styles.statusMessage}>
<FormattedMessage
defaultMessage="Loading..."
description="Loading backpack message"
id="gui.backpack.loadingBackpack"
/>
</div>
) : (
contents.length > 0 ? (
<div className={styles.backpackListInner}>
{contents.map(item => (
<SpriteSelectorItem
className={styles.backpackItem}
costumeURL={item.thumbnailUrl}
details={item.name}
key={item.id}
name={item.type}
selected={false}
onClick={noop}
/>
))}
</div>
) : (
<div className={styles.statusMessage}>
<FormattedMessage
defaultMessage="Backpack is empty"
description="Empty backpack message"
id="gui.backpack.emptyBackpack"
/>
</div>
)
)
)}
</div>
) : null}
</div>
);
Backpack.propTypes = {
contents: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string,
thumbnailUrl: PropTypes.string,
type: PropTypes.string,
name: PropTypes.string
})),
error: PropTypes.bool,
expanded: PropTypes.bool,
loading: PropTypes.bool,
onToggle: PropTypes.func
};
Backpack.defaultProps = {
contents: [],
expanded: false,
loading: false,
onToggle: null
};
+55 -4
View File
@@ -2,26 +2,57 @@ import React from 'react';
import PropTypes from 'prop-types';
import bindAll from 'lodash.bindall';
import BackpackComponent from '../components/backpack/backpack.jsx';
import {getBackpackContents} from '../lib/backpack-api';
import {connect} from 'react-redux';
class Backpack extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleToggle'
'handleToggle',
'refreshContents'
]);
this.state = {
error: false,
offset: 0,
itemsPerPage: 20,
loading: false,
expanded: false,
contents: []
};
}
handleToggle () {
this.setState({expanded: !this.state.expanded});
const newState = !this.state.expanded;
this.setState({expanded: newState, offset: 0});
if (newState) {
this.refreshContents();
}
}
refreshContents () {
if (this.props.token && this.props.username) {
this.setState({loading: true, error: false});
getBackpackContents({
host: this.props.host,
token: this.props.token,
username: this.props.username,
offset: this.state.offset,
limit: this.state.itemsPerPage
})
.then(contents => {
this.setState({contents, loading: false});
})
.catch(() => {
this.setState({error: true, loading: false});
});
}
}
render () {
return (
<BackpackComponent
contents={this.state.contents}
error={this.state.error}
expanded={this.state.expanded}
loading={this.state.loading}
onToggle={this.props.host ? this.handleToggle : null}
/>
);
@@ -29,7 +60,27 @@ class Backpack extends React.Component {
}
Backpack.propTypes = {
host: PropTypes.string
host: PropTypes.string,
token: PropTypes.string,
username: PropTypes.string
};
export default Backpack;
const mapStateToProps = state => {
// Look for the session state provided by scratch-www
if (state.session && state.session.session) {
return {
token: state.session.session.token,
username: state.session.session.username
};
}
// Otherwise try to pull testing params out of the URL, or return nulls
// TODO a hack for testing the backpack
const tokenMatches = window.location.href.match(/[?&]token=([^&]*)&?/);
const usernameMatches = window.location.href.match(/[?&]username=([^&]*)&?/);
return {
token: tokenMatches ? tokenMatches[1] : null,
username: usernameMatches ? usernameMatches[1] : null
};
};
export default connect(mapStateToProps)(Backpack);
+29
View File
@@ -0,0 +1,29 @@
import xhr from 'xhr';
const getBackpackContents = ({
host,
username,
token,
limit,
offset
}) => new Promise((resolve, reject) => {
xhr({
method: 'GET',
uri: `${host}${username}?limit=${limit}&offset=${offset}`,
headers: {'x-token': token},
json: true
}, (error, response) => {
if (error || response.statusCode !== 200) {
return reject();
}
// Add a new property for the full thumbnail url, which includes the host.
// TODO retreiving the images through storage would allow us to remove this.
return resolve(response.body.map(item => (
Object.assign({}, item, {thumbnailUrl: `${host}/${item.thumbnail}`})
)));
});
});
export {
getBackpackContents
};
+1 -1
View File
@@ -25,7 +25,7 @@ GUI.setAppElement(appTarget);
const WrappedGui = HashParserHOC(AppStateHOC(GUI));
// TODO a hack for testing the backpack, allow backpack host to be set by url param
const backpackHostMatches = window.location.href.match(/[?&]backpack_host=(.*)&?/);
const backpackHostMatches = window.location.href.match(/[?&]backpack_host=([^&]*)&?/);
const backpackHost = backpackHostMatches ? backpackHostMatches[1] : null;
const backpackOptions = {