diff --git a/src/components/backpack/backpack.css b/src/components/backpack/backpack.css index e12fe8008a0e6ef515d2f3a8746594c00417dc46..6eddfa905c4a6f79eb1017d149b7030e32aa278a 100644 --- a/src/components/backpack/backpack.css +++ b/src/components/backpack/backpack.css @@ -76,3 +76,16 @@ .backpack-item img { mix-blend-mode: multiply; /* Make white transparent for thumnbnails */ } + +.more { + background: $motion-primary; + color: $ui-white; + border: none; + outline: none; + font-weight: bold; + border-radius: 0.5rem; + font-size: 0.85rem; + padding: 0.5rem; + margin: 0.5rem; + cursor: pointer; +} diff --git a/src/components/backpack/backpack.jsx b/src/components/backpack/backpack.jsx index f5f4500393bc73d80e888d7b7b90b97ad6cde567..12fed8238b7040e5fb249ef35d2400ebfb1b75a9 100644 --- a/src/components/backpack/backpack.jsx +++ b/src/components/backpack/backpack.jsx @@ -25,10 +25,12 @@ const Backpack = ({ error, expanded, loading, + showMore, onToggle, onDelete, onMouseEnter, - onMouseLeave + onMouseLeave, + onMore }) => ( <div className={styles.backpackContainer}> <div @@ -98,6 +100,18 @@ const Backpack = ({ onDeleteButtonClick={onDelete} /> ))} + {showMore && ( + <button + className={styles.more} + onClick={onMore} + > + <FormattedMessage + defaultMessage="More" + description="Load more from backpack" + id="gui.backpack.more" + /> + </button> + )} </div> ) : ( <div className={styles.statusMessage}> @@ -129,6 +143,7 @@ Backpack.propTypes = { expanded: PropTypes.bool, loading: PropTypes.bool, onDelete: PropTypes.func, + onMore: PropTypes.func, onMouseEnter: PropTypes.func, onMouseLeave: PropTypes.func, onToggle: PropTypes.func @@ -140,6 +155,8 @@ Backpack.defaultProps = { dragOver: false, expanded: false, loading: false, + showMore: false, + onMore: null, onToggle: null }; diff --git a/src/containers/backpack.jsx b/src/containers/backpack.jsx index 32c6fea0025f06bdb153ff61b889c31a9d04eb0f..a79b43a025cc982a3b3e4420c33e5b1396375b4c 100644 --- a/src/containers/backpack.jsx +++ b/src/containers/backpack.jsx @@ -29,11 +29,12 @@ class Backpack extends React.Component { 'handleToggle', 'handleDelete', 'getBackpackAssetURL', - 'refreshContents', + 'getContents', 'handleMouseEnter', 'handleMouseLeave', 'handleBlockDragEnd', - 'handleBlockDragUpdate' + 'handleBlockDragUpdate', + 'handleMore' ]); this.state = { // While the DroppableHOC manages drop interactions for asset tiles, @@ -42,8 +43,8 @@ class Backpack extends React.Component { blockDragOutsideWorkspace: false, blockDragOverBackpack: false, error: false, - offset: 0, itemsPerPage: 20, + moreToLoad: false, loading: false, expanded: false, contents: [] @@ -72,12 +73,12 @@ class Backpack extends React.Component { } handleToggle () { const newState = !this.state.expanded; - this.setState({expanded: newState, offset: 0}, () => { + this.setState({expanded: newState, contents: []}, () => { // Emit resize on window to get blocks to resize window.dispatchEvent(new Event('resize')); }); if (newState) { - this.refreshContents(); + this.getContents(); } } handleDrop (dragInfo) { @@ -107,33 +108,57 @@ class Backpack extends React.Component { username: this.props.username, ...payload })) - .then(this.refreshContents); + .then(item => { + this.setState({ + loading: false, + contents: [item].concat(this.state.contents) + }); + }) + .catch(() => { + this.setState({error: true, loading: false}); + }); }); } handleDelete (id) { - deleteBackpackObject({ - host: this.props.host, - token: this.props.token, - username: this.props.username, - id: id - }).then(this.refreshContents); - } - refreshContents () { - if (this.props.token && this.props.username) { - this.setState({loading: true, error: false}); - getBackpackContents({ + this.setState({loading: true}, () => { + deleteBackpackObject({ host: this.props.host, token: this.props.token, username: this.props.username, - offset: this.state.offset, - limit: this.state.itemsPerPage + id: id }) - .then(contents => { - this.setState({contents, loading: false}); + .then(() => { + this.setState({ + loading: false, + contents: this.state.contents.filter(o => o.id !== id) + }); }) .catch(() => { this.setState({error: true, loading: false}); }); + }); + } + getContents () { + 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.contents.length, + limit: this.state.itemsPerPage + }) + .then(contents => { + this.setState({ + contents: this.state.contents.concat(contents), + moreToLoad: contents.length === this.state.itemsPerPage, + loading: false + }); + }) + .catch(() => { + this.setState({error: true, loading: false}); + }); + }); } } handleBlockDragUpdate (isOutsideWorkspace) { @@ -168,6 +193,9 @@ class Backpack extends React.Component { blockDragOutsideWorkspace: false }); } + handleMore () { + this.getContents(); + } render () { return ( <DroppableBackpack @@ -176,8 +204,10 @@ class Backpack extends React.Component { error={this.state.error} expanded={this.state.expanded} loading={this.state.loading} + showMore={this.state.moreToLoad} onDelete={this.handleDelete} onDrop={this.handleDrop} + onMore={this.handleMore} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} onToggle={this.props.host ? this.handleToggle : null} diff --git a/src/lib/backpack-api.js b/src/lib/backpack-api.js index 4a6f7f6b24ccf2e9fba256fbed97d28ccf5c8aa7..8a5231843aeb409727407b0817178c55d7933a43 100644 --- a/src/lib/backpack-api.js +++ b/src/lib/backpack-api.js @@ -4,6 +4,14 @@ import soundPayload from './backpack/sound-payload'; import spritePayload from './backpack/sprite-payload'; import codePayload from './backpack/code-payload'; +// Add a new property for the full thumbnail url, which includes the host. +// Also include a full body url for loading sprite zips +// TODO retreiving the images through storage would allow us to remove this. +const includeFullUrls = (item, host) => Object.assign({}, item, { + thumbnailUrl: `${host}/${item.thumbnail}`, + bodyUrl: `${host}/${item.body}` +}); + const getBackpackContents = ({ host, username, @@ -20,15 +28,7 @@ const getBackpackContents = ({ if (error || response.statusCode !== 200) { return reject(); } - // Add a new property for the full thumbnail url, which includes the host. - // Also include a full body url for loading sprite zips - // 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}`, - bodyUrl: `${host}/${item.body}` - }) - ))); + return resolve(response.body.map(item => includeFullUrls(item, host))); }); }); @@ -51,7 +51,7 @@ const saveBackpackObject = ({ if (error || response.statusCode !== 200) { return reject(); } - return resolve(response.body); + return resolve(includeFullUrls(response.body, host)); }); });