From 82290540eb9f67d3c6b5e7211cc5cdd8b85fbcf4 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Fri, 4 May 2018 08:50:30 -0400 Subject: [PATCH 1/4] 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}) => (
(
{expanded ? (
-
- -
+ {loading ? ( +
+ +
+ ) : ( + contents.length > 0 ? ( +
+ {contents.map(item => ( + + ))} +
+ ) : ( +
+ +
+ ) + )}
) : null}
); 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 ( ); @@ -29,7 +54,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..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 = { From 9ce75e805a4a95f007c9be482b9eb7c9262a8b59 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Tue, 19 Jun 2018 08:31:02 -0400 Subject: [PATCH 2/4] Update backpack api to use `xhr` module --- src/lib/backpack-api.js | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/lib/backpack-api.js b/src/lib/backpack-api.js index e77b6d832..0ec46260a 100644 --- a/src/lib/backpack-api.js +++ b/src/lib/backpack-api.js @@ -1,18 +1,28 @@ +import xhr from 'xhr'; + 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 => +}) => 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. - Object.assign(item, {thumbnailUrl: `${host}${item.thumbnail}`}) - )); + return resolve(response.body.map(item => ( + Object.assign({}, item, {thumbnailUrl: `${host}/${item.thumbnail}`}) + ))); + }); +}); export { getBackpackContents From 2195088f45409a5dc65368b94b9b0a4766871daf Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Tue, 19 Jun 2018 08:39:25 -0400 Subject: [PATCH 3/4] Add an error state for backpack loading --- src/components/backpack/backpack.jsx | 57 +++++++++++++++++----------- src/containers/backpack.jsx | 14 +++++-- 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/src/components/backpack/backpack.jsx b/src/components/backpack/backpack.jsx index 81934ae14..83e62ae7f 100644 --- a/src/components/backpack/backpack.jsx +++ b/src/components/backpack/backpack.jsx @@ -8,7 +8,7 @@ import styles from './backpack.css'; // TODO make sprite selector item not require onClick const noop = () => {}; -const Backpack = ({contents, expanded, loading, onToggle}) => ( +const Backpack = ({contents, error, expanded, loading, onToggle}) => (
(
{expanded ? (
- {loading ? ( + {error ? (
) : ( - contents.length > 0 ? ( -
- {contents.map(item => ( - - ))} -
- ) : ( + loading ? (
+ ) : ( + contents.length > 0 ? ( +
+ {contents.map(item => ( + + ))} +
+ ) : ( +
+ +
+ ) ) )}
@@ -80,6 +90,7 @@ Backpack.propTypes = { type: PropTypes.string, name: PropTypes.string }), + error: PropTypes.bool, expanded: PropTypes.bool, loading: PropTypes.bool, onToggle: PropTypes.func diff --git a/src/containers/backpack.jsx b/src/containers/backpack.jsx index 949b20846..878a6cd5f 100644 --- a/src/containers/backpack.jsx +++ b/src/containers/backpack.jsx @@ -13,6 +13,7 @@ class Backpack extends React.Component { 'refreshContents' ]); this.state = { + error: false, offset: 0, itemsPerPage: 20, loading: false, @@ -29,22 +30,27 @@ class Backpack extends React.Component { } refreshContents () { if (this.props.token && this.props.username) { - this.setState({loading: true}); + 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}); - }); + }) + .then(contents => { + this.setState({contents, loading: false}); + }) + .catch(() => { + this.setState({error: true, loading: false}); + }); } } render () { return ( Date: Tue, 19 Jun 2018 08:40:41 -0400 Subject: [PATCH 4/4] Fix proptypes for backpack contents --- src/components/backpack/backpack.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/backpack/backpack.jsx b/src/components/backpack/backpack.jsx index 83e62ae7f..3711cafd4 100644 --- a/src/components/backpack/backpack.jsx +++ b/src/components/backpack/backpack.jsx @@ -84,12 +84,12 @@ const Backpack = ({contents, error, expanded, loading, onToggle}) => ( ); Backpack.propTypes = { - contents: PropTypes.shape({ + 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,