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:
parent
d81e2cea14
commit
39f2bbaaf4
6 changed files with 116 additions and 33 deletions
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue