diff --git a/src/components/library-item/library-item.css b/src/components/library-item/library-item.css
index 760153eae..79aebf796 100644
--- a/src/components/library-item/library-item.css
+++ b/src/components/library-item/library-item.css
@@ -201,42 +201,3 @@
[dir="rtl"] .coming-soon-text {
transform: translate(calc(-2 * $space), calc(2 * $space));
}
-
-.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 {
- left: .5rem;
-}
-
-[dir="rtl"] .play-button {
- right: .5rem;
-}
diff --git a/src/components/library-item/library-item.jsx b/src/components/library-item/library-item.jsx
index f63f92d39..504be2b66 100644
--- a/src/components/library-item/library-item.jsx
+++ b/src/components/library-item/library-item.jsx
@@ -3,18 +3,12 @@ import PropTypes from 'prop-types';
import React from 'react';
import Box from '../box/box.jsx';
+import PlayButton from '../play-button/play-button.jsx';
import styles from './library-item.css';
import classNames from 'classnames';
import bluetoothIconURL from './bluetooth.svg';
import internetConnectionIconURL from './internet-connection.svg';
-import playIcon from './icon--play.svg';
-import stopIcon from './icon--stop.svg';
-
-const preventClick = e => {
- e.stopPropagation();
- e.preventDefault();
-};
/* eslint-disable react/prefer-stateless-function */
class LibraryItemComponent extends React.PureComponent {
@@ -140,19 +134,11 @@ class LibraryItemComponent extends React.PureComponent {
{this.props.name}
{this.props.showPlayButton ? (
-
-

-
+
) : null}
);
diff --git a/src/components/library/library.jsx b/src/components/library/library.jsx
index f2c8e770b..259e922ff 100644
--- a/src/components/library/library.jsx
+++ b/src/components/library/library.jsx
@@ -71,10 +71,19 @@ class LibraryComponent extends React.Component {
this.props.onRequestClose();
}
handleTagClick (tag) {
- this.setState({
- filterQuery: '',
- selectedTag: tag.toLowerCase()
- });
+ if (this.state.playingItem === null) {
+ this.setState({
+ ilterQuery: '',
+ selectedTag: tag.toLowerCase()
+ });
+ } else {
+ const playingId = this.state.playingItem;
+ this.setState({
+ filterQuery: '',
+ playingItem: null,
+ selectedTag: tag.toLowerCase()
+ }, this.props.onItemMouseLeave(this.getFilteredData()[[playingId]]));
+ }
}
handleMouseEnter (id) {
// don't restart if mouse over already playing item
@@ -99,10 +108,19 @@ class LibraryComponent extends React.Component {
}
}
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 {
+ const playingId = this.state.playingItem;
+ this.setState({
+ filterQuery: event.target.value,
+ playingItem: null,
+ selectedTag: ALL_TAG.tag
+ }, this.props.onItemMouseLeave(this.getFilteredData()[[playingId]]));
+ }
}
handleFilterClear () {
this.setState({filterQuery: ''});
diff --git a/src/components/library-item/icon--play.svg b/src/components/play-button/icon--play.svg
similarity index 100%
rename from src/components/library-item/icon--play.svg
rename to src/components/play-button/icon--play.svg
diff --git a/src/components/library-item/icon--stop.svg b/src/components/play-button/icon--stop.svg
similarity index 100%
rename from src/components/library-item/icon--stop.svg
rename to src/components/play-button/icon--stop.svg
diff --git a/src/components/play-button/play-button.css b/src/components/play-button/play-button.css
new file mode 100644
index 000000000..fbc901e66
--- /dev/null
+++ b/src/components/play-button/play-button.css
@@ -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 {
+ left: .5rem;
+}
+
+[dir="rtl"] .play-button {
+ right: .5rem;
+}
diff --git a/src/components/play-button/play-button.jsx b/src/components/play-button/play-button.jsx
new file mode 100644
index 000000000..8071ce849
--- /dev/null
+++ b/src/components/play-button/play-button.jsx
@@ -0,0 +1,148 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import classNames from 'classnames';
+import bindAll from 'lodash.bindall';
+
+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'
+ }
+});
+
+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,
+ intl,
+ isPlaying,
+ onPlay, // eslint-disable-line no-unused-vars
+ onStop // eslint-disable-line no-unused-vars
+ } = this.props;
+ const label = isPlaying ?
+ intl.formatMessage(messages.stop) :
+ intl.formatMessage(messages.play);
+
+ return (
+
+

+
+ );
+ }
+}
+
+PlayButton.propTypes = {
+ className: PropTypes.string,
+ intl: intlShape,
+ isPlaying: PropTypes.bool.isRequired,
+ onPlay: PropTypes.func.isRequired,
+ onStop: PropTypes.func.isRequired
+};
+
+export default injectIntl(PlayButton);
diff --git a/src/containers/library-item.jsx b/src/containers/library-item.jsx
index 75f6b2998..8881a57cc 100644
--- a/src/containers/library-item.jsx
+++ b/src/containers/library-item.jsx
@@ -39,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') {
@@ -48,30 +50,32 @@ 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 (e) {
- e.stopPropagation(); // To prevent from bubbling back to handleClick
- e.preventDefault();
+ handlePlay () {
this.props.onMouseEnter(this.props.id);
}
- handleStop (e) {
- e.stopPropagation(); // To prevent from bubbling back to handleClick
- e.preventDefault();
+ handleStop () {
this.props.onMouseLeave(this.props.id);
}
startRotatingIcons () {