From 82290540eb9f67d3c6b5e7211cc5cdd8b85fbcf4 Mon Sep 17 00:00:00 2001 From: Paul Kaplan <pkaplan@media.mit.edu> Date: Fri, 4 May 2018 08:50:30 -0400 Subject: [PATCH] Get backpack contents from server --- src/components/backpack/backpack.css | 22 ++++++++++- src/components/backpack/backpack.jsx | 56 +++++++++++++++++++++++----- src/containers/backpack.jsx | 53 ++++++++++++++++++++++++-- src/lib/backpack-api.js | 19 ++++++++++ src/playground/index.jsx | 2 +- 5 files changed, 136 insertions(+), 16 deletions(-) create mode 100644 src/lib/backpack-api.js 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..81934ae14 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, expanded, loading, onToggle}) => ( <div className={styles.backpackContainer}> <div className={styles.backpackHeader} @@ -32,25 +35,60 @@ 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> + {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.shape({ + id: PropTypes.string, + thumbnailUrl: PropTypes.string, + type: PropTypes.string, + name: PropTypes.string + }), 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..949b20846 100644 --- a/src/containers/backpack.jsx +++ b/src/containers/backpack.jsx @@ -2,26 +2,51 @@ 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 = { + 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}); + 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}); + }); + } } render () { return ( <BackpackComponent contents={this.state.contents} expanded={this.state.expanded} + loading={this.state.loading} onToggle={this.props.host ? this.handleToggle : null} /> ); @@ -29,7 +54,27 @@ class Backpack extends React.Component { } Backpack.propTypes = { - host: PropTypes.string + host: PropTypes.string, + token: PropTypes.string, + username: PropTypes.string +}; + +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 Backpack; +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..e77b6d832 --- /dev/null +++ b/src/lib/backpack-api.js @@ -0,0 +1,19 @@ +const getBackpackContents = ({ + host, + username, + token, + limit, + offset +}) => fetch(`${host}${username}?limit=${limit}&offset=${offset}`, { + headers: {'x-token': token} +}) + .then(d => d.json()) + .then(items => items.map(item => + // 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. + 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 = { -- GitLab