Skip to content

Commit 69b7eef

Browse files
authored
Merge pull request scratchfoundation#4926 from LLK/sound-lib-touch
Add play buttons to sound library tiles for touch
2 parents 3feff9f + e2c2bd3 commit 69b7eef

File tree

12 files changed

+444
-35
lines changed

12 files changed

+444
-35
lines changed
Lines changed: 9 additions & 0 deletions
Loading
Lines changed: 9 additions & 0 deletions
Loading

src/components/library-item/library-item.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
align-items: center;
88
justify-content: flex-start;
99
flex-basis: 160px;
10+
position: relative;
1011
height: 160px;
1112
max-width: 160px;
1213
margin: $space;

src/components/library-item/library-item.jsx

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
33
import React from 'react';
44

55
import Box from '../box/box.jsx';
6+
import PlayButton from '../../containers/play-button.jsx';
67
import styles from './library-item.css';
78
import classNames from 'classnames';
89

@@ -106,28 +107,40 @@ class LibraryItemComponent extends React.PureComponent {
106107
) : (
107108
<Box
108109
className={classNames(
109-
styles.libraryItem,
110-
this.props.hidden ? styles.hidden : null
110+
styles.libraryItem, {
111+
[styles.hidden]: this.props.hidden
112+
}
111113
)}
112114
role="button"
113115
tabIndex="0"
114116
onBlur={this.props.onBlur}
115117
onClick={this.props.onClick}
116118
onFocus={this.props.onFocus}
117119
onKeyPress={this.props.onKeyPress}
118-
onMouseEnter={this.props.onMouseEnter}
119-
onMouseLeave={this.props.onMouseLeave}
120+
onMouseEnter={this.props.showPlayButton ? null : this.props.onMouseEnter}
121+
onMouseLeave={this.props.showPlayButton ? null : this.props.onMouseLeave}
120122
>
121123
{/* Layers of wrapping is to prevent layout thrashing on animation */}
122124
<Box className={styles.libraryItemImageContainerWrapper}>
123-
<Box className={styles.libraryItemImageContainer}>
125+
<Box
126+
className={styles.libraryItemImageContainer}
127+
onMouseEnter={this.props.showPlayButton ? this.props.onMouseEnter : null}
128+
onMouseLeave={this.props.showPlayButton ? this.props.onMouseLeave : null}
129+
>
124130
<img
125131
className={styles.libraryItemImage}
126132
src={this.props.iconURL}
127133
/>
128134
</Box>
129135
</Box>
130136
<span className={styles.libraryItemName}>{this.props.name}</span>
137+
{this.props.showPlayButton ? (
138+
<PlayButton
139+
isPlaying={this.props.isPlaying}
140+
onPlay={this.props.onPlay}
141+
onStop={this.props.onStop}
142+
/>
143+
) : null}
131144
</Box>
132145
);
133146
}
@@ -149,6 +162,7 @@ LibraryItemComponent.propTypes = {
149162
iconURL: PropTypes.string,
150163
insetIconURL: PropTypes.string,
151164
internetConnectionRequired: PropTypes.bool,
165+
isPlaying: PropTypes.bool,
152166
name: PropTypes.oneOfType([
153167
PropTypes.string,
154168
PropTypes.node
@@ -158,11 +172,15 @@ LibraryItemComponent.propTypes = {
158172
onFocus: PropTypes.func.isRequired,
159173
onKeyPress: PropTypes.func.isRequired,
160174
onMouseEnter: PropTypes.func.isRequired,
161-
onMouseLeave: PropTypes.func.isRequired
175+
onMouseLeave: PropTypes.func.isRequired,
176+
onPlay: PropTypes.func.isRequired,
177+
onStop: PropTypes.func.isRequired,
178+
showPlayButton: PropTypes.bool
162179
};
163180

164181
LibraryItemComponent.defaultProps = {
165-
disabled: false
182+
disabled: false,
183+
showPlayButton: false
166184
};
167185

168186
export default LibraryItemComponent;

src/components/library/library.jsx

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,13 @@ class LibraryComponent extends React.Component {
3838
'handleFilterClear',
3939
'handleMouseEnter',
4040
'handleMouseLeave',
41+
'handlePlayingEnd',
4142
'handleSelect',
4243
'handleTagClick',
4344
'setFilteredDataRef'
4445
]);
4546
this.state = {
46-
selectedItem: null,
47+
playingItem: null,
4748
filterQuery: '',
4849
selectedTag: ALL_TAG.tag,
4950
loaded: false
@@ -54,6 +55,7 @@ class LibraryComponent extends React.Component {
5455
setTimeout(() => {
5556
this.setState({loaded: true});
5657
});
58+
if (this.props.setStopHandler) this.props.setStopHandler(this.handlePlayingEnd);
5759
}
5860
componentDidUpdate (prevProps, prevState) {
5961
if (prevState.filterQuery !== this.state.filterQuery ||
@@ -69,22 +71,58 @@ class LibraryComponent extends React.Component {
6971
this.props.onRequestClose();
7072
}
7173
handleTagClick (tag) {
72-
this.setState({
73-
filterQuery: '',
74-
selectedTag: tag.toLowerCase()
75-
});
74+
if (this.state.playingItem === null) {
75+
this.setState({
76+
filterQuery: '',
77+
selectedTag: tag.toLowerCase()
78+
});
79+
} else {
80+
this.props.onItemMouseLeave(this.getFilteredData()[[this.state.playingItem]]);
81+
this.setState({
82+
filterQuery: '',
83+
playingItem: null,
84+
selectedTag: tag.toLowerCase()
85+
});
86+
}
7687
}
7788
handleMouseEnter (id) {
78-
if (this.props.onItemMouseEnter) this.props.onItemMouseEnter(this.getFilteredData()[id]);
89+
// don't restart if mouse over already playing item
90+
if (this.props.onItemMouseEnter && this.state.playingItem !== id) {
91+
this.props.onItemMouseEnter(this.getFilteredData()[id]);
92+
this.setState({
93+
playingItem: id
94+
});
95+
}
7996
}
8097
handleMouseLeave (id) {
81-
if (this.props.onItemMouseLeave) this.props.onItemMouseLeave(this.getFilteredData()[id]);
98+
if (this.props.onItemMouseLeave) {
99+
this.props.onItemMouseLeave(this.getFilteredData()[id]);
100+
this.setState({
101+
playingItem: null
102+
});
103+
}
104+
}
105+
handlePlayingEnd () {
106+
if (this.state.playingItem !== null) {
107+
this.setState({
108+
playingItem: null
109+
});
110+
}
82111
}
83112
handleFilterChange (event) {
84-
this.setState({
85-
filterQuery: event.target.value,
86-
selectedTag: ALL_TAG.tag
87-
});
113+
if (this.state.playingItem === null) {
114+
this.setState({
115+
filterQuery: event.target.value,
116+
selectedTag: ALL_TAG.tag
117+
});
118+
} else {
119+
this.props.onItemMouseLeave(this.getFilteredData()[[this.state.playingItem]]);
120+
this.setState({
121+
filterQuery: event.target.value,
122+
playingItem: null,
123+
selectedTag: ALL_TAG.tag
124+
});
125+
}
88126
}
89127
handleFilterClear () {
90128
this.setState({filterQuery: ''});
@@ -185,8 +223,10 @@ class LibraryComponent extends React.Component {
185223
id={index}
186224
insetIconURL={dataItem.insetIconURL}
187225
internetConnectionRequired={dataItem.internetConnectionRequired}
226+
isPlaying={this.state.playingItem === index}
188227
key={typeof dataItem.name === 'string' ? dataItem.name : dataItem.rawURL}
189228
name={dataItem.name}
229+
showPlayButton={this.props.showPlayButton}
190230
onMouseEnter={this.handleMouseEnter}
191231
onMouseLeave={this.handleMouseLeave}
192232
onSelect={this.handleSelect}
@@ -227,12 +267,15 @@ LibraryComponent.propTypes = {
227267
onItemMouseLeave: PropTypes.func,
228268
onItemSelected: PropTypes.func,
229269
onRequestClose: PropTypes.func,
270+
setStopHandler: PropTypes.func,
271+
showPlayButton: PropTypes.bool,
230272
tags: PropTypes.arrayOf(PropTypes.shape(TagButton.propTypes)),
231273
title: PropTypes.string.isRequired
232274
};
233275

234276
LibraryComponent.defaultProps = {
235-
filterable: true
277+
filterable: true,
278+
showPlayButton: false
236279
};
237280

238281
export default injectIntl(LibraryComponent);
Lines changed: 27 additions & 0 deletions
Loading
Lines changed: 28 additions & 0 deletions
Loading
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
2+
@import "../../css/colors.css";
3+
@import "../../css/units.css";
4+
5+
.play-button {
6+
display: flex;
7+
align-items: center;
8+
justify-content: center;
9+
10+
overflow: hidden; /* Mask the icon animation */
11+
width: 2.5rem;
12+
height: 2.5rem;
13+
background-color: $sound-primary;
14+
color: $ui-white;
15+
border-radius: 50%;
16+
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
17+
user-select: none;
18+
cursor: pointer;
19+
transition: all 0.15s ease-out;
20+
}
21+
22+
.play-button {
23+
position: absolute;
24+
top: .5rem;
25+
z-index: auto;
26+
}
27+
28+
.play-button:focus {
29+
outline: none;
30+
}
31+
32+
.play-icon {
33+
width: 50%;
34+
}
35+
36+
[dir="ltr"] .play-button {
37+
right: .5rem;
38+
}
39+
40+
[dir="rtl"] .play-button {
41+
left: .5rem;
42+
}

0 commit comments

Comments
 (0)