Skip to content
Snippets Groups Projects
Commit 893beb5a authored by Ben Wheeler's avatar Ben Wheeler
Browse files

added library-item container, moved costume rotation to library items

parent 912306e6
No related branches found
No related tags found
No related merge requests found
......@@ -10,24 +10,14 @@ import classNames from 'classnames';
import bluetoothIconURL from './bluetooth.svg';
import internetConnectionIconURL from './internet-connection.svg';
class LibraryItem extends React.PureComponent {
class LibraryItemComponent extends React.PureComponent {
constructor (props) {
super(props);
bindAll(this, [
'handleBlur',
'handleClick',
'handleFocus',
'handleKeyPress',
'handleMouseEnter',
'handleMouseLeave'
'handleKeyPress'
]);
}
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);
......@@ -40,12 +30,6 @@ class LibraryItem extends React.PureComponent {
this.props.onSelect(this.props.id);
}
}
handleMouseEnter () {
this.props.onMouseEnter(this.props.id);
}
handleMouseLeave () {
this.props.onMouseLeave(this.props.id);
}
render () {
return this.props.featured ? (
<div
......@@ -146,12 +130,12 @@ class LibraryItem extends React.PureComponent {
)}
role="button"
tabIndex="0"
onBlur={this.handleBlur}
onBlur={this.props.onBlur}
onClick={this.handleClick}
onFocus={this.handleFocus}
onFocus={this.props.onFocus}
onKeyPress={this.handleKeyPress}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
onMouseEnter={this.props.onMouseEnter}
onMouseLeave={this.props.onMouseLeave}
>
{/* Layers of wrapping is to prevent layout thrashing on animation */}
<Box className={styles.libraryItemImageContainerWrapper}>
......@@ -168,7 +152,7 @@ class LibraryItem extends React.PureComponent {
}
}
LibraryItem.propTypes = {
LibraryItemComponent.propTypes = {
bluetoothRequired: PropTypes.bool,
collaborator: PropTypes.string,
description: PropTypes.oneOfType([
......@@ -194,8 +178,8 @@ LibraryItem.propTypes = {
onSelect: PropTypes.func.isRequired
};
LibraryItem.defaultProps = {
LibraryItemComponent.defaultProps = {
disabled: false
};
export default LibraryItem;
export default LibraryItemComponent;
......@@ -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]);
......@@ -76,10 +68,10 @@ class LibraryComponent extends React.Component {
selectedTag: tag.toLowerCase()
});
}
handleMouseEnter (id) {
handleMouseEnter (id) { // no longer used for sprite costume switching, only for other libraries
if (this.props.onItemMouseEnter) this.props.onItemMouseEnter(this.getFilteredData()[id]);
}
handleMouseLeave (id) {
handleMouseLeave (id) { // no longer used for sprite costume switching, only for other libraries
if (this.props.onItemMouseLeave) this.props.onItemMouseLeave(this.getFilteredData()[id]);
}
handleFilterChange (event) {
......@@ -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 (
<LibraryItem
bluetoothRequired={dataItem.bluetoothRequired}
collaborator={dataItem.collaborator}
description={dataItem.description}
disabled={dataItem.disabled}
extensionId={dataItem.extensionId}
featured={dataItem.featured}
hidden={dataItem.hidden}
iconURL={scratchURL}
id={index}
insetIconURL={dataItem.insetIconURL}
internetConnectionRequired={dataItem.internetConnectionRequired}
key={`item_${index}`}
name={dataItem.name}
onBlur={this.handleBlur}
onFocus={this.handleFocus}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
onSelect={this.handleSelect}
/>
);
})}
{this.getFilteredData().map((dataItem, index) => (
<LibraryItem
bluetoothRequired={dataItem.bluetoothRequired}
collaborator={dataItem.collaborator}
description={dataItem.description}
disabled={dataItem.disabled}
extensionId={dataItem.extensionId}
featured={dataItem.featured}
hidden={dataItem.hidden}
iconMd5={dataItem.md5}
iconRawURL={dataItem.rawURL}
icons={dataItem.json && dataItem.json.costumes}
id={index}
insetIconURL={dataItem.insetIconURL}
internetConnectionRequired={dataItem.internetConnectionRequired}
key={`item_${index}`}
name={dataItem.name}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
onSelect={this.handleSelect}
/>
))}
</div>
</Modal>
);
......
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',
'handleFocus',
'handleMouseEnter',
'handleMouseLeave',
'rotateIcon',
'startRotatingIcons',
'stopRotatingIcons'
]);
this.state = {
iconIndex: 0,
isRotatingIcon: false
};
}
componentWillUnmount () {
clearInterval(this.intervalId);
}
handleBlur (id) {
this.handleMouseLeave(id);
}
handleFocus (id) {
this.handleMouseEnter(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 (
<LibraryItemComponent
bluetoothRequired={this.props.bluetoothRequired}
collaborator={this.props.collaborator}
description={this.props.description}
disabled={this.props.disabled}
extensionId={this.props.extensionId}
featured={this.props.featured}
hidden={this.props.hidden}
iconURL={iconURL}
icons={this.props.icons}
id={this.props.id}
insetIconURL={this.props.insetIconURL}
internetConnectionRequired={this.props.internetConnectionRequired}
name={this.props.name}
onBlur={this.handleBlur}
onFocus={this.handleFocus}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
onSelect={this.props.onSelect}
/>
);
}
}
LibraryItem.propTypes = {
bluetoothRequired: PropTypes.bool,
collaborator: PropTypes.string,
description: PropTypes.oneOfType([
PropTypes.string,
PropTypes.node
]),
disabled: PropTypes.bool,
extensionId: PropTypes.string,
featured: PropTypes.bool,
hidden: PropTypes.bool,
iconMd5: PropTypes.string,
iconRawURL: PropTypes.string,
icons: PropTypes.arrayOf(
PropTypes.shape({
baseLayerMD5: PropTypes.string
})
),
id: PropTypes.number.isRequired,
insetIconURL: PropTypes.string,
internetConnectionRequired: PropTypes.bool,
name: PropTypes.oneOfType([
PropTypes.string,
PropTypes.node
]),
onMouseEnter: PropTypes.func.isRequired,
onMouseLeave: PropTypes.func.isRequired,
onSelect: PropTypes.func.isRequired
};
export default injectIntl(LibraryItem);
......@@ -22,21 +22,8 @@ class SpriteLibrary extends React.PureComponent {
constructor (props) {
super(props);
bindAll(this, [
'handleItemSelect',
'handleMouseEnter',
'handleMouseLeave',
'rotateCostume',
'startRotatingCostumes',
'stopRotatingCostumes'
'handleItemSelect'
]);
this.state = {
activeSprite: null,
costumeIndex: 0,
sprites: spriteLibraryContent
};
}
componentWillUnmount () {
clearInterval(this.intervalId);
}
handleItemSelect (item) {
this.props.vm.addSprite(JSON.stringify(item.json)).then(() => {
......@@ -48,46 +35,13 @@ class SpriteLibrary extends React.PureComponent {
label: item.name
});
}
handleMouseEnter (item) {
this.stopRotatingCostumes();
this.setState({activeSprite: item}, this.startRotatingCostumes);
}
handleMouseLeave () {
this.stopRotatingCostumes();
}
startRotatingCostumes () {
if (!this.state.activeSprite) return;
this.rotateCostume();
this.intervalId = setInterval(this.rotateCostume, 300);
}
stopRotatingCostumes () {
this.intervalId = clearInterval(this.intervalId);
}
rotateCostume () {
const costumes = this.state.activeSprite.json.costumes;
const nextCostumeIndex = (this.state.costumeIndex + 1) % costumes.length;
this.setState({
costumeIndex: nextCostumeIndex,
sprites: this.state.sprites.map(sprite => {
if (sprite.name === this.state.activeSprite.name) {
return {
...sprite,
md5: sprite.json.costumes[nextCostumeIndex].baseLayerMD5
};
}
return sprite;
})
});
}
render () {
return (
<LibraryComponent
data={this.state.sprites}
data={spriteLibraryContent}
id="spriteLibrary"
tags={spriteTags}
title={this.props.intl.formatMessage(messages.libraryTitle)}
onItemMouseEnter={this.handleMouseEnter}
onItemMouseLeave={this.handleMouseLeave}
onItemSelected={this.handleItemSelect}
onRequestClose={this.props.onRequestClose}
/>
......
......@@ -164,4 +164,21 @@ describe('Working with costumes', () => {
const logs = await getLogs();
await expect(logs).toEqual([]);
});
test('Costumes animate on mouseover', async () => {
await loadUri(uri);
await clickXpath('//button[@title="Try It"]');
await clickXpath('//button[@aria-label="Choose a Sprite"]');
const searchElement = await findByXpath("//input[@placeholder='Search']");
await searchElement.sendKeys('abb');
const abbyElement = await findByXpath('//*[span[text()="Abby"]]');
driver.actions()
.mouseMove(abbyElement)
.perform();
// wait for one of Abby's alternate costumes to appear
await findByXpath('//img[@src="https://cdn.assets.scratch.mit.edu/internalapi/asset/b6e23922f23b49ddc6f62f675e77417c.svg/get/"]');
const logs = await getLogs();
await expect(logs).toEqual([]);
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment