Tile styling and early behaviour

This commit is contained in:
Travis Ralston 2021-06-23 19:34:34 -06:00
parent aaec9857fd
commit 9c752680ba
12 changed files with 310 additions and 30 deletions

View file

@ -22,11 +22,16 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
import { formatBytes } from "../../../utils/FormattingUtils";
import DurationClock from "./DurationClock";
import { Key } from "../../../Keyboard";
import { _t } from "../../../languageHandler";
import SeekBar from "./SeekBar";
import PlaybackClock from "./PlaybackClock";
interface IProps {
// Playback instance to render. Cannot change during component lifecycle: create
// an all-new component instead.
playback: Playback;
mediaName: string;
}
interface IState {
@ -83,13 +88,21 @@ export default class AudioPlayer extends React.PureComponent<IProps, IState> {
tabIndex={-1} // prevent tabbing into the button
ref={this.playPauseRef}
/>
<div className='mx_AudioPlayer_byline'>
<DurationClock playback={this.props.playback} />
&nbsp; {/* easiest way to introduce a gap between the components */}
{ this.renderFileSize() }
<div className='mx_AudioPlayer_mediaInfo'>
<span className='mx_AudioPlayer_mediaName'>
{this.props.mediaName || _t("Unnamed audio")}
</span>
<div className='mx_AudioPlayer_byline'>
<DurationClock playback={this.props.playback} />
&nbsp; {/* easiest way to introduce a gap between the components */}
{ this.renderFileSize() }
</div>
</div>
</div>
<span>TODO: Seek bar</span>
<div className='mx_AudioPlayer_seek'>
<SeekBar playback={this.props.playback} tabIndex={-1} playbackPhase={this.state.playbackPhase} />
<PlaybackClock playback={this.props.playback} defaultDisplaySeconds={0} />
</div>
</div>
}
}

View file

@ -22,6 +22,11 @@ import { UPDATE_EVENT } from "../../../stores/AsyncStore";
interface IProps {
playback: Playback;
// The default number of seconds to show when the playback has completed or
// has not started. Not used during playback, even when paused. Defaults to
// clip duration length.
defaultDisplaySeconds?: number;
}
interface IState {
@ -64,7 +69,11 @@ export default class PlaybackClock extends React.PureComponent<IProps, IState> {
public render() {
let seconds = this.state.seconds;
if (this.state.playbackPhase === PlaybackState.Stopped) {
seconds = this.state.durationSeconds;
if (Number.isFinite(this.props.defaultDisplaySeconds)) {
seconds = this.props.defaultDisplaySeconds;
} else {
seconds = this.state.durationSeconds;
}
}
return <Clock seconds={seconds} />;
}

View file

@ -0,0 +1,110 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { Playback, PlaybackState } from "../../../voice/Playback";
import React, { ChangeEvent, createRef, CSSProperties, ReactNode, RefObject } from "react";
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import PlayPauseButton from "./PlayPauseButton";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { formatBytes } from "../../../utils/FormattingUtils";
import DurationClock from "./DurationClock";
import { Key } from "../../../Keyboard";
import { _t } from "../../../languageHandler";
import { MarkedExecution } from "../../../utils/MarkedExecution";
import { percentageOf } from "../../../utils/numbers";
interface IProps {
// Playback instance to render. Cannot change during component lifecycle: create
// an all-new component instead.
playback: Playback;
// Tab index for the underlying component. Useful if the seek bar is in a managed state.
// Defaults to zero.
tabIndex?: number;
playbackPhase: PlaybackState;
}
interface IState {
percentage: number;
}
interface ISeekCSS extends CSSProperties {
'--fillTo': number;
}
@replaceableComponent("views.audio_messages.SeekBar")
export default class SeekBar extends React.PureComponent<IProps, IState> {
// We use an animation frame request to avoid overly spamming prop updates, even if we aren't
// really using anything demanding on the CSS front.
private animationFrameFn = new MarkedExecution(
() => this.doUpdate(),
() => requestAnimationFrame(() => this.animationFrameFn.trigger()));
public static defaultProps = {
tabIndex: 0,
};
constructor(props: IProps) {
super(props);
this.state = {
percentage: 0,
};
// We don't need to de-register: the class handles this for us internally
this.props.playback.clockInfo.liveData.onUpdate(() => this.animationFrameFn.mark());
}
private doUpdate() {
this.setState({
percentage: percentageOf(
this.props.playback.clockInfo.timeSeconds,
0,
this.props.playback.clockInfo.durationSeconds),
});
}
public left() {
console.log("@@ LEFT");
}
public right() {
console.log("@@ RIGHT");
}
private onChange = (ev: ChangeEvent<HTMLInputElement>) => {
console.log('@@ CHANGE', ev.target.value);
};
public render(): ReactNode {
// We use a range input to avoid having to re-invent accessibility handling on
// a custom set of divs.
return <input
type="range"
className='mx_SeekBar'
tabIndex={this.props.tabIndex}
onChange={this.onChange}
min={0}
max={1}
value={this.state.percentage}
step={0.001}
style={{'--fillTo': this.state.percentage} as ISeekCSS}
disabled={this.props.playbackPhase === PlaybackState.Decoding}
/>;
}
}

View file

@ -102,7 +102,7 @@ export default class MAudioBody extends React.PureComponent<IProps, IState> {
// At this point we should have a playable state
return (
<span className="mx_MAudioBody">
<AudioPlayer playback={this.state.playback} />
<AudioPlayer playback={this.state.playback} mediaName={this.props.mxEvent.getContent().body} />
<MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} showGenericPlaceholder={false} />
</span>
)

View file

@ -918,8 +918,6 @@
"Silence call": "Silence call",
"Decline": "Decline",
"Accept": "Accept",
"Pause": "Pause",
"Play": "Play",
"The other party cancelled the verification.": "The other party cancelled the verification.",
"Verified!": "Verified!",
"You've successfully verified this user.": "You've successfully verified this user.",
@ -1870,7 +1868,7 @@
"Ignored attempt to disable encryption": "Ignored attempt to disable encryption",
"Encryption not enabled": "Encryption not enabled",
"The encryption used by this room isn't supported.": "The encryption used by this room isn't supported.",
"Error decrypting audio": "Error decrypting audio",
"Error processing audio message": "Error processing audio message",
"React": "React",
"Edit": "Edit",
"Retry": "Retry",
@ -2602,6 +2600,9 @@
"Use email or phone to optionally be discoverable by existing contacts.": "Use email or phone to optionally be discoverable by existing contacts.",
"Use email to optionally be discoverable by existing contacts.": "Use email to optionally be discoverable by existing contacts.",
"Sign in with SSO": "Sign in with SSO",
"Unnamed audio": "Unnamed audio",
"Pause": "Pause",
"Play": "Play",
"Couldn't load page": "Couldn't load page",
"You must <a>register</a> to use this functionality": "You must <a>register</a> to use this functionality",
"You must join the room to see its files": "You must join the room to see its files",