diff --git a/src/components/library-item/library-item.css b/src/components/library-item/library-item.css index 55edcc2e3..692e1b612 100644 --- a/src/components/library-item/library-item.css +++ b/src/components/library-item/library-item.css @@ -8,6 +8,7 @@ justify-content: flex-start; flex-basis: 160px; height: 160px; + max-width: 160px; margin: $space; padding: 1rem 1rem 0 1rem; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; diff --git a/src/components/library-item/library-item.jsx b/src/components/library-item/library-item.jsx index 03a9d6b78..3880a4cf6 100644 --- a/src/components/library-item/library-item.jsx +++ b/src/components/library-item/library-item.jsx @@ -1,4 +1,3 @@ -import bindAll from 'lodash.bindall'; import {FormattedMessage} from 'react-intl'; import PropTypes from 'prop-types'; import React from 'react'; @@ -10,42 +9,8 @@ import classNames from 'classnames'; import bluetoothIconURL from './bluetooth.svg'; import internetConnectionIconURL from './internet-connection.svg'; -class LibraryItem extends React.PureComponent { - constructor (props) { - super(props); - bindAll(this, [ - 'handleBlur', - 'handleClick', - 'handleFocus', - 'handleKeyPress', - 'handleMouseEnter', - 'handleMouseLeave' - ]); - } - handleBlur () { - this.props.onBlur(this.props.id); - } - handleFocus () { - this.props.onFocus(this.props.id); - } - handleClick (e) { - if (!this.props.disabled) { - this.props.onSelect(this.props.id); - } - e.preventDefault(); - } - handleKeyPress (e) { - if (e.key === ' ' || e.key === 'Enter') { - e.preventDefault(); - this.props.onSelect(this.props.id); - } - } - handleMouseEnter () { - this.props.onMouseEnter(this.props.id); - } - handleMouseLeave () { - this.props.onMouseLeave(this.props.id); - } +/* eslint-disable react/prefer-stateless-function */ +class LibraryItemComponent extends React.PureComponent { render () { return this.props.featured ? (
{this.props.disabled ? ( @@ -146,12 +111,12 @@ class LibraryItem extends React.PureComponent { )} role="button" tabIndex="0" - onBlur={this.handleBlur} - onClick={this.handleClick} - onFocus={this.handleFocus} - onKeyPress={this.handleKeyPress} - onMouseEnter={this.handleMouseEnter} - onMouseLeave={this.handleMouseLeave} + onBlur={this.props.onBlur} + onClick={this.props.onClick} + onFocus={this.props.onFocus} + onKeyPress={this.props.onKeyPress} + onMouseEnter={this.props.onMouseEnter} + onMouseLeave={this.props.onMouseLeave} > {/* Layers of wrapping is to prevent layout thrashing on animation */} @@ -167,8 +132,10 @@ class LibraryItem extends React.PureComponent { ); } } +/* eslint-enable react/prefer-stateless-function */ -LibraryItem.propTypes = { + +LibraryItemComponent.propTypes = { bluetoothRequired: PropTypes.bool, collaborator: PropTypes.string, description: PropTypes.oneOfType([ @@ -180,22 +147,22 @@ LibraryItem.propTypes = { featured: PropTypes.bool, hidden: PropTypes.bool, iconURL: PropTypes.string, - id: PropTypes.number.isRequired, insetIconURL: PropTypes.string, internetConnectionRequired: PropTypes.bool, name: PropTypes.oneOfType([ PropTypes.string, PropTypes.node ]), - onBlur: PropTypes.func, - onFocus: PropTypes.func, + onBlur: PropTypes.func.isRequired, + onClick: PropTypes.func.isRequired, + onFocus: PropTypes.func.isRequired, + onKeyPress: PropTypes.func.isRequired, onMouseEnter: PropTypes.func.isRequired, - onMouseLeave: PropTypes.func.isRequired, - onSelect: PropTypes.func.isRequired + onMouseLeave: PropTypes.func.isRequired }; -LibraryItem.defaultProps = { +LibraryItemComponent.defaultProps = { disabled: false }; -export default LibraryItem; +export default LibraryItemComponent; diff --git a/src/components/library/library.jsx b/src/components/library/library.jsx index 8ecb76181..8073b0f49 100644 --- a/src/components/library/library.jsx +++ b/src/components/library/library.jsx @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import {defineMessages, injectIntl, intlShape} from 'react-intl'; -import LibraryItem from '../library-item/library-item.jsx'; +import LibraryItem from '../../containers/library-item.jsx'; import Modal from '../../containers/modal.jsx'; import Divider from '../divider/divider.jsx'; import Filter from '../filter/filter.jsx'; @@ -33,11 +33,9 @@ class LibraryComponent extends React.Component { constructor (props) { super(props); bindAll(this, [ - 'handleBlur', 'handleClose', 'handleFilterChange', 'handleFilterClear', - 'handleFocus', 'handleMouseEnter', 'handleMouseLeave', 'handleSelect', @@ -56,12 +54,6 @@ class LibraryComponent extends React.Component { this.scrollToTop(); } } - handleBlur (id) { - this.handleMouseLeave(id); - } - handleFocus (id) { - this.handleMouseEnter(id); - } handleSelect (id) { this.handleClose(); this.props.onItemSelected(this.getFilteredData()[id]); @@ -172,33 +164,28 @@ class LibraryComponent extends React.Component { })} ref={this.setFilteredDataRef} > - {this.getFilteredData().map((dataItem, index) => { - const scratchURL = dataItem.md5 ? - `https://cdn.assets.scratch.mit.edu/internalapi/asset/${dataItem.md5}/get/` : - dataItem.rawURL; - return ( -
); diff --git a/src/containers/library-item.jsx b/src/containers/library-item.jsx new file mode 100644 index 000000000..6177b916f --- /dev/null +++ b/src/containers/library-item.jsx @@ -0,0 +1,149 @@ +import bindAll from 'lodash.bindall'; +import PropTypes from 'prop-types'; +import React from 'react'; +import {injectIntl} from 'react-intl'; + +import LibraryItemComponent from '../components/library-item/library-item.jsx'; + +class LibraryItem extends React.PureComponent { + constructor (props) { + super(props); + bindAll(this, [ + 'handleBlur', + 'handleClick', + 'handleFocus', + 'handleKeyPress', + 'handleMouseEnter', + 'handleMouseLeave', + 'rotateIcon', + 'startRotatingIcons', + 'stopRotatingIcons' + ]); + this.state = { + iconIndex: 0, + isRotatingIcon: false + }; + } + componentWillUnmount () { + clearInterval(this.intervalId); + } + handleBlur (id) { + this.handleMouseLeave(id); + } + handleClick (e) { + if (!this.props.disabled) { + this.props.onSelect(this.props.id); + } + e.preventDefault(); + } + handleFocus (id) { + this.handleMouseEnter(id); + } + handleKeyPress (e) { + if (e.key === ' ' || e.key === 'Enter') { + e.preventDefault(); + this.props.onSelect(this.props.id); + } + } + handleMouseEnter () { + this.props.onMouseEnter(this.props.id); + if (this.props.icons && this.props.icons.length) { + this.stopRotatingIcons(); + this.setState({ + isRotatingIcon: true + }, this.startRotatingIcons); + } + } + handleMouseLeave () { + this.props.onMouseLeave(this.props.id); + if (this.props.icons && this.props.icons.length) { + this.setState({ + isRotatingIcon: false + }, this.stopRotatingIcons); + } + } + startRotatingIcons () { + this.rotateIcon(); + this.intervalId = setInterval(this.rotateIcon, 300); + } + stopRotatingIcons () { + if (this.intervalId) { + this.intervalId = clearInterval(this.intervalId); + } + } + rotateIcon () { + const nextIconIndex = (this.state.iconIndex + 1) % this.props.icons.length; + this.setState({iconIndex: nextIconIndex}); + } + curIconMd5 () { + if (this.props.icons && + this.state.isRotatingIcon && + this.state.iconIndex < this.props.icons.length && + this.props.icons[this.state.iconIndex] && + this.props.icons[this.state.iconIndex].baseLayerMD5) { + return this.props.icons[this.state.iconIndex].baseLayerMD5; + } + return this.props.iconMd5; + } + render () { + const iconMd5 = this.curIconMd5(); + const iconURL = iconMd5 ? + `https://cdn.assets.scratch.mit.edu/internalapi/asset/${iconMd5}/get/` : + this.props.iconRawURL; + return ( +