Merge pull request #6530 from matrix-org/travis/voice-messages/uploading
Improve voice messages uploading state
This commit is contained in:
commit
280c017238
3 changed files with 56 additions and 13 deletions
|
@ -46,6 +46,21 @@ limitations under the License.
|
||||||
mask-image: url('$(res)/img/element-icons/trashcan.svg');
|
mask-image: url('$(res)/img/element-icons/trashcan.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_VoiceRecordComposerTile_uploadingState {
|
||||||
|
margin-right: 10px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_VoiceRecordComposerTile_failedState {
|
||||||
|
margin-right: 21px;
|
||||||
|
|
||||||
|
.mx_VoiceRecordComposerTile_uploadState_badge {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 4px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_MessageComposer_row .mx_VoiceMessagePrimaryContainer {
|
.mx_MessageComposer_row .mx_VoiceMessagePrimaryContainer {
|
||||||
// Note: remaining class properties are in the PlayerContainer CSS.
|
// Note: remaining class properties are in the PlayerContainer CSS.
|
||||||
|
|
||||||
|
|
|
@ -17,10 +17,7 @@ limitations under the License.
|
||||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import React, { ReactNode } from "react";
|
import React, { ReactNode } from "react";
|
||||||
import {
|
import { IUpload, RecordingState, VoiceRecording } from "../../../audio/VoiceRecording";
|
||||||
RecordingState,
|
|
||||||
VoiceRecording,
|
|
||||||
} from "../../../audio/VoiceRecording";
|
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
@ -34,6 +31,10 @@ import { MsgType } from "matrix-js-sdk/src/@types/event";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import ErrorDialog from "../dialogs/ErrorDialog";
|
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||||
import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../MediaDeviceHandler";
|
import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../MediaDeviceHandler";
|
||||||
|
import NotificationBadge from "./NotificationBadge";
|
||||||
|
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
|
||||||
|
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
|
||||||
|
import InlineSpinner from "../elements/InlineSpinner";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -42,6 +43,7 @@ interface IProps {
|
||||||
interface IState {
|
interface IState {
|
||||||
recorder?: VoiceRecording;
|
recorder?: VoiceRecording;
|
||||||
recordingPhase?: RecordingState;
|
recordingPhase?: RecordingState;
|
||||||
|
didUploadFail?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -69,9 +71,19 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
|
||||||
|
|
||||||
await this.state.recorder.stop();
|
await this.state.recorder.stop();
|
||||||
|
|
||||||
|
let upload: IUpload;
|
||||||
try {
|
try {
|
||||||
const upload = await this.state.recorder.upload(this.props.room.roomId);
|
upload = await this.state.recorder.upload(this.props.room.roomId);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error uploading voice message:", e);
|
||||||
|
|
||||||
|
// Flag error and move on. The recording phase will be reset by the upload function.
|
||||||
|
this.setState({ didUploadFail: true });
|
||||||
|
|
||||||
|
return; // don't dispose the recording: the user has a chance to re-upload
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
// noinspection ES6MissingAwait - we don't care if it fails, it'll get queued.
|
// noinspection ES6MissingAwait - we don't care if it fails, it'll get queued.
|
||||||
MatrixClientPeg.get().sendMessage(this.props.room.roomId, {
|
MatrixClientPeg.get().sendMessage(this.props.room.roomId, {
|
||||||
"body": "Voice message",
|
"body": "Voice message",
|
||||||
|
@ -104,12 +116,11 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
|
||||||
"org.matrix.msc3245.voice": {}, // No content, this is a rendering hint
|
"org.matrix.msc3245.voice": {}, // No content, this is a rendering hint
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error sending/uploading voice message:", e);
|
console.error("Error sending voice message:", e);
|
||||||
Modal.createTrackedDialog('Upload failed', '', ErrorDialog, {
|
|
||||||
title: _t('Upload Failed'),
|
// Voice message should be in the timeline at this point, so let other things take care
|
||||||
description: _t("The voice message failed to upload."),
|
// of error handling. We also shouldn't need the recording anymore, so fall through to
|
||||||
});
|
// disposal.
|
||||||
return; // don't dispose the recording so the user can retry, maybe
|
|
||||||
}
|
}
|
||||||
await this.disposeRecording();
|
await this.disposeRecording();
|
||||||
}
|
}
|
||||||
|
@ -118,7 +129,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
|
||||||
await VoiceRecordingStore.instance.disposeRecording();
|
await VoiceRecordingStore.instance.disposeRecording();
|
||||||
|
|
||||||
// Reset back to no recording, which means no phase (ie: restart component entirely)
|
// Reset back to no recording, which means no phase (ie: restart component entirely)
|
||||||
this.setState({ recorder: null, recordingPhase: null });
|
this.setState({ recorder: null, recordingPhase: null, didUploadFail: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
private onCancel = async () => {
|
private onCancel = async () => {
|
||||||
|
@ -234,7 +245,25 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let uploadIndicator;
|
||||||
|
if (this.state.recordingPhase === RecordingState.Uploading) {
|
||||||
|
uploadIndicator = <span className='mx_VoiceRecordComposerTile_uploadingState'>
|
||||||
|
<InlineSpinner w={16} h={16} />
|
||||||
|
</span>;
|
||||||
|
} else if (this.state.didUploadFail && this.state.recordingPhase === RecordingState.Ended) {
|
||||||
|
uploadIndicator = <span className='mx_VoiceRecordComposerTile_failedState'>
|
||||||
|
<span className='mx_VoiceRecordComposerTile_uploadState_badge'>
|
||||||
|
{ /* Need to stick the badge in a span to ensure it doesn't create a block component */ }
|
||||||
|
<NotificationBadge
|
||||||
|
notification={StaticNotificationState.forSymbol("!", NotificationColor.Red)}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span className='text-warning'>{ _t("Failed to send") }</span>
|
||||||
|
</span>;
|
||||||
|
}
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
|
{ uploadIndicator }
|
||||||
{ deleteButton }
|
{ deleteButton }
|
||||||
{ this.renderWaveformArea() }
|
{ this.renderWaveformArea() }
|
||||||
{ recordingInfo }
|
{ recordingInfo }
|
||||||
|
|
|
@ -1697,7 +1697,6 @@
|
||||||
"Invited by %(sender)s": "Invited by %(sender)s",
|
"Invited by %(sender)s": "Invited by %(sender)s",
|
||||||
"Jump to first unread message.": "Jump to first unread message.",
|
"Jump to first unread message.": "Jump to first unread message.",
|
||||||
"Mark all as read": "Mark all as read",
|
"Mark all as read": "Mark all as read",
|
||||||
"The voice message failed to upload.": "The voice message failed to upload.",
|
|
||||||
"Unable to access your microphone": "Unable to access your microphone",
|
"Unable to access your microphone": "Unable to access your microphone",
|
||||||
"We were unable to access your microphone. Please check your browser settings and try again.": "We were unable to access your microphone. Please check your browser settings and try again.",
|
"We were unable to access your microphone. Please check your browser settings and try again.": "We were unable to access your microphone. Please check your browser settings and try again.",
|
||||||
"No microphone found": "No microphone found",
|
"No microphone found": "No microphone found",
|
||||||
|
|
Loading…
Reference in a new issue