Skip to content

Commit a924340

Browse files
Add an indicator to show that the microphone is listening (#3205)
* Add mic indicator * Fix mic indicator position and margin * Don’t always show indicator, don’t sneaky-enable extension * Position mic indicator in RTL * Actually, don’t move indicator in RTL * Update event name for MIC_LISTENING * Move mic indicator state into redux * Move stageSizeToTransform into screen-utils * Position mic indicator and question at bottom of stage * Fix pointer events * JSDOC for stageSizeToTransform * Pass micIndicator into StageComponent via …props
1 parent f562569 commit a924340

File tree

14 files changed

+189
-35
lines changed

14 files changed

+189
-35
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
@keyframes popIn {
2+
from {transform: scale(0.5)}
3+
to {transform: scale(1)}
4+
}
5+
6+
.mic-img {
7+
margin: 10px;
8+
transform-origin: center;
9+
animation: popIn 0.1s ease-in-out;
10+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import styles from './mic-indicator.css';
4+
import micIcon from './mic-indicator.svg';
5+
import {stageSizeToTransform} from '../../lib/screen-utils';
6+
7+
const MicIndicatorComponent = props => (
8+
<div
9+
className={props.className}
10+
style={stageSizeToTransform(props.stageSize)}
11+
>
12+
<img
13+
className={styles.micImg}
14+
src={micIcon}
15+
/>
16+
</div>
17+
);
18+
19+
MicIndicatorComponent.propTypes = {
20+
className: PropTypes.string,
21+
stageSize: PropTypes.shape({
22+
width: PropTypes.number,
23+
height: PropTypes.number,
24+
widthDefault: PropTypes.number,
25+
heightDefault: PropTypes.number
26+
}).isRequired
27+
};
28+
29+
export default MicIndicatorComponent;
Lines changed: 40 additions & 0 deletions
Loading

src/components/monitor-list/monitor-list.jsx

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,10 @@ import Box from '../box/box.jsx';
44
import Monitor from '../../containers/monitor.jsx';
55
import PropTypes from 'prop-types';
66
import {OrderedMap} from 'immutable';
7+
import {stageSizeToTransform} from '../../lib/screen-utils';
78

89
import styles from './monitor-list.css';
910

10-
const stageSizeToTransform = ({width, height, widthDefault, heightDefault}) => {
11-
const scaleX = width / widthDefault;
12-
const scaleY = height / heightDefault;
13-
if (scaleX === 1 && scaleY === 1) {
14-
// Do not set a transform if the scale is 1 because
15-
// it messes up `position: fixed` elements like the context menu.
16-
return;
17-
}
18-
return {transform: `scale(${scaleX},${scaleY})`};
19-
};
20-
2111
const MonitorList = props => (
2212
<Box
2313
// Use static `monitor-overlay` class for bounds of draggables

src/components/question/question.css

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
@import "../../css/units.css";
22
@import "../../css/colors.css";
33

4-
.question-wrapper {
5-
position: absolute;
6-
bottom: 0;
7-
left: 0;
8-
width: 100%;
9-
}
10-
114
.question-container {
125
margin: $space;
136
border: 1px solid $ui-black-transparent;

src/components/question/question.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import enterIcon from './icon--enter.svg';
77
const QuestionComponent = props => {
88
const {
99
answer,
10+
className,
1011
question,
1112
onChange,
1213
onClick,
1314
onKeyPress
1415
} = props;
1516
return (
16-
<div className={styles.questionWrapper}>
17+
<div className={className}>
1718
<div className={styles.questionContainer}>
1819
{question ? (
1920
<div className={styles.questionLabel}>{question}</div>
@@ -43,6 +44,7 @@ const QuestionComponent = props => {
4344

4445
QuestionComponent.propTypes = {
4546
answer: PropTypes.string,
47+
className: PropTypes.string,
4648
onChange: PropTypes.func.isRequired,
4749
onClick: PropTypes.func.isRequired,
4850
onKeyPress: PropTypes.func.isRequired,

src/components/stage/stage.css

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,6 @@
7474
border: none;
7575
}
7676

77-
.question-wrapper {
78-
position: absolute;
79-
}
80-
8177
/* adjust monitors when stage is standard size:
8278
shift them down and right to compensate for the stage's border */
8379
.stage-wrapper .monitor-wrapper {
@@ -93,7 +89,7 @@ to adjust for the border using a different method */
9389
padding-bottom: calc($stage-full-screen-stage-padding + $stage-full-screen-border-width);
9490
}
9591

96-
.monitor-wrapper, .color-picker-wrapper, .queston-wrapper {
92+
.monitor-wrapper, .color-picker-wrapper {
9793
position: absolute;
9894
top: 0;
9995
left: 0;
@@ -110,3 +106,24 @@ to adjust for the border using a different method */
110106
z-index: $z-index-dragging-sprite;
111107
filter: drop-shadow(5px 5px 5px $ui-black-transparent);
112108
}
109+
110+
.stage-bottom-wrapper {
111+
position: absolute;
112+
display: flex;
113+
flex-direction: column;
114+
justify-content: flex-end;
115+
top: 0;
116+
overflow: hidden;
117+
pointer-events: none;
118+
}
119+
120+
.mic-indicator {
121+
transform-origin: bottom right;
122+
z-index: $z-index-stage-indicator;
123+
pointer-events: none;
124+
align-self: flex-end;
125+
}
126+
127+
.question-wrapper {
128+
pointer-events: auto;
129+
}

src/components/stage/stage.jsx

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import DOMElementRenderer from '../../containers/dom-element-renderer.jsx';
77
import Loupe from '../loupe/loupe.jsx';
88
import MonitorList from '../../containers/monitor-list.jsx';
99
import Question from '../../containers/question.jsx';
10+
import MicIndicator from '../mic-indicator/mic-indicator.jsx';
1011
import {STAGE_DISPLAY_SIZES} from '../../lib/layout-constants.js';
1112
import {getStageDimensions} from '../../lib/screen-utils.js';
1213
import styles from './stage.css';
@@ -18,6 +19,7 @@ const StageComponent = props => {
1819
isColorPicking,
1920
isFullScreen,
2021
colorInfo,
22+
micIndicator,
2123
question,
2224
stageSize,
2325
useEditorDragStyle,
@@ -66,13 +68,22 @@ const StageComponent = props => {
6668
<Loupe colorInfo={colorInfo} />
6769
</Box>
6870
) : null}
69-
{question === null ? null : (
70-
<div
71-
className={classNames(
72-
styles.stageOverlayContent,
73-
styles.stageOverlayContentBorderOverride
74-
)}
75-
>
71+
<div
72+
className={styles.stageBottomWrapper}
73+
style={{
74+
width: stageDimensions.width,
75+
height: stageDimensions.height,
76+
left: '50%',
77+
marginLeft: stageDimensions.width * -0.5
78+
}}
79+
>
80+
{micIndicator ? (
81+
<MicIndicator
82+
className={styles.micIndicator}
83+
stageSize={stageDimensions}
84+
/>
85+
) : null}
86+
{question === null ? null : (
7687
<div
7788
className={styles.questionWrapper}
7889
style={{width: stageDimensions.width}}
@@ -82,8 +93,8 @@ const StageComponent = props => {
8293
onQuestionAnswered={onQuestionAnswered}
8394
/>
8495
</div>
85-
</div>
86-
)}
96+
)}
97+
</div>
8798
<canvas
8899
className={styles.draggingSprite}
89100
height={0}

src/containers/stage.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ class Stage extends React.Component {
7575
this.props.isColorPicking !== nextProps.isColorPicking ||
7676
this.state.colorInfo !== nextState.colorInfo ||
7777
this.props.isFullScreen !== nextProps.isFullScreen ||
78-
this.state.question !== nextState.question;
78+
this.state.question !== nextState.question ||
79+
this.props.micIndicator !== nextProps.micIndicator;
7980
}
8081
componentDidUpdate (prevProps) {
8182
if (this.props.isColorPicking && !prevProps.isColorPicking) {
@@ -402,6 +403,7 @@ Stage.defaultProps = {
402403
const mapStateToProps = state => ({
403404
isColorPicking: state.scratchGui.colorPicker.active,
404405
isFullScreen: state.scratchGui.mode.isFullScreen,
406+
micIndicator: state.scratchGui.micIndicator,
405407
// Do not use editor drag style in fullscreen or player mode.
406408
useEditorDragStyle: !(state.scratchGui.mode.isFullScreen || state.scratchGui.mode.isPlayerOnly)
407409
});

src/css/z-index.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ $z-index-extension-button: 50; /* Force extension button above the ScratchBlocks
88
$z-index-menu-bar: 50; /* blocklyToolboxDiv is 40 */
99

1010
$z-index-monitor: 100;
11+
$z-index-stage-indicator: 110;
1112
$z-index-add-button: 120;
1213
$z-index-tooltip: 130; /* tooltips should go over add buttons if they overlap */
1314

0 commit comments

Comments
 (0)