Merge pull request #4926 from LLK/sound-lib-touch
Add play buttons to sound library tiles for touch
This commit is contained in:
12 files changed
+444
-35
No files matched your search
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 56.2 (81672) - https://sketch.com -->
|
||||
<title>Sound/General/Library Tile Icon/RTL</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="Sound/General/Library-Tile-Icon/RTL" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M40.7941213,60.3333334 C39.5757531,60.3333334 38.3858512,59.7627412 37.7254274,58.7262523 C34.4973213,53.700852 34.4973213,47.4609802 37.7254274,42.43558 C38.7160634,40.8860816 40.8909074,40.373072 42.5989001,41.2839258 C44.2898131,42.2052491 44.8534507,44.2101744 43.8514282,45.7701424 C41.9441696,48.7382694 41.9441696,52.423563 43.8514282,55.3969245 C44.8534507,56.951658 44.2898131,58.956583 42.5989001,59.8779064 C42.0295692,60.1867593 41.4033053,60.3333334 40.7941213,60.3333334 Z M30.014623,64.6666666 C28.9761967,64.6666666 27.9668853,64.1309203 27.3991478,63.1577291 C22.8669507,55.401691 22.8669507,45.7582521 27.3991478,38.002214 C28.2531806,36.5473425 30.1068197,36.0705772 31.5528528,36.9208906 C32.9940332,37.7859493 33.4744267,39.6733502 32.6203939,41.1331368 C29.2188198,46.957538 29.2188198,54.202405 32.6203939,60.0317213 C33.4744267,61.491508 32.9940332,63.3739938 31.5528528,64.2390524 C31.0724594,64.5290438 30.5338363,64.6666666 30.014623,64.6666666 Z M48.8695653,34.1786293 C48.8695653,30.5538239 53.3068341,28.6536838 56.0736627,31.1003021 L62.1037975,36.4397458 C64.2189991,38.3098044 66.9754844,39.3476118 69.8405742,39.3476118 L70.8283578,39.3476118 C73.6831043,39.3476118 76,41.5936876 76,44.3611739 L76,54.689112 C76,57.4616116 73.6831043,59.7026738 70.8283578,59.7026738 L69.7836861,59.7026738 C66.9185963,59.7026738 64.1621111,60.7404813 62.0469094,62.6105398 L56.0736627,67.899848 C53.3068341,70.3464662 48.8695653,68.4463264 48.8695653,64.8165072 L48.8695653,34.1786293 Z" id="Shape" fill="#575E75" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 56.2 (81672) - https://sketch.com -->
|
||||
<title>Sound/General/Library Tile Icon/LTR</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="Sound/General/Library-Tile-Icon/LTR" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M59.2058787,60.3333334 C58.5966947,60.3333334 57.9704308,60.1867593 57.4010999,59.8779064 C55.7101869,58.956583 55.1465493,56.951658 56.1485718,55.3969245 C58.0558304,52.423563 58.0558304,48.7382694 56.1485718,45.7701424 C55.1465493,44.2101744 55.7101869,42.2052491 57.4010999,41.2839258 C59.1090926,40.373072 61.2839366,40.8860816 62.2745726,42.43558 C65.5026787,47.4609802 65.5026787,53.700852 62.2745726,58.7262523 C61.6141488,59.7627412 60.4242469,60.3333334 59.2058787,60.3333334 Z M69.985377,64.6666666 C69.4661637,64.6666666 68.9275406,64.5290438 68.4471472,64.2390524 C67.0059668,63.3739938 66.5255733,61.491508 67.3796061,60.0317213 C70.7811802,54.202405 70.7811802,46.957538 67.3796061,41.1331368 C66.5255733,39.6733502 67.0059668,37.7859493 68.4471472,36.9208906 C69.8931803,36.0705772 71.7468194,36.5473425 72.6008522,38.002214 C77.1330493,45.7582521 77.1330493,55.401691 72.6008522,63.1577291 C72.0331147,64.1309203 71.0238033,64.6666666 69.985377,64.6666666 Z M51.1304347,34.1786293 L51.1304347,64.8165072 C51.1304347,68.4463264 46.6931659,70.3464662 43.9263373,67.899848 L37.9530906,62.6105398 C35.8378889,60.7404813 33.0814037,59.7026738 30.2163139,59.7026738 L29.1716422,59.7026738 C26.3168957,59.7026738 24,57.4616116 24,54.689112 L24,44.3611739 C24,41.5936876 26.3168957,39.3476118 29.1716422,39.3476118 L30.1594258,39.3476118 C33.0245156,39.3476118 35.7810009,38.3098044 37.8962025,36.4397458 L43.9263373,31.1003021 C46.6931659,28.6536838 51.1304347,30.5538239 51.1304347,34.1786293 Z" id="Combined-Shape" fill="#575E75" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -7,6 +7,7 @@
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
flex-basis: 160px;
|
||||
position: relative;
|
||||
height: 160px;
|
||||
max-width: 160px;
|
||||
margin: $space;
|
||||
|
||||
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import Box from '../box/box.jsx';
|
||||
import PlayButton from '../../containers/play-button.jsx';
|
||||
import styles from './library-item.css';
|
||||
import classNames from 'classnames';
|
||||
|
||||
@@ -106,8 +107,9 @@ class LibraryItemComponent extends React.PureComponent {
|
||||
) : (
|
||||
<Box
|
||||
className={classNames(
|
||||
styles.libraryItem,
|
||||
this.props.hidden ? styles.hidden : null
|
||||
styles.libraryItem, {
|
||||
[styles.hidden]: this.props.hidden
|
||||
}
|
||||
)}
|
||||
role="button"
|
||||
tabIndex="0"
|
||||
@@ -115,12 +117,16 @@ class LibraryItemComponent extends React.PureComponent {
|
||||
onClick={this.props.onClick}
|
||||
onFocus={this.props.onFocus}
|
||||
onKeyPress={this.props.onKeyPress}
|
||||
onMouseEnter={this.props.onMouseEnter}
|
||||
onMouseLeave={this.props.onMouseLeave}
|
||||
onMouseEnter={this.props.showPlayButton ? null : this.props.onMouseEnter}
|
||||
onMouseLeave={this.props.showPlayButton ? null : this.props.onMouseLeave}
|
||||
>
|
||||
{/* Layers of wrapping is to prevent layout thrashing on animation */}
|
||||
<Box className={styles.libraryItemImageContainerWrapper}>
|
||||
<Box className={styles.libraryItemImageContainer}>
|
||||
<Box
|
||||
className={styles.libraryItemImageContainer}
|
||||
onMouseEnter={this.props.showPlayButton ? this.props.onMouseEnter : null}
|
||||
onMouseLeave={this.props.showPlayButton ? this.props.onMouseLeave : null}
|
||||
>
|
||||
<img
|
||||
className={styles.libraryItemImage}
|
||||
src={this.props.iconURL}
|
||||
@@ -128,6 +134,13 @@ class LibraryItemComponent extends React.PureComponent {
|
||||
</Box>
|
||||
</Box>
|
||||
<span className={styles.libraryItemName}>{this.props.name}</span>
|
||||
{this.props.showPlayButton ? (
|
||||
<PlayButton
|
||||
isPlaying={this.props.isPlaying}
|
||||
onPlay={this.props.onPlay}
|
||||
onStop={this.props.onStop}
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -149,6 +162,7 @@ LibraryItemComponent.propTypes = {
|
||||
iconURL: PropTypes.string,
|
||||
insetIconURL: PropTypes.string,
|
||||
internetConnectionRequired: PropTypes.bool,
|
||||
isPlaying: PropTypes.bool,
|
||||
name: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.node
|
||||
@@ -158,11 +172,15 @@ LibraryItemComponent.propTypes = {
|
||||
onFocus: PropTypes.func.isRequired,
|
||||
onKeyPress: PropTypes.func.isRequired,
|
||||
onMouseEnter: PropTypes.func.isRequired,
|
||||
onMouseLeave: PropTypes.func.isRequired
|
||||
onMouseLeave: PropTypes.func.isRequired,
|
||||
onPlay: PropTypes.func.isRequired,
|
||||
onStop: PropTypes.func.isRequired,
|
||||
showPlayButton: PropTypes.bool
|
||||
};
|
||||
|
||||
LibraryItemComponent.defaultProps = {
|
||||
disabled: false
|
||||
disabled: false,
|
||||
showPlayButton: false
|
||||
};
|
||||
|
||||
export default LibraryItemComponent;
|
||||
@@ -38,12 +38,13 @@ class LibraryComponent extends React.Component {
|
||||
'handleFilterClear',
|
||||
'handleMouseEnter',
|
||||
'handleMouseLeave',
|
||||
'handlePlayingEnd',
|
||||
'handleSelect',
|
||||
'handleTagClick',
|
||||
'setFilteredDataRef'
|
||||
]);
|
||||
this.state = {
|
||||
selectedItem: null,
|
||||
playingItem: null,
|
||||
filterQuery: '',
|
||||
selectedTag: ALL_TAG.tag,
|
||||
loaded: false
|
||||
@@ -54,6 +55,7 @@ class LibraryComponent extends React.Component {
|
||||
setTimeout(() => {
|
||||
this.setState({loaded: true});
|
||||
});
|
||||
if (this.props.setStopHandler) this.props.setStopHandler(this.handlePlayingEnd);
|
||||
}
|
||||
componentDidUpdate (prevProps, prevState) {
|
||||
if (prevState.filterQuery !== this.state.filterQuery ||
|
||||
@@ -69,22 +71,58 @@ class LibraryComponent extends React.Component {
|
||||
this.props.onRequestClose();
|
||||
}
|
||||
handleTagClick (tag) {
|
||||
this.setState({
|
||||
filterQuery: '',
|
||||
selectedTag: tag.toLowerCase()
|
||||
});
|
||||
if (this.state.playingItem === null) {
|
||||
this.setState({
|
||||
filterQuery: '',
|
||||
selectedTag: tag.toLowerCase()
|
||||
});
|
||||
} else {
|
||||
this.props.onItemMouseLeave(this.getFilteredData()[[this.state.playingItem]]);
|
||||
this.setState({
|
||||
filterQuery: '',
|
||||
playingItem: null,
|
||||
selectedTag: tag.toLowerCase()
|
||||
});
|
||||
}
|
||||
}
|
||||
handleMouseEnter (id) {
|
||||
if (this.props.onItemMouseEnter) this.props.onItemMouseEnter(this.getFilteredData()[id]);
|
||||
// don't restart if mouse over already playing item
|
||||
if (this.props.onItemMouseEnter && this.state.playingItem !== id) {
|
||||
this.props.onItemMouseEnter(this.getFilteredData()[id]);
|
||||
this.setState({
|
||||
playingItem: id
|
||||
});
|
||||
}
|
||||
}
|
||||
handleMouseLeave (id) {
|
||||
if (this.props.onItemMouseLeave) this.props.onItemMouseLeave(this.getFilteredData()[id]);
|
||||
if (this.props.onItemMouseLeave) {
|
||||
this.props.onItemMouseLeave(this.getFilteredData()[id]);
|
||||
this.setState({
|
||||
playingItem: null
|
||||
});
|
||||
}
|
||||
}
|
||||
handlePlayingEnd () {
|
||||
if (this.state.playingItem !== null) {
|
||||
this.setState({
|
||||
playingItem: null
|
||||
});
|
||||
}
|
||||
}
|
||||
handleFilterChange (event) {
|
||||
this.setState({
|
||||
filterQuery: event.target.value,
|
||||
selectedTag: ALL_TAG.tag
|
||||
});
|
||||
if (this.state.playingItem === null) {
|
||||
this.setState({
|
||||
filterQuery: event.target.value,
|
||||
selectedTag: ALL_TAG.tag
|
||||
});
|
||||
} else {
|
||||
this.props.onItemMouseLeave(this.getFilteredData()[[this.state.playingItem]]);
|
||||
this.setState({
|
||||
filterQuery: event.target.value,
|
||||
playingItem: null,
|
||||
selectedTag: ALL_TAG.tag
|
||||
});
|
||||
}
|
||||
}
|
||||
handleFilterClear () {
|
||||
this.setState({filterQuery: ''});
|
||||
@@ -185,8 +223,10 @@ class LibraryComponent extends React.Component {
|
||||
id={index}
|
||||
insetIconURL={dataItem.insetIconURL}
|
||||
internetConnectionRequired={dataItem.internetConnectionRequired}
|
||||
isPlaying={this.state.playingItem === index}
|
||||
key={typeof dataItem.name === 'string' ? dataItem.name : dataItem.rawURL}
|
||||
name={dataItem.name}
|
||||
showPlayButton={this.props.showPlayButton}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
onSelect={this.handleSelect}
|
||||
@@ -227,12 +267,15 @@ LibraryComponent.propTypes = {
|
||||
onItemMouseLeave: PropTypes.func,
|
||||
onItemSelected: PropTypes.func,
|
||||
onRequestClose: PropTypes.func,
|
||||
setStopHandler: PropTypes.func,
|
||||
showPlayButton: PropTypes.bool,
|
||||
tags: PropTypes.arrayOf(PropTypes.shape(TagButton.propTypes)),
|
||||
title: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
LibraryComponent.defaultProps = {
|
||||
filterable: true
|
||||
filterable: true,
|
||||
showPlayButton: false
|
||||
};
|
||||
|
||||
export default injectIntl(LibraryComponent);
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="49px" height="52px" viewBox="-5 0 49 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>play</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<path d="M17.2214651,8.34499443 C19.7766419,4.08636653 23.9234057,4.09303946 26.4745787,8.34499443 L40.1520974,31.140859 C42.7072741,35.3994869 40.7489672,38.8517869 35.7726637,38.8517869 L7.92338006,38.8517869 C2.9495089,38.8517869 0.992773429,35.392814 3.54394641,31.140859 L17.2214651,8.34499443 Z" id="path-1"></path>
|
||||
<filter x="-16.7%" y="-19.3%" width="133.5%" height="138.6%" filterUnits="objectBoundingBox" id="filter-2">
|
||||
<feMorphology radius="2.5" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1"></feMorphology>
|
||||
<feOffset dx="0" dy="0" in="shadowSpreadOuter1" result="shadowOffsetOuter1"></feOffset>
|
||||
<feComposite in="shadowOffsetOuter1" in2="SourceAlpha" operator="out" result="shadowOffsetOuter1"></feComposite>
|
||||
<feColorMatrix values="0 0 0 0 0.298039216 0 0 0 0 0.592156863 0 0 0 0 1 0 0 0 0.1 0" type="matrix" in="shadowOffsetOuter1"></feColorMatrix>
|
||||
</filter>
|
||||
</defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Desktop---1280x720" transform="translate(-623.000000, -347.000000)">
|
||||
<g id="Step-3---Altering-Suggested-Trim" transform="translate(0.000000, 42.000000)">
|
||||
<g id="Play-/-Record-/-Stop" transform="translate(623.000000, 307.000000)">
|
||||
<g id="play" transform="translate(21.849131, 22.002656) rotate(90.000000) translate(-21.849131, -22.002656) ">
|
||||
<use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
|
||||
<use stroke="#FFFFFF" stroke-width="1" fill="#FFFFFF" fill-rule="evenodd" xlink:href="#path-1"></use>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="52px" height="52px" viewBox="0 0 52 52" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>stop-playback</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<rect id="path-1" x="0" y="0" width="44" height="44" rx="4"></rect>
|
||||
<filter x="-13.6%" y="-13.6%" width="127.3%" height="127.3%" filterUnits="objectBoundingBox" id="filter-2">
|
||||
<feMorphology radius="2" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1"></feMorphology>
|
||||
<feOffset dx="0" dy="0" in="shadowSpreadOuter1" result="shadowOffsetOuter1"></feOffset>
|
||||
<feComposite in="shadowOffsetOuter1" in2="SourceAlpha" operator="out" result="shadowOffsetOuter1"></feComposite>
|
||||
<feColorMatrix values="0 0 0 0 0.298039216 0 0 0 0 0.592156863 0 0 0 0 1 0 0 0 0.1 0" type="matrix" in="shadowOffsetOuter1"></feColorMatrix>
|
||||
</filter>
|
||||
</defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Desktop---1280x720" transform="translate(-619.000000, -345.000000)">
|
||||
<g id="Step-4---Playing-Trim" transform="translate(0.000000, 42.000000)">
|
||||
<g id="Play-/-Record-/-Stop" transform="translate(623.000000, 307.000000)">
|
||||
<g id="stop-playback">
|
||||
<use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
|
||||
<use fill="#FFFFFF" fill-rule="evenodd" xlink:href="#path-1"></use>
|
||||
<rect stroke="#FFFFFF" stroke-width="1" x="0.5" y="0.5" width="43" height="43" rx="4"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,42 @@
|
||||
|
||||
@import "../../css/colors.css";
|
||||
@import "../../css/units.css";
|
||||
|
||||
.play-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
overflow: hidden; /* Mask the icon animation */
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
background-color: $sound-primary;
|
||||
color: $ui-white;
|
||||
border-radius: 50%;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease-out;
|
||||
}
|
||||
|
||||
.play-button {
|
||||
position: absolute;
|
||||
top: .5rem;
|
||||
z-index: auto;
|
||||
}
|
||||
|
||||
.play-button:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.play-icon {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
[dir="ltr"] .play-button {
|
||||
right: .5rem;
|
||||
}
|
||||
|
||||
[dir="rtl"] .play-button {
|
||||
left: .5rem;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import {defineMessages, injectIntl, intlShape} from 'react-intl';
|
||||
|
||||
import styles from './play-button.css';
|
||||
|
||||
import playIcon from './icon--play.svg';
|
||||
import stopIcon from './icon--stop.svg';
|
||||
|
||||
const messages = defineMessages({
|
||||
play: {
|
||||
id: 'gui.playButton.play',
|
||||
description: 'Title of the button to start playing the sound',
|
||||
defaultMessage: 'Play'
|
||||
},
|
||||
stop: {
|
||||
id: 'gui.playButton.stop',
|
||||
description: 'Title of the button to stop the sound',
|
||||
defaultMessage: 'Stop'
|
||||
}
|
||||
});
|
||||
|
||||
const PlayButtonComponent = ({
|
||||
className,
|
||||
intl,
|
||||
isPlaying,
|
||||
onClick,
|
||||
onMouseDown,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
setButtonRef,
|
||||
...props
|
||||
}) => {
|
||||
const label = isPlaying ?
|
||||
intl.formatMessage(messages.stop) :
|
||||
intl.formatMessage(messages.play);
|
||||
|
||||
return (
|
||||
<div
|
||||
aria-label={label}
|
||||
className={classNames(styles.playButton, className, {
|
||||
[styles.playing]: isPlaying
|
||||
})}
|
||||
onClick={onClick}
|
||||
onMouseDown={onMouseDown}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
ref={setButtonRef}
|
||||
{...props}
|
||||
>
|
||||
<img
|
||||
className={styles.playIcon}
|
||||
draggable={false}
|
||||
src={isPlaying ? stopIcon : playIcon}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
PlayButtonComponent.propTypes = {
|
||||
className: PropTypes.string,
|
||||
intl: intlShape,
|
||||
isPlaying: PropTypes.bool.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
onMouseDown: PropTypes.func.isRequired,
|
||||
onMouseEnter: PropTypes.func.isRequired,
|
||||
onMouseLeave: PropTypes.func.isRequired,
|
||||
setButtonRef: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(PlayButtonComponent);
|
||||
@@ -15,6 +15,8 @@ class LibraryItem extends React.PureComponent {
|
||||
'handleKeyPress',
|
||||
'handleMouseEnter',
|
||||
'handleMouseLeave',
|
||||
'handlePlay',
|
||||
'handleStop',
|
||||
'rotateIcon',
|
||||
'startRotatingIcons',
|
||||
'stopRotatingIcons'
|
||||
@@ -37,7 +39,9 @@ class LibraryItem extends React.PureComponent {
|
||||
e.preventDefault();
|
||||
}
|
||||
handleFocus (id) {
|
||||
this.handleMouseEnter(id);
|
||||
if (!this.props.showPlayButton) {
|
||||
this.handleMouseEnter(id);
|
||||
}
|
||||
}
|
||||
handleKeyPress (e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
@@ -46,22 +50,34 @@ class LibraryItem extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
handleMouseEnter () {
|
||||
this.props.onMouseEnter(this.props.id);
|
||||
if (this.props.icons && this.props.icons.length) {
|
||||
this.stopRotatingIcons();
|
||||
this.setState({
|
||||
isRotatingIcon: true
|
||||
}, this.startRotatingIcons);
|
||||
// only show hover effects on the item if not showing a play button
|
||||
if (!this.props.showPlayButton) {
|
||||
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);
|
||||
// only show hover effects on the item if not showing a play button
|
||||
if (!this.props.showPlayButton) {
|
||||
this.props.onMouseLeave(this.props.id);
|
||||
if (this.props.icons && this.props.icons.length) {
|
||||
this.setState({
|
||||
isRotatingIcon: false
|
||||
}, this.stopRotatingIcons);
|
||||
}
|
||||
}
|
||||
}
|
||||
handlePlay () {
|
||||
this.props.onMouseEnter(this.props.id);
|
||||
}
|
||||
handleStop () {
|
||||
this.props.onMouseLeave(this.props.id);
|
||||
}
|
||||
startRotatingIcons () {
|
||||
this.rotateIcon();
|
||||
this.intervalId = setInterval(this.rotateIcon, 300);
|
||||
@@ -104,13 +120,17 @@ class LibraryItem extends React.PureComponent {
|
||||
id={this.props.id}
|
||||
insetIconURL={this.props.insetIconURL}
|
||||
internetConnectionRequired={this.props.internetConnectionRequired}
|
||||
isPlaying={this.props.isPlaying}
|
||||
name={this.props.name}
|
||||
showPlayButton={this.props.showPlayButton}
|
||||
onBlur={this.handleBlur}
|
||||
onClick={this.handleClick}
|
||||
onFocus={this.handleFocus}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
onPlay={this.handlePlay}
|
||||
onStop={this.handleStop}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -137,13 +157,15 @@ LibraryItem.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
insetIconURL: PropTypes.string,
|
||||
internetConnectionRequired: PropTypes.bool,
|
||||
isPlaying: PropTypes.bool,
|
||||
name: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.node
|
||||
]),
|
||||
onMouseEnter: PropTypes.func.isRequired,
|
||||
onMouseLeave: PropTypes.func.isRequired,
|
||||
onSelect: PropTypes.func.isRequired
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
showPlayButton: PropTypes.bool
|
||||
};
|
||||
|
||||
export default injectIntl(LibraryItem);
|
||||
@@ -0,0 +1,115 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import bindAll from 'lodash.bindall';
|
||||
|
||||
import PlayButtonComponent from '../components/play-button/play-button.jsx';
|
||||
|
||||
class PlayButton extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleClick',
|
||||
'handleMouseDown',
|
||||
'handleMouseEnter',
|
||||
'handleMouseLeave',
|
||||
'handleTouchStart',
|
||||
'setButtonRef'
|
||||
]);
|
||||
this.state = {
|
||||
touchStarted: false
|
||||
};
|
||||
}
|
||||
getDerivedStateFromProps (props, state) {
|
||||
// if touchStarted is true and it's not playing, the sound must have ended.
|
||||
// reset the touchStarted state to allow the sound to be replayed
|
||||
if (state.touchStarted && !props.isPlaying) {
|
||||
return {
|
||||
touchStarted: false
|
||||
};
|
||||
}
|
||||
return null; // nothing changed
|
||||
}
|
||||
componentDidMount () {
|
||||
// Touch start
|
||||
this.buttonRef.addEventListener('touchstart', this.handleTouchStart);
|
||||
}
|
||||
componentWillUnmount () {
|
||||
this.buttonRef.removeEventListener('touchstart', this.handleTouchStart);
|
||||
}
|
||||
handleClick (e) {
|
||||
// stop the click from propagating out of the button
|
||||
e.stopPropagation();
|
||||
}
|
||||
handleMouseDown (e) {
|
||||
// prevent default (focus) on mouseDown
|
||||
e.preventDefault();
|
||||
if (this.props.isPlaying) {
|
||||
// stop sound and reset touch state
|
||||
this.props.onStop();
|
||||
if (this.state.touchstarted) this.setState({touchStarted: false});
|
||||
} else {
|
||||
this.props.onPlay();
|
||||
if (this.state.touchstarted) {
|
||||
// started on touch, but now clicked mouse
|
||||
this.setState({touchStarted: false});
|
||||
}
|
||||
}
|
||||
}
|
||||
handleTouchStart (e) {
|
||||
if (this.props.isPlaying) {
|
||||
// If playing, stop sound, and reset touch state
|
||||
e.preventDefault();
|
||||
this.setState({touchStarted: false});
|
||||
this.props.onStop();
|
||||
} else {
|
||||
// otherwise start playing, and set touch state
|
||||
e.preventDefault();
|
||||
this.setState({touchStarted: true});
|
||||
this.props.onPlay();
|
||||
}
|
||||
}
|
||||
handleMouseEnter (e) {
|
||||
// start the sound if it's not already playing
|
||||
e.preventDefault();
|
||||
if (!this.props.isPlaying) {
|
||||
this.props.onPlay();
|
||||
}
|
||||
}
|
||||
handleMouseLeave () {
|
||||
// stop the sound unless it was started by touch
|
||||
if (this.props.isPlaying && !this.state.touchstarted) {
|
||||
this.props.onStop();
|
||||
}
|
||||
}
|
||||
setButtonRef (ref) {
|
||||
this.buttonRef = ref;
|
||||
}
|
||||
render () {
|
||||
const {
|
||||
className,
|
||||
isPlaying,
|
||||
onPlay, // eslint-disable-line no-unused-vars
|
||||
onStop // eslint-disable-line no-unused-vars
|
||||
} = this.props;
|
||||
return (
|
||||
<PlayButtonComponent
|
||||
className={className}
|
||||
isPlaying={isPlaying}
|
||||
onClick={this.handleClick}
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
setButtonRef={this.setButtonRef}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PlayButton.propTypes = {
|
||||
className: PropTypes.string,
|
||||
isPlaying: PropTypes.bool.isRequired,
|
||||
onPlay: PropTypes.func.isRequired,
|
||||
onStop: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default PlayButton;
|
||||
@@ -7,8 +7,8 @@ import AudioEngine from 'scratch-audio';
|
||||
|
||||
import LibraryComponent from '../components/library/library.jsx';
|
||||
|
||||
import soundIcon from '../components/asset-panel/icon--sound.svg';
|
||||
import soundIconRtl from '../components/asset-panel/icon--sound-rtl.svg';
|
||||
import soundIcon from '../components/library-item/lib-icon--sound.svg';
|
||||
import soundIconRtl from '../components/library-item/lib-icon--sound-rtl.svg';
|
||||
|
||||
import soundLibraryContent from '../lib/libraries/sounds.json';
|
||||
import soundTags from '../lib/libraries/sound-tags';
|
||||
@@ -29,7 +29,9 @@ class SoundLibrary extends React.PureComponent {
|
||||
bindAll(this, [
|
||||
'handleItemSelected',
|
||||
'handleItemMouseEnter',
|
||||
'handleItemMouseLeave'
|
||||
'handleItemMouseLeave',
|
||||
'onStop',
|
||||
'setStopHandler'
|
||||
]);
|
||||
|
||||
/**
|
||||
@@ -43,6 +45,11 @@ class SoundLibrary extends React.PureComponent {
|
||||
* @type {Promise<SoundPlayer>}
|
||||
*/
|
||||
this.playingSoundPromise = null;
|
||||
|
||||
/**
|
||||
* function to call when the sound ends
|
||||
*/
|
||||
this.handleStop = null;
|
||||
}
|
||||
componentDidMount () {
|
||||
this.audioEngine = new AudioEngine();
|
||||
@@ -51,10 +58,22 @@ class SoundLibrary extends React.PureComponent {
|
||||
componentWillUnmount () {
|
||||
this.stopPlayingSound();
|
||||
}
|
||||
onStop () {
|
||||
if (this.playingSoundPromise !== null) {
|
||||
this.playingSoundPromise.then(soundPlayer => soundPlayer.removeListener('stop', this.onStop));
|
||||
if (this.handleStop) this.handleStop();
|
||||
}
|
||||
|
||||
}
|
||||
setStopHandler (func) {
|
||||
this.handleStop = func;
|
||||
}
|
||||
stopPlayingSound () {
|
||||
// Playback is queued, playing, or has played recently and finished
|
||||
// normally.
|
||||
if (this.playingSoundPromise !== null) {
|
||||
// Forcing sound to stop, so stop listening for sound ending:
|
||||
this.playingSoundPromise.then(soundPlayer => soundPlayer.removeListener('stop', this.onStop));
|
||||
// Queued playback began playing before this method.
|
||||
if (this.playingSoundPromise.isPlaying) {
|
||||
// Fetch the player from the promise and stop playback soon.
|
||||
@@ -102,6 +121,7 @@ class SoundLibrary extends React.PureComponent {
|
||||
// Play the sound. Playing the sound will always come before a
|
||||
// paired stop if the sound must stop early.
|
||||
soundPlayer.play();
|
||||
soundPlayer.addListener('stop', this.onStop);
|
||||
// Set that the sound is playing. This affects the type of stop
|
||||
// instruction given if the sound must stop early.
|
||||
if (this.playingSoundPromise !== null) {
|
||||
@@ -141,8 +161,10 @@ class SoundLibrary extends React.PureComponent {
|
||||
|
||||
return (
|
||||
<LibraryComponent
|
||||
showPlayButton
|
||||
data={soundLibraryThumbnailData}
|
||||
id="soundLibrary"
|
||||
setStopHandler={this.setStopHandler}
|
||||
tags={soundTags}
|
||||
title={this.props.intl.formatMessage(messages.libraryTitle)}
|
||||
onItemMouseEnter={this.handleItemMouseEnter}
|
||||
|
||||
Reference in new issue
Block a user