diff --git a/src/components/backpack/backpack.css b/src/components/backpack/backpack.css
index fc5006386..db8b9cd05 100644
--- a/src/components/backpack/backpack.css
+++ b/src/components/backpack/backpack.css
@@ -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;
+}
diff --git a/src/components/backpack/backpack.jsx b/src/components/backpack/backpack.jsx
index 74798b8a7..3711cafd4 100644
--- a/src/components/backpack/backpack.jsx
+++ b/src/components/backpack/backpack.jsx
@@ -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}) => (
(
{expanded ? (
-
-
-
+ {error ? (
+
+
+
+ ) : (
+ loading ? (
+
+
+
+ ) : (
+ contents.length > 0 ? (
+
+ {contents.map(item => (
+
+ ))}
+
+ ) : (
+
+
+
+ )
+ )
+ )}
) : null}
);
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
};
diff --git a/src/containers/backpack.jsx b/src/containers/backpack.jsx
index 3af28f9e1..878a6cd5f 100644
--- a/src/containers/backpack.jsx
+++ b/src/containers/backpack.jsx
@@ -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 (
);
@@ -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);
diff --git a/src/lib/backpack-api.js b/src/lib/backpack-api.js
new file mode 100644
index 000000000..0ec46260a
--- /dev/null
+++ b/src/lib/backpack-api.js
@@ -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
+};
diff --git a/src/playground/index.jsx b/src/playground/index.jsx
index 9c0879f2c..47abac046 100644
--- a/src/playground/index.jsx
+++ b/src/playground/index.jsx
@@ -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 = {