Merge pull request #2187 from paulkaplan/backpack-step-3
Backpack load contents from server
This commit is contained in:
5 files changed
+163
-16
No files matched your search
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -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
|
||||
};
|
||||
@@ -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 = {
|
||||
|
||||
Reference in new issue
Block a user