Bring back waveform for voice messages and retain seeking (#8843)

* Crude way of layering the waveform and seek bar

Not intended for production.

* Use a layout prop instead of something less descriptive

* Fix alignment properly, and play with styles

* Convert back to a ball

* Use `transparent` which makes NVDA happy enough

* Allow keyboards in the seek bar

* Try to make the clock behave more correctly with screen readers

MIDNIGHT

* Remove legacy export

* Remove redundant attr

* Appease the linter
This commit is contained in:
Travis Ralston 2022-06-14 18:13:13 -06:00 committed by GitHub
parent d81e2cea14
commit 39f2bbaaf4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 116 additions and 33 deletions

View file

@ -18,7 +18,7 @@ import React, { HTMLProps } from "react";
import { formatSeconds } from "../../../DateUtils";
export interface IProps extends Pick<HTMLProps<HTMLSpanElement>, "aria-live"> {
interface IProps extends Pick<HTMLProps<HTMLSpanElement>, "aria-live" | "role"> {
seconds: number;
}
@ -31,14 +31,14 @@ export default class Clock extends React.Component<IProps> {
super(props);
}
shouldComponentUpdate(nextProps: Readonly<IProps>): boolean {
public shouldComponentUpdate(nextProps: Readonly<IProps>): boolean {
const currentFloor = Math.floor(this.props.seconds);
const nextFloor = Math.floor(nextProps.seconds);
return currentFloor !== nextFloor;
}
public render() {
return <span aria-live={this.props["aria-live"]} className='mx_Clock'>
return <span aria-live={this.props["aria-live"]} role={this.props.role} className='mx_Clock'>
{ formatSeconds(this.props.seconds) }
</span>;
}

View file

@ -76,7 +76,7 @@ export default class PlaybackClock extends React.PureComponent<IProps, IState> {
}
return <Clock
seconds={seconds}
aria-live={this.state.playbackPhase === PlaybackState.Playing ? "off" : undefined}
role="timer"
/>;
}
}

View file

@ -22,37 +22,60 @@ import AudioPlayerBase, { IProps as IAudioPlayerBaseProps } from "./AudioPlayerB
import SeekBar from "./SeekBar";
import PlaybackWaveform from "./PlaybackWaveform";
interface IProps extends IAudioPlayerBaseProps {
export enum PlaybackLayout {
/**
* When true, use a waveform instead of a seek bar
* Clock on the left side of a waveform, without seek bar.
*/
withWaveform?: boolean;
Composer,
/**
* Clock on the right side of a waveform, with an added seek bar.
*/
Timeline,
}
interface IProps extends IAudioPlayerBaseProps {
layout?: PlaybackLayout; // Defaults to Timeline layout
}
export default class RecordingPlayback extends AudioPlayerBase<IProps> {
// This component is rendered in two ways: the composer and timeline. They have different
// rendering properties (specifically the difference of a waveform or not).
private renderWaveformLook(): ReactNode {
private renderComposerLook(): ReactNode {
return <>
<PlaybackClock playback={this.props.playback} />
<PlaybackWaveform playback={this.props.playback} />
</>;
}
private renderSeekableLook(): ReactNode {
private renderTimelineLook(): ReactNode {
return <>
<SeekBar
playback={this.props.playback}
tabIndex={-1} // prevent tabbing into the bar
playbackPhase={this.state.playbackPhase}
ref={this.seekRef}
/>
<div className="mx_RecordingPlayback_timelineLayoutMiddle">
<PlaybackWaveform playback={this.props.playback} />
<SeekBar
playback={this.props.playback}
tabIndex={0} // allow keyboard users to fall into the seek bar
playbackPhase={this.state.playbackPhase}
ref={this.seekRef}
/>
</div>
<PlaybackClock playback={this.props.playback} />
</>;
}
protected renderComponent(): ReactNode {
let body: ReactNode;
switch (this.props.layout) {
case PlaybackLayout.Composer:
body = this.renderComposerLook();
break;
case PlaybackLayout.Timeline: // default is timeline, fall through.
default:
body = this.renderTimelineLook();
break;
}
return (
<div className="mx_MediaBody mx_VoiceMessagePrimaryContainer" onKeyDown={this.onKeyDown}>
<PlayPauseButton
@ -60,7 +83,7 @@ export default class RecordingPlayback extends AudioPlayerBase<IProps> {
playbackPhase={this.state.playbackPhase}
ref={this.playPauseRef}
/>
{ this.props.withWaveform ? this.renderWaveformLook() : this.renderSeekableLook() }
{ body }
</div>
);
}